paint-brush
How I Built a Low-Cost URL Shortener With Cloudflare KVby@vickyrathee
242 reads

How I Built a Low-Cost URL Shortener With Cloudflare KV

by Vikash RatheeDecember 14th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Explore the journey of creating an open-source URL shortener using Cloudflare KV. Achieve scalability and customization at a mere $5/month, offering auto-expirable links for appointment bookings. The project, showcased on IDM and GitHub, is a cost-effective alternative with powerful features like custom domains and analytics.

Company Mentioned

Mention Thumbnail
featured image - How I Built a Low-Cost URL Shortener With Cloudflare KV
Vikash Rathee HackerNoon profile picture




I am building an open-source, highly scalable, and low-cost URL shortener with Cloudflare KV.

By low-cost I mean, just $5 per month on Cloudflare worker and a free plan for basic requirement


URL shortening software




Idea

At DaySchedule, we had many customers upvote a URL shortener feature with auto-expirable links to book an appointment instead of sharing their original URL.


So, I decided to try Cloudflare for this project -


By leveraging Cloudflare KV's capabilities, the goal was to develop a solution that offered not only URL shortening but also customization options, analytics, and scalability, all while maintaining a significantly lower cost compared to our established services on AWS.


Features

  • Expirable links using cacheTtl option on KV
  • Custom domain using Cloudflare for SaaS
  • Analytics using Worker analytics


API

I used hono.dev by @yusukebe for API routing, and it's blazing fast and offers Cloudflare pages template to build Edge projects quickly.


Here is an example of server.ts from their docs -

import { Hono } from 'hono'
const app = new Hono()

app.get('/', (c) => c.text('Hono!'))

export default app


Controller

The /links API is all I need to create, update, and manage the short links.


const links = new Hono<{ Bindings: Bindings }>();

links.post(
  '/',
  validator('json', (value, c) => {
    const parsed = linkSchema.safeParse(value);
    if (!parsed.success) {
      return c.json(parsed, 400);
    }
    return parsed.data as ShortLink;
  }),
  async (ctx) => {
    const data = ctx.req.valid('json');
    // Set expire_at if given, or fallback to 1 month expiry
    const expireAt = data.expire_at
      ? dayjs(data.expire_at).unix()
      : dayjs().add(1, 'month').unix();
    if (expireAt <= dayjs().unix()) {
      return ctx.json(
        { message: 'The expire_at must be greater then current date time' },
        400
      );
    }

    let key = data.key || nanoid(7);
    let exists = await ctx.env.SHORTLINKS.get(key);
    while (exists) {
      key = nanoid(7);
      exists = await ctx.env.SHORTLINKS.get(key);
    }
    await ctx.env.SHORTLINKS.put(key, JSON.stringify(data), {
      expiration: expireAt,
    });
    return ctx.json(
      { ...data, key: key, short_url: `https://idm.in/${key}` },
      200
    );
  }
);


Code explanations -

This code is POST request to create short links. Here's a breakdown of the code to explain what I am doing:


  1. const links = new Hono<{ Bindings: Bindings }>(): It creates an instance of the Hono object.
  2. Validation with zod - The validator('json', (value, c) => {...}): function is used as a middleware for validating the incoming JSON payload defined in linkSchema.
  3. Set expiration: Checks if an expiry date is provided in the payload. If not, it sets an expiry date one month from the current date and time.
  4. Generates a unique key for the link using nanoid(7) or uses the provided key from the payload if available.
  5. Stores the link data (converted to a JSON string) in the Cloudflare KV (SHORTLINKS) with an expiration time based on the calculated expiry date.


Demo

The demo is available on IDM, it's free to use and open-sourced on Github to build your custom URL shortener.


Short your links


You can clone the repo to deploy on your Cloudflare account.


Don’t forget to Star the repository on GitHub to show your support :-)


Also published here.