Skip to content

Repository files navigation

duan - URL shortener powered by Cloudflare Workers and D1 Database

Cloudflare Workers TypeScript Cloudflare D1 Free Tier Compatible

πŸ“– Overview

duan is a lightweight, high-performance URL shortener built on Cloudflare's edge infrastructure. It leverages Cloudflare Workers for serverless compute and D1 Database for reliable storage, offering global low-latency performance.

πŸ’° Cost-Effective Solution

Completely free to run within Cloudflare's generous free tier limits:

  • Runs entirely on Cloudflare Workers and D1 Database free tier
  • No server costs or database hosting fees
  • 100,000 Worker requests per day for free
  • 5 million D1 database reads and 100,000 writes per day for free
  • Global CDN distribution at no additional cost

✨ Features

  • Fast Redirects: Serve redirects from the edge with minimal latency
  • API-First Design: Complete RESTful API for managing short links
  • Secure Authentication: API token-based authentication for administrative endpoints
  • Batch Operations: Create multiple short links in a single request
  • Custom Short Codes: Define your own memorable short codes or let the system generate random ones
  • Analytics: Track visit counts and last access time for each link
  • Lightweight: Minimal dependencies, optimized for edge deployment

πŸš€ Getting Started

Prerequisites

  • Cloudflare account with Workers and D1 access
  • A domain name for your short links.
    • Optional for pure API usage, Cloudflare provides a *.workers.dev subdomain
    • Necessary for Duan Raycast extension

Installation

  1. Create a D1 database in Cloudflare. image image

  2. Execute SQL statements in Console

    DROP TABLE IF EXISTS links;
    CREATE TABLE links (
    	short_code TEXT PRIMARY KEY,
     	original_url TEXT NOT NULL,
     	description TEXT,
     	is_enabled INTEGER DEFAULT 1,
     	created_at TEXT DEFAULT CURRENT_TIMESTAMP,
     	last_visited_at TEXT,
     	visit_count INTEGER DEFAULT 0
    );
    image image
  3. Copy the D1 Database ID. image

  4. Fork the repository to your GitHub account. image

  5. Edit the wrangler.jsonc file and replace the database_name whit duan-db and the database_id with your D1 database ID. image image

  6. Create a project in Cloudflare Workers, select your forked repository. image image image

  7. Set API_TOKEN in Cloudflare Workers. image image

    Step 7: Online random token generator: https://it-tools.tech/token-generator

    image
  8. Cloudflare provides a free domain for your worker, e.g., your-worker-name.workers.dev. image

    You can test it with cURL or HTTPie. image

  9. Bind your own domain.

    *This is required if you use the Duan Raycast extension, as .workers.dev has TLS issues.

    For easy configuration, I recommend hosting your domain on Cloudflare. The images below illustrate how straightforward the setup process is. image image image

  10. How to use it?

    The Duan extension in Raycast is currently the only UI available.

    A web interface isn't available yet. πŸ™ƒ
    

Development

  1. Clone the repository:

    git clone https://github.com/insv23/duan.git
    cd duan
  2. Install dependencies:

    npm install
  3. Create a D1 database:

    npx wrangler d1 create prod-cf-d1-short-link
  4. Update the wrangler.jsonc file with your database ID.

  5. Initialize the database schema:

    npx wrangler d1 execute prod-cf-d1-short-link --remote --file=./schema.sql
  6. Set up your API token:

    npx wrangler secret put API_TOKEN
  7. Start the deployment server:

    npx wrangler deploy

πŸ§ͺ Hurl Tests

This project includes a suite of Hurl tests for API and redirect behavior.

What’s covered

  • Authentication: missing, malformed, invalid, and valid tokens (hurl/00_auth.hurl)
  • Create/Get/List/Duplicate: single link lifecycle and 409 conflict (hurl/10_create_get_list.hurl)
  • Update & Redirect Toggle: update url, description, is_enabled; 404 when disabled; 302 with correct Location when enabled (hurl/20_update_redirect_toggle.hurl)
  • Delete: delete then verify 404 (hurl/30_delete.hurl)
  • Batch Create: mixed success/errors (201) and all-invalid (400) with counts checks (hurl/40_batch.hurl)

Prerequisites

  • Install Hurl (macOS): brew install hurl
  • Configure variables file hurl/.env: base_url=https://your-domain api_token=your-api-token

Run tests

  • Single file: hurl --test --variables-file hurl/.env hurl/00_auth.hurl
  • Common flow: hurl --test --variables-file hurl/.env hurl/00_auth.hurl hurl --test --variables-file hurl/.env hurl/10_create_get_list.hurl hurl --test --variables-file hurl/.env hurl/20_update_redirect_toggle.hurl hurl --test --variables-file hurl/.env hurl/30_delete.hurl hurl --test --variables-file hurl/.env hurl/40_batch.hurl

Notes

  • Tests use fixed shortcodes (e.g., hurl10-cases-001). Re-running against the same database may cause expected-201 creations to fail due to existing rows. Run against a fresh or staging DB, or clean data between runs.
  • The redirect tests assert status 302 and the Location header; when disabled, redirect returns 404 with JSON error.
  • Avoid running against production unless you understand the data changes these tests perform.

πŸ”Œ API Reference

Public Endpoints

  • GET /:shortcode
    • Redirects to the original URL associated with the shortcode

Protected Endpoints (require API token)

  • POST /api/links

    • Create a new short link
    • Body: { "short_code": "custom", "url": "https://example.com", "description": "Optional description" }
  • POST /api/links/batch

    • Create multiple short links in a single request
    • Body: Array of link objects [{ "url": "https://example.com", "short_code": "optional", "description": "optional" }, ...]
  • GET /api/links

    • List all links
  • GET /api/links/:shortcode

    • Get details for a specific link
  • GET /api/shortcodes

    • Get a list of all shortcodes
  • PATCH /api/links/:shortcode

    • Update an existing link
    • Body: { "url": "https://new-url.com", "is_enabled": 1, "description": "Updated description" }
  • DELETE /api/links/:shortcode

    • Delete a specific link

Authentication

All API endpoints require an API token passed in the Authorization header:

Authorization: Bearer your-api-token

πŸ“ Project Structure

src/
β”œβ”€β”€ handlers/           // Request handler directory for specific endpoint logic
β”‚   β”œβ”€β”€ createBatchLinks.ts  // Logic for batch link creation
β”‚   β”œβ”€β”€ createLink.ts       // Logic for creating a single link
β”‚   β”œβ”€β”€ deleteLink.ts       // Logic for deleting a specific link
β”‚   β”œβ”€β”€ getLink.ts          // Logic for retrieving a specific link
β”‚   β”œβ”€β”€ listLinks.ts        // Logic for listing all links
β”‚   β”œβ”€β”€ listShortcodes.ts   // Logic for listing all shortcodes
β”‚   β”œβ”€β”€ redirect.ts         // Logic for shortcode redirection
β”‚   └── updateLink.ts       // Logic for updating a link
β”œβ”€β”€ middleware/         // Middleware directory for request processing functions
β”‚   └── auth.ts             // Authentication middleware
β”œβ”€β”€ types/              // Type definitions directory for TypeScript types
β”‚   └── env.ts              // Environment variable type definitions
β”œβ”€β”€ utils/              // Utility functions directory for reusable helper functions
β”‚   └── response.ts         // Common response formatting functions
└── index.ts            // Project entry point responsible for routing

Example: Creating a batch of links

http POST https://your-worker-url.workers.dev/api/links/batch \
  Authorization:"Bearer your-api-token" \
  @batch-links.json

Example batch-links.json:

[
  {
    "url": "https://example.com/page1",
    "description": "Example page 1"
  },
  {
    "short_code": "custom1",
    "url": "https://example.com/page2",
    "description": "Example page with custom shortcode"
  }
]

πŸ“Š Database Schema

CREATE TABLE links (
    short_code TEXT PRIMARY KEY,
    original_url TEXT NOT NULL,
    description TEXT,
    is_enabled INTEGER DEFAULT 1,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    last_visited_at TEXT,
    visit_count INTEGER DEFAULT 0
);

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgements

About

URL shortener powered by Cloudflare Workers and D1 Database

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages