Contents

Build a link shortener in under 50 lines of code with Cloudflare Workers and KV

I’ve been hearing a lot about Cloudflare lately, a few weeks about I read a post by Troy Hunt where he talks about one of his recent experiences using Cloudflare Workers. Prior to this I had read about doom scrolled past tweets about Workers, but never had a chance to try them out. And again this weekend someone I follow on Twitter posted about Workers again, so I figured it was now time to give them a try.

What are Cloudflare Workers and KV?

If you aren’t familiar with Workers or KV then be sure to take a look at the Cloudflare documentation as it is extensive and very well written.

This is straight from the Cloudflare docs:

Cloudflare Workers provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.

And Cloudflare Workers KV again from the docs

Workers KV is a global, low-latency, key-value data store. It supports exceptionally high read volumes with low-latency, making it possible to build highly dynamic APIs and websites which respond as quickly as a cached static file would.

Setting up

With those definitions and background out of the way it’s time to setup the various bits for our link shortener. The first thing you’ll need is a Cloudflare account - which is free - and ideally you will also have a domain name but this isn’t required (although Cloudflare will ask you about this when you signup). I’ve setup my unravelled.dev domain to use Cloudflare DNS which will come in handy at the end of this post, however you can follow these steps with an unverified domain.

Head over to http://www.cloudflare.com to sign up for a free account.

Workers KV

Once you’re signed up the next thing to do is create the Workers KV. To do this browse to the Workers section and click on Manage KV namespaces. This will list all your existing Workers KVs and give you the option to add a new Namespace. I’ve called mine SHORT_LINKS.

/cloudflare-workers-link-shortener/images/1-workers-kv.png

We will come back to the KV at the end of the setup process.

Worker

The next step is to create the Worker. This can be done locally using the wrangler cli which is provided by Cloudflare, but I found getting going via the in-browser editor a good experience. It also means you don’t have to mess about getting components installed and authenticated.

Browse to the Workers Overview and click on Create a Worker, this will open up the inbrowser editor experience with a sample Worker.

/cloudflare-workers-link-shortener/images/2-sample-worker.png

Click Save and Deploy to deploy the sample, we’ll come back in just a minute and modify the implementation. Once it has been deployed you can click the Send button to test it out.

/cloudflare-workers-link-shortener/images/3-testing-out-the-sample.png

Binding the Worker KV and Worker

The last setup task that needs to be done is binding the Worker KV to the Worker. Click the back button to go to the Worker overview, then Settings and scroll down to KV Namespace Bindings.

/cloudflare-workers-link-shortener/images/4-worker-settings.png /cloudflare-workers-link-shortener/images/5-worker-kv-binding.png

Click Add binding and set variable name to SHORT_LINKS and then select SHORT_LINKS from the namespaces dropdown.

/cloudflare-workers-link-shortener/images/6-filled-binding.png

Show me the code

With the building blocks in place it’s time to start writing some code. The Worker is going to serve two purposes. First, when handling a POST request, it will take a URL from the request body and return a short link in the response body. Second, when handling a GET request it will use the path of the URL to look up the associated full URL that’s stored in the SHORT_LINKS KV.

Let’s take a look at the code required to handle POST requests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

function getHash() {
  //https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
  return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
}

const baseUrl = "https://broken-breeze-fe96.dev-cloudflare9033.workers.dev/";

async function handleRequest(request) {

  if (request.method === "POST") {
    const body = await request.json();
    const hash = getHash();
    await SHORT_LINKS.put(hash, body.url);
    const resp = { shortUrl: baseUrl + hash };
    const json = JSON.stringify(resp, null, 2);
    return new Response(json, { headers: { "content-type": "application/json;charset=UTF-8", }});
  } else if (request.method === "GET") {
    /* TODO */
  } else {
    /* TODO */
  }
}

A worker is made up of two parts, first there is always an event listener which is listening for fetch events and second an event handler which returns a response. Lines 1-7 show the event listener, which passes through the incoming request to the event handler function handleRequest.

In the code above you can see in the handleRequest method the first thing I’m doing is checking request.method === "POST" and if it does then I’m getting the request body as json (line 19), generating a random hash (line 20) using a method that I found on Stackoverflow.

The interesting part is line 21 where the full url from the request body is stored along with the hash into my SHORT_LINKS KV, it’s not actually very interesting because it’s just so simple, kudos to Cloudflare 🙌 for making things so easy.

The rest of this block then returns a response containing the short link url to the caller. With the in-browser editor we can take this for a quick spin.

/cloudflare-workers-link-shortener/images/7-generating-shortlinks.png

If we browse back to the Workers KV then we can see that the hash and shortlink have been stored successfully.

/cloudflare-workers-link-shortener/images/8-kv-short-links.png

Now that short links can be stored when the Worker receives a POST request, the next step is to handle GET requests and forward the user to the correct location.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
} else if (request.method === "GET") {
  const url = new URL(request.url);
  const { pathname } = url; //pathname starts with a /
  const shortLinkHash = pathname.substr(1); //remove leading /
  let value = await SHORT_LINKS.get(shortLinkHash);

  if (value === null) {
      return new Response(`Shortlink not found.`, { status: 404 });
  }

  return Response.redirect(value, 301);
}

Now when someone browses to our Worker URL this snippet of code will be executed. There isn’t much to this, lines 2-4 get the hash from the request.url, then on line 5 the hash is used to look up the value from the SHORT_LINKS KV. If there’s no value then the result is null and the Worker resonds with a 404, if a result is found then a Response.redirect is returned and the user is redirected.

Putting it all together

Here is the full code of my Worker, it includes an extra bit which returns a 405 if it receives a request that isn’t a GET or POST or request. The other thing to note is that the baseUrl on line 18 is the URL of the Worker.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

/* example request body
    { "url": "https://www.bbc.com/news/something-with-a-link" }
*/

function getHash() {
  //https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
  return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
}

const baseUrl = "https://broken-breeze-fe96.dev-cloudflare9033.workers.dev/";

async function handleRequest(request) {

  if (request.method === "POST") {
    const body = await request.json();
    const hash = getHash();
    await SHORT_LINKS.put(hash, body.url);
    const resp = { shortUrl: baseUrl + hash };
    const json = JSON.stringify(resp, null, 2);
    return new Response(json, { headers: { "content-type": "application/json;charset=UTF-8", }});
  } else if (request.method === "GET") {
    const url = new URL(request.url);
    const { pathname } = url; //pathname starts with a /
    const shortLinkHash = pathname.substr(1); //remove leading /
    let value = await SHORT_LINKS.get(shortLinkHash);

    if (value === null) {
        return new Response(`Shortlink not found.`, { status: 404 });
    }

    return Response.redirect(value, 301);
  } else {
    return new Response(`Method ${request.method} not allowed.`, {
      status: 405,
      headers: {
        Allow: "GET, POST",
      },
    });
  }
}

Taking it further

The last thing that can be done to make this a more complete example is to setup a route for the Worker, this means that Cloudflare can be configured with a custom DNS entry for the Worker - so that you can use your own domain. For details checkout the excellent Routes documentation.

Something else you would probably want to do is put authentication in place so you can control who can generate shortlinks. I’ll save that exercise for the reader 🙂 (hint, there are plenty of examples available).

This post can be accessed via https://go.unravelled.dev/kkiyt served up using the above Worker.

🍪 I use Disqus for comments

Because Disqus requires cookies this site doesn't automatically load comments.

I don't mind about cookies - Show me the comments from now on (and set a cookie to remember my preference)