DEV Community

Tomoyuki KOYAMA
Tomoyuki KOYAMA

Posted on • Edited on

Create URL shortener by Cloudflare Workers

Introduction

In order to use a domain name which was unused, I developed a web service of URL shortener by myself.

Choosing Cloud Providers and its Services

The service candidates for use include container-based service or serverless computing services. URL shortener service has a simple processing logic and doesn't require storing data as in-memory data. Therefore, serverless computing services are chosen.

I have experience for using public cloud services such as AWS, GCP, and Azure. In contrast, I have no experience for using the web services which is provided by Cloudflare. Therefore, this project uses Cloudflare Workers as the service platform.
The requirement for this project is to store URL on databases. Cloudflare provides Cloudflare Workers KV as key-value pair store. These services are provided as a free tier service.

System Architecture

      Clients
         |
         v
+-----------------+     +--------------------+
|Cloudflare Worker| --> |Cloudflare Worker KV|
+-----------------+     +--------------------+
Enter fullscreen mode Exit fullscreen mode

Codes

Following codes are deployed on Cloudflare Workers as a function. Then, you create a key-value store on Cloudflare Workers KV, and this is named as MY_KV.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  if (request.method === "GET") {
    return readRedirect(request)
  } else if (request.method === "POST") {
    return createRedirect(request)
  } else {
    return new Response("Forbidden", { status: 403 })
  }
};

async function readRedirect(request) {
  const url = new URL(request.url);
  const req_path = url.pathname;

  const value = await MY_KV.get(req_path);
  if (value === null) {
    console.log("Not Found", req_path);
    return new Response("Not Found", { status: 404 });
  }

  console.log("Redirect", req_path);
  const statusCode = 301;
  return Response.redirect(value, statusCode);
};

async function createRedirect(request) {
  const url = new URL(request.url);
  const req_path = url.pathname;

  const secret_token = "xxxxx-xxxxxx-xxxxxx-xxxxxx";
  if (req_path !== "/token/" + secret_token) {
    return new Response("Invalid Token", { status: 401 });
  }

  const content_type = request.headers.get("content-type");
  if (!content_type.includes("application/json")) {
    return new Response("Invalid Content Type", { status: 400 });
  }
  const request_body = await request.json();
  const redirect_from_path = request_body["from_path"];  // "/foo"
  const redirect_to_url = request_body["to_url"]; // http://example.com
  await MY_KV.put(redirect_from_path, redirect_to_url);
  return new Response("https://1ch.dev" + redirect_from_path);
};
Enter fullscreen mode Exit fullscreen mode

Usage

iOS Shortcuts

Image description

CLI commands

This code is supported for .zshrc.

function tiny() {
    read to_url
    echo $to_url | grep 'http\(s\)\?://' || {
        echo "Invalid URL"
        return 1
    }

    from_path="/cli-$(date +%s)"
    curl -XPOST -H 'Content-Type: application/json' \
      -d "{\"from_path\": \"$from_path\", \"to_url\": \"$to_url\"}" \
      https://1ch-dev.xxxxxxxx.workers.dev/token/xxxxxxxx \
      | pbcopy
}
Enter fullscreen mode Exit fullscreen mode

Usage:

echo "http://example.com/" | tiny
Enter fullscreen mode Exit fullscreen mode

Top comments (0)