Skip to content
Pitrick3141Public
forked from x-dr/short

About

A URL Shortener based on Cloudflare

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

Pi3 Short Link

A modern, multilingual URL shortener from PiTrick Technology, built on Cloudflare Workers with Workers Static Assets and D1.

The project has been migrated from the legacy Cloudflare Pages Functions layout to a module Worker. wrangler.jsonc is the deployment source of truth, and current Wrangler releases can provision the DB D1 binding automatically on the first deployment.

Features

  • POST /create creates a random or custom short link.
  • GET /<slug> redirects to the saved URL and records a visit.
  • Optional expiry periods invalidate links after their deadline without deleting the link or its analytics.
  • The public interface follows the browser language by default and offers a persistent selector for English, Simplified Chinese, Traditional Chinese, and Japanese.
  • /admin provides token-gated link management and analytics.
  • Links can be invalidated, revalidated, searched, paged, and removed.
  • Domain blacklists are enforced both when a link is created and immediately before redirecting.
  • Configurable slug length, random length, character combinations, and reserved words.
  • D1 statements are parameterized, and random slug collisions are retried safely.
  • Static files and the Worker deploy together as one Workers application.
  • The schema initializes automatically, so a fresh deployment is immediately usable.

Local development

Use Node.js 22 or newer (required by the current Wrangler release).

npm install
cp .dev.vars.example .dev.vars
# Edit .dev.vars and replace the example ADMIN_TOKEN.
npm run dev

Wrangler creates a persistent local D1 database automatically. Open the URL printed by Wrangler, usually http://localhost:8787.

The public interface remains at /. Open /admin and sign in with the token from .dev.vars. The token is exchanged for a signed, HttpOnly, eight-hour session cookie and is not stored in browser storage.

Optional: apply the checked-in migration explicitly to the local database:

npm run db:migrate:local

Deploy to Cloudflare Workers

Option 1: Wrangler CLI

npm install
npx wrangler login
cp .dev.vars.example .dev.vars
# Put a long, random ADMIN_TOKEN in .dev.vars, then deploy it with the code:
npx wrangler deploy --secrets-file .dev.vars

ADMIN_TOKEN is declared as a required encrypted Worker secret. Do not put its value in wrangler.jsonc, source code, or Git. For an existing deployment, update it later with npm run secret:admin.

On the first deployment, Wrangler automatically creates and binds a D1 database because wrangler.jsonc declares DB without an account-specific database ID. The Worker creates and upgrades the tables on its first request. The deployment URL is printed as https://short-link.<your-subdomain>.workers.dev.

If Wrangler writes a database_id into your local wrangler.jsonc, keep it locally for this deployment or remove it before publishing the repository as a reusable template. Cloudflare keeps the provisioned resource linked to the deployed Worker.

Option 2: Automatic deployment from GitHub or GitLab

  1. Push this repository to GitHub or GitLab.
  2. For the existing short-link Worker, open Settings > Variables and Secrets and add a Secret named ADMIN_TOKEN. Use a long random value, then deploy that settings change.
  3. In the Cloudflare dashboard, open Workers & Pages and choose Create application > Import a repository, or update the existing Worker's Git connection.
  4. Select the repository and production branch.
  5. Leave the build command empty. Use npx wrangler deploy as the deploy command (the current Workers Builds default).
  6. Save and deploy.

For a completely new Worker, perform the CLI deployment with --secrets-file once before enabling Workers Builds. Wrangler validates required runtime secrets before every deployment.

Workers Builds uses the repository's Wrangler version and provisions the D1 binding during the first build. Every later push to the production branch deploys automatically. Preview branches are uploaded as preview versions by default.

Do not also enable a separate GitHub Actions deployment unless you intentionally want two deployment pipelines.

Optional: GitHub Actions instead of Workers Builds

Cloudflare's official cloudflare/wrangler-action@v3 can deploy this repo. Add these repository secrets:

  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN

Create a scoped token with Workers Scripts: Edit and D1: Edit for the target account. Then use the current workflow from Cloudflare's GitHub Actions guide.

Existing D1 database

To reuse a database from an older Pages deployment, replace the draft binding in wrangler.jsonc with its details:

"d1_databases": [
  {
    "binding": "DB",
    "database_name": "short",
    "database_id": "YOUR_EXISTING_DATABASE_ID"
  }
]

The Worker uses CREATE TABLE IF NOT EXISTS and keeps legacy slugs readable, so it is compatible with the original links and logs tables. Back up the database before changing a production deployment.

On first request after this upgrade, the Worker adds missing analytics and expiry columns and backfills total/last-click data once from existing logs. The versioned 0002_admin.sql migration adds the flexible settings and blacklist tables; 0003_expiry.sql records the runtime-safe expiry upgrade. Runtime bootstrap remains idempotent, so automatic deployments do not require a separate migration command.

For a new explicitly configured database, you can apply the versioned migration after adding its ID:

npm run db:migrate:remote

API

Create a random slug:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}' \
  https://short-link.<your-subdomain>.workers.dev/create

Create a custom slug:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","slug":"docs"}' \
  https://short-link.<your-subdomain>.workers.dev/create

Create a link that expires after seven days (expiresIn is measured in seconds and supports up to one year):

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","slug":"event","expiresIn":604800}' \
  https://short-link.<your-subdomain>.workers.dev/create

Response:

{
  "slug": "docs",
  "link": "https://short-link.<your-subdomain>.workers.dev/docs",
  "expiresAt": null
}

Custom slugs accept 2-10 letters, numbers, underscores, or hyphens. create is reserved.

The defaults above can be changed from Admin > Slug rules. Built-in application paths remain reserved regardless of custom rules.

Admin API

The browser panel uses its signed HttpOnly session. Scripts can authenticate directly with the same secret:

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  "https://short-link.<your-subdomain>.workers.dev/api/admin/links?page=1&search=docs"

Admin endpoints include:

  • GET /api/admin/links — paged list and slug/URL search.
  • PATCH /api/admin/links/:id — { "active": false } invalidates a link; true restores it. Restoring an expired link clears its elapsed expiry deadline.
  • DELETE /api/admin/links/:id — removes the link and its analytics.
  • GET /api/admin/links/:id/analytics — totals, daily clicks, referrers, countries, and recent events.
  • GET|POST /api/admin/blacklist and DELETE /api/admin/blacklist/:id.
  • GET|PUT /api/admin/slug-rules.

Click analytics retain the IP address, user agent, referrer, country code, and timestamp already available to the Worker. Adjust this retention behavior if your privacy policy or local law requires it.

About

A URL Shortener based on Cloudflare

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages