Easy link shortener in JavaScript, Cloudflare Workers and Telegram Bot
Tags: cloudflare, javascript, telegram
Published: 2021-03-20 21:10:49

What if you need to quickly create a short link?  Of course - use a link shortener.  What if you also make this link readable?  Still using your own domain?  And it would be better to do it without additional servers.  It seems that there is an answer.

## Background

The idea of ​​an "easy link shortener" came to me when I was looking for a redirect option using a domain for one of the rooms in the newfangled Clubhouse social network.  The essence of the call forwarding idea for a room was to restart a room with the same name, but always online.  It was necessary to solve the problem of constantly changing the address of the room by parking such a link to the sub-domain.

The solution came up by itself, since the site was pre-planted on Cloudflare.  Initially, I used the “Page Rules” function, which allows you to set, among other things, redirect rules, but soon the idea came to make this redirection more flexible and changeable without the need to go into the service settings.  Of course, Telegram Bot became such a solution.

## Formulation of the problem

In order to accomplish our plan, several problems need to be solved:

 * How to redirect from a specific sub-domain?
 * Where to save links by key (path) - value (forwarding address)?
 * How to create such path?

As you may have guessed, the answers to these questions are in the very title of the article. Therefore, I propose to proceed to the practical part.


## Preconditions

For a more detailed description, I will note the basic conditions necessary for the implementation of our project:

* Domain connected to Cloudflare;
* General knowledge of JavaScript;
* Telegram bot created;
* Documentation for [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Telegram Bot API](https://core.telegram.org/bots/api).

How to fulfill the necessary preconditions is not covered in this article. The solution to these problems remains with the reader.

## Starting

 It would seem that all the preconditions are met - "What other preparation?". I propose to mark several steps in preparation for implementation:

### 1. Creation of storage - [Cloudflare KV](https://developers.cloudflare.com/workers/reference/storage/namespaces/) will help us.

Cloudflare KV is a key-value database for Workers. _As you understand, the second problem was solved by the forces of Cloudflare itself._

The sequence is simple: on the page of our Workers, go to the KV tab, enter the desired name for the storage, click add.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nd2l8k7svxl7hzz9bgb6.png)

By the result, we can even see what is inside our storage. Not surprisingly nothing, but we can load our desired abbreviations here directly.  You may need this to start working with them, for example, test the redirection first.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uf6thyvexbfh6q1s1c25.png)

### 2. We create our own Worker and configure it.

To do this, use the "Create worker" button, immediately save and deploy a new Worker ("Save and Deploy") in the editor and return back to the menu.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtd1rqdzow3jcni1vdc8.png)

Immediately set a sane name and go to "Settings" to write down the token of our Telegram bot, as well as bind the storage.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osig3d24ouh18fog599y.png)


### 3. Bind the sub-domain to the script

In order for a call to the desired address, in my case url.mydomain.com, to direct the user to our future "service-shortener", we will set up a binding to a sub-domain.  

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ygc77w01xpur7ok52g1.png)

Namely, on the "Workers" page for our domain, we need to add our "Route" to the future shortener service.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4tyffljf0ak7jcm3pezh.png) 

Note that the asterisk at the end of the link means that any value after our domain (path) will be routed to the abbreviation.

This is an important aspect in order for everything to work further.

Accordingly, we also create a record in DNS so that all requests pass DNS check.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3kg6vqli570hiaywelg3.png)

Done!  We can start coding.

## Implementation 

Let's start with the direct implementation. Further actions will take place in the code editor provided by Cloudlfare.  We have already seen it before initializing the new Worker. Let's go back there using the "Quick edit" button on the page of our project.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ycdcg9xhzu0sxznd2rk.png)

Our service will consist of two parts:

* Call forwarding
* Recording a new cut

To implement redirection, we will write a function that will take a value from our database and, if the path we entered (URL path) is found, it will create a redirect.  Otherwise, we will issue a 404 error.

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

/**
 * Respond to the request
 * @param {Request} request
 */
async function handleRequest(request) {
  const requestUrl = new URL(request.url);
  const path = requestUrl.pathname.substring(1); // Let's delete "/" symbol
  return await redirect(path)
}

/**
 * Make redirect
 * @param {string} shortName
 */
async function redirect(shortName) {
  // Get value of address that has been requested by short link
  const url = await db.get(shortName);
  if (url) {
    return Response.redirect(url)
  }
  // Short link not found
  return new Response(null, {status: 404})
}
```

Right there, in the right half of the editor that allows you to debug the code that has not yet been deployed, we check the redirection:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i9u6yu9spfz0v35yx23.png)
 
Now let's get down to implementing the second part.  Here the task will be more voluminous.  To begin with, we will determine that it was Telegram who knocked on us through the URL we specified.  Next, let's check that we wrote it to the bot so that no one else has access to the bot, we will write our Telegram User ID into a constant.  The next step is to get from the sent message a short path and a link where to redirect and write the link to the database.  Finally, we will connect our bot via webhooks.

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

const ADMIN = 11111111; // Our Telegram User ID

/**
 * Respond to the request
 * @param {Request} request
 */
async function handleRequest(request) {
  const requestUrl = new URL(request.url);
  const path = requestUrl.pathname.substring(1);
  // Added bot token check
  if (path == BOT_TOKEN) {
    return await bot(await request.json())
  }

  return await redirect(path)
}

/**
 * Make redirect
 * @param {string} shortName
 */
async function redirect(shortName) {
  const url = await db.get(shortName);
  if (url) {
    return Response.redirect(url)
  }
  return new Response(null, {status: 404})
}

/**
 * Create new shorten URL
 * @param {Object} update
 */
async function bot(update) {
  // Skip message from not admin users
  if (update.message.from.id != ADMIN) {
    return new Response("OK", {status: 200})
  }
  // Split message eg "shortname url"
  const [shortName, url] = update.message.text.split(" ");
  // Let's remember short link
  await db.put(shortName, url);
  const response = {
    "method": "sendMessage",
    "text": `Now ${url} are available by url.mydomain.com/${shortName}`,
    "chat_id": update.message.from.id
  }

  return new Response(
    JSON.stringify(response), 
    {
      status: 200,
      headers: new Headers({"Content-Type": "application/json"})
    }
  )
}
```

Right there, in the debug, we check the work of our code:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfnq4bwncduiulsno77r.png)

Let's look into our database to make sure that everything has been recorded (we can immediately clear the storage from our test values):
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aznftz9qp4av0pv5h386.png)

The only thing left is to add a Telegram Bot Webhook to our page.  We have everything ready for this, so we will use the format link: `https://api.telegram.org/bot[BOT_TOKEN]/setWebhook?url=url.domain.com/[BOT_TOKEN]`

The Telegram API response should be:

``` json
{"ok":true,"result":true,"description":"Webhook was set"}
```

We check the result of the bot's work.  We send him a short name and a link, as set in the code, and try to follow the link to check the functionality.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6h3o3zsfdgrpks6lfpl.png)

"He's alive!"

Conclusion

As a result, we have a short and easy-to-implement "link shortener" that we can modify at our discretion.

It is worth noting that this approach has some limitations, which can be found on [the Cloudflare Workers page](https://developers.cloudflare.com/workers/platform/pricing). In short:

* we can write to the database up to 1000 values ​​per day (the maximum possible number of created abbreviations);
* read from the database up to 100,000 times a day (maximum number of visits);
* the script itself can be run up to 100,000 times a day (the number of messages to the bot and visits to shortened links);
* the script should run no more than 1000 times per minute.

These restrictions should be enough for personal use, share your opinion on this in the comments.