• Stars
    star
    134
  • Rank 270,967 (Top 6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created 10 months ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Creating URL Shortener with Cloudflare Pages

Creating URL Shortener with Cloudflare Pages

Let's create a super simple URL Shortener with Cloudflare Pages! By creating this application you will experience:

  • Creating web pages with Hono.
  • Using Cloudflare KV in your application.
  • Deploying your application to Cloudflare Pages.

The application feature

  • Developing with Vite.
  • Having UI.
  • The main code is less than 100 lines.
  • Validation with Zod.
  • Handling validation error.
  • CSRF Protection.

Demo

Demo

Source Code

You can see the entire source code here.

Tutorial

I'll show you how to create your application!


Account

To deploy an application to Cloudflare Pages, a Cloudflare account is needed. Since it can be used within the free tier, if you don't have an account, please create one.

Project Setup

Let's start by setting up the project.

Initial Project

We'll use a CLI called "create-hono" to create the project. Execute the following command:

npm create hono@latest url-shortener

When prompted to choose a template, select "cloudflare-pages". Then, when asked about installing dependencies and which package manager to use, press Enter to proceed.

Now, you have your initial project setup. Enter the project directory:

cd url-shortener

Start the Development Server

Let's start the development server. It's easy, just run the following command:

npm run dev

By default, it launches at http://localhost:5173, so access it. You should be able to see the page.

Create KV

This app uses Cloudflare KV, a Key-Value store. To use it, you need to create a KV project by running the following command:

npm exec wrangler kv:namespace create KV

You'll see a message like this:

🌀 Creating namespace with title "url-shortener-KV"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "KV", id = "xxxxxx" }

Copy the id value xxxxxx, and write it into wrangler.toml in the format shown above.

Install Dependencies

For this app, we'll validate input values. For that, we'll include the Zod library and Hono middleware.

npm i zod @hono/zod-validator

Remove public

Finally, the starter template includes a public directory with CSS for customization, but since we won't use it this time, let's remove it.

rm -rf public

Writing Code

Now, let's start coding.

Organize Layout

We'll arrange a common layout for the pages by editing src/renderer.tsx.

To save time, we'll use a CSS framework called new.css, which is a class-less framework. This means you don't need to specify any special class values; the existing HTML styles will automatically look good.

The final version will look like this:

import { jsxRenderer } from 'hono/jsx-renderer'

export const renderer = jsxRenderer(({ children }) => {
  return (
    <html>
      <head>
        <link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css" />
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/[email protected]/new.min.css"></link>
      </head>
      <body>
        <header>
          <h1>
            <a href="/">URL Shortener</a>
          </h1>
        </header>
        <div>{children}</div>
      </body>
    </html>
  )
})

Making the top Page

First, we're making a top page. It responds when someone visits the main path /. Here's how we set it up.

app.get('/', (c) => {
  //...
})

Inside the handler, we use c.render() to return HTML with our layout applied. We've set it up to send a POST request to /create to make a short URL.

app.get('/', (c) => {
  return c.render(
    <div>
      <h2>Create shorten URL!</h2>
      <form action="/create" method="post">
        <input
          type="text"
          name="url"
          autocomplete="off"
          style={{
            width: '80%'
          }}
        />
        &nbsp;
        <button type="submit">Create</button>
      </form>
    </div>
  )
})

This will look something like this:

Screenshot

Making a Validator

We want to check the form data from the top page. So, let's make a validator.

First, we import stuff from the library we installed earlier.

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

Then, we make a schema. This is how we say, "We want a string that's a URL named url".

const schema = z.object({
  url: z.string().url()
})

We register this with zValidator. The form we pass as the first argument is because we want to handle form requests.

const validator = zValidator('form', schema)

Let's make an endpoint to handle the POST request to /create using our finished validator. Since a validators is middleware, we can put it before our handler. Then, we use c.req.valid() to get the value, which in this case, is named url.

app.post('/create', validator, async (c) => {
  const { url } = c.req.valid('form')

  // TODO: Create a short URL
})

If it passes the check, the value will be in url.

Defining KV Types

Now that we have the form value, let's write the logic to make a short URL.

First, we define the types for KV we're using. KVNamespace represents KV. In Hono, if you pass Bindings as a name for Cloudflare's Bindings type to the Hono class generics, you can then access c.env.KV with types.

type Bindings = {
  KV: KVNamespace
}

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

Generating and Saving Keys

Let's make a function named createKey() to generate keys for short URLs. We need the KV object and the URL to generate a key.

app.post('/create', validator, async (c) => {
  const { url } = c.req.valid('form')

  const key = await createKey(c.env.KV, url)

  // ...
})

There are a few strategies for generating a key, but we'll go with this:

  • Create a random string.
  • Use 6 characters of it.
  • If there's no object in KV with that key, save the URL as its value.
  • If there is, run createKey() again.
  • Return the created key.

You can get and set values in KV with kv.get(key) and kv.put(key, value).

The finished function looks like this:

const createKey = async (kv: KVNamespace, url: string) => {
  const uuid = crypto.randomUUID()
  const key = uuid.substring(0, 6)
  const result = await kv.get(key)
  if (!result) {
    await kv.put(key, url)
  } else {
    return await createKey(kv, url)
  }
  return key
}

Showing the Result

Now we've made a key. The URL with this key as the pathname is our short URL. If you're developing locally and your key was, for example, abcdef, it would be:

http://localhost:5173/abcdef

We made a page to display this URL in an input element for easy copying, using autofocus too.

app.post('/create', validator, async (c) => {
  const { url } = c.req.valid('form')
  const key = await createKey(c.env.KV, url)

  const shortenUrl = new URL(`/${key}`, c.req.url)

  return c.render(
    <div>
      <h2>Created!</h2>
      <input
        type="text"
        value={shortenUrl.toString()}
        style={{
          width: '80%'
        }}
        autofocus
      />
    </div>
  )
})

Now, the short URL is created and displayed nicely.

Screenshot

Redirecting

Now that we can generate short URLs, let's make them redirect to the registered URL. We use regex to match the address like /abcdef and, in the handler, get the value from KV using that string as the key. If it exists, that's the original URL, and we redirect there. If not, we go back to the top page.

app.get('/:key{[0-9a-z]{6}}', async (c) => {
  the key = c.req.param('key')
  const url = await c.env.KV.get(key)

  if (url === null) {
    return c.redirect('/')
  }

  return c.redirect(url)
})

Handling Errors

We're almost done, and it's looking good!

But one issue is what happens if someone puts a non-URL value in the form. The validator catches the error, but it just shows a string of JSON.

Screenshot

Let's show an error page instead. For this, we write a hook as the third argument to zValidator. result is the result object from Zod validation, so we use it to decide what to do based on whether it was successful.

const validator = zValidator('form', schema, (result, c) => {
  if (!result.success) {
    return c.render(
      <div>
        <h2>Error!</h2>
        <a href="/">Back to top</a>
      </div>
    )
  }
})

Now, if there's a validation error, an error message is shown.

Screenshot

Adding a CSRF Protector

This is the last step! Our URL shortening service is pretty great as it is, but there's a chance someone could send a POST request directly from a form on a different site. So, we use Hono's built-in middleware, CSRF Protector.

It's super easy to use. Just import it.

import { csrf } from 'hono/csrf'

And use it before the handler on routes where you want it.

app.post('/create', csrf(), validator, async (c) => {
  const { url } = c.req.valid('form')
  const key = await createKey(c.env.KV, url)
  //...
})

And that's it! You've made a URL shortening app with a UI, validation, error handling, and CSRF protection, all within about 100 lines in index.tsx!

Deploying

Let's deploy to Cloudflare Pages. Run the following command:

npm run deploy

If it's your first time, you'll be asked a few questions like this. Just answer them:

Create a new project
? Enter the name of your new project: › url-shortener
? Enter the production branch name: › main

After running the command, a URL for your deployed site will be displayed. It might look something like this:

https://random-strings.url-shortener-abc.pages.dev/

It takes a bit of time to be ready for viewing after it's created, so let's wait. In some cases, you might be able to view it by accessing url-shortener-abc.pages.dev, removing the initial host name part.

Setting KV in the Dashboard

But wait! You might see an "Internal Server Error". This is because KV settings are not done for the production environment. Despite writing settings in wrangler.toml, they won't apply; dashboard settings are required. Go to the settings page of the Pages project you created, navigate to the KV section, and specify the namespace you created earlier with the name KV.

Screenshot

Deploying again should work now!

Deleting the Project

If you're not planning to use it, remember to delete the production Pages project.

Summary

We made a URL shortening app using Cloudflare KV and Hono and deployed it to Cloudflare Pages. The main src/index.tsx is about 100 lines, but it's a complete app with page layouts, validation, and error handling, not just "returning JSON". However, as it stands, external users could potentially create unlimited short URLs, hitting KV indefinitely, so consider this for further development.

How was it? Pretty neat, right? Creating apps on Cloudflare Pages with Hono offers a lot of possibilities, so give it a try. Also, if you're building a bigger app, HonoX, which allows for file-based routing, might be more convenient, so consider using that too.


Author

Yusuke Wada https://github.com/yusukebe

License

MIT

More Repositories

1

gh-markdown-preview

GitHub CLI extension to preview Markdown looks like GitHub.
Go
362
star
2

revealgo

Markdown driven presentation tool written in Go!
Go
230
star
3

r2-image-worker

Store and Deliver images with R2 backend Cloudflare Workers.
TypeScript
160
star
4

hono-htmx

Hono+htmx stack
TypeScript
150
star
5

pico

Ultra-tiny router for Cloudflare Workers and Deno
TypeScript
133
star
6

ramen-api

Web API about 🍜
TypeScript
100
star
7

hono-and-remix-on-vite

TypeScript
93
star
8

top-github-users-only-japan

「Top GitHub Users」ただし日本に限る
62
star
9

rj

CLI for printing HTTP Response as JSON.
Go
46
star
10

minidon

Minimal implementation of ActivityPub using Cloudflare Workers and D1
TypeScript
42
star
11

hono-spa-react

TypeScript
41
star
12

chatgpt-streaming

TypeScript
40
star
13

sonik

[WIP] Supersonik web framework for the Edges.
TypeScript
40
star
14

App-revealup

HTTP Server app for viewing Markdown formatted text as slides
Perl
38
star
15

honox-examples

HonoX examples
TypeScript
38
star
16

t

t is a command line tool for testing on your terminal.
Go
33
star
17

cloudflare-d1-drizzle-honox-starter

cloudflare-d1-drizzle-honox-starter
TypeScript
31
star
18

my-first-workers-ai

TypeScript
30
star
19

go-pngquant

Golang wrapper of pngquant / PNG compressor
Go
29
star
20

service-worker-magic

Server and Browser code are really the same. It's magic.
JavaScript
28
star
21

sonik-blog

TypeScript
27
star
22

hono-examples

My Hono examples
TypeScript
27
star
23

Yomico

Yet Another Markdown Viewer
CSS
25
star
24

marks

Bookmarks App with Cloudflare Workers.
TypeScript
23
star
25

Noe

true tears on web application framework.
Perl
21
star
26

hono-with-vercel-ai

An example for using Vercel AI SDK with Hono
TypeScript
21
star
27

hello-d1

My first Cloudflare D1 app
TypeScript
20
star
28

remix-on-hono

Remix on Hono
TypeScript
20
star
29

Hitagi

Shall we talk about stars and micro web application frameworks.
Perl
20
star
30

hono-remix-adapter

Hono <-> Remix Adapter
TypeScript
20
star
31

slack-deno-karma

Example for new Slack platform with Deno
TypeScript
19
star
32

h-web-router

H - Ultra-small Web Router built with Hono's Pattern Router
TypeScript
18
star
33

hono-with-qwik

TypeScript
18
star
34

r2-blog

Experimental blogging system using Cloudflare R2
TypeScript
18
star
35

vite-plugin-wrangler-dev

[WIP] Vite with Wrangler
TypeScript
17
star
36

cloudflare-pages-step-by-step

Create a full-stack application on Cloudflare Pages step-by-step.
TypeScript
17
star
37

file-base-routing-framework

TypeScript
14
star
38

Acme-Porn-JP

Porn terms in Japan.
Perl
13
star
39

cf-s3-uploader

Cloudflare Worker for uploading images to Amazon S3
TypeScript
13
star
40

Kutter

A Web Application displays the tweets about everyone eating. It's sample of Catalyst, DBIx::Class, and Moose.
Perl
13
star
41

hono-jsx-server-components-simple

TypeScript
13
star
42

hono-playground

HTML
12
star
43

slack-zenra-bot

An example for "new Slack platform" with Deno.
TypeScript
12
star
44

mirror

mirror is command line tool for mirroring a web page.
Go
12
star
45

Shiori

Yet another Perl implementation of Shiori web-app.
Perl
11
star
46

signed-request-middleware

SignedRequest Middleware for Hono
TypeScript
11
star
47

Dropdown

Display Markdown on Dropbox as cool HTML.
Perl
10
star
48

TailF

Web Application like a "tail -f xxx" command for Twitter etc.
Perl
10
star
49

xc

Express-like HTTP Client
TypeScript
10
star
50

my-first-js-rpc

My first JS RPC for Cloudflare Workers
TypeScript
10
star
51

Podder

Cool and Easy standalone viewer of Perl codes, Pods, and Inao style Texts in local directory.
CSS
9
star
52

web-framework-bench

For web frameworks on Node, on Deno, and on Bun.
TypeScript
9
star
53

hono-jsx-spa

TypeScript
9
star
54

sushi-ramen-to

https://🍣🍜.to
TypeScript
8
star
55

async-local-storage-middleware

TypeScript
8
star
56

dynamic-content-storing

Dynamic Content Storing
TypeScript
7
star
57

miyagawanize

Any Perl Mongers can be like Miyagawa-San with a "Purple Thing" !
Perl
7
star
58

Haartraining-App

Plack Application For Creating Positive/Negative Collection Files of Haartraning OpenCV.
JavaScript
7
star
59

workerd-minimal

Really minimal project for "workerd"
TypeScript
7
star
60

App-mookview

View Markdown texts as a "Mook-Book"
CSS
7
star
61

hono-vite-pages-template

TypeScript
7
star
62

get-platform-proxy-cloudflare-ai

TypeScript
7
star
63

Acme-Zenra

zenrize Japanese sentence.
Perl
7
star
64

preact-ssr

Preact SSR on Cloudflare Workers
TypeScript
7
star
65

WWW-Veoh-Download

WWW::Veoh::Download is module to get and download mp4 files for iPod etc. from Veoh Video Network.
Perl
7
star
66

Nagare

IRC Proxy using HTTP Streaming with Tatsumaki
JavaScript
7
star
67

iekei-workers

家系ラーメン食べたい!
TypeScript
6
star
68

karma-bot

Slack Bot with Cloudflare Workers. It’s about Karma.
TypeScript
6
star
69

hono-hybrid-build

TypeScript
6
star
70

Plagger-Plugin-Notify-XMPP-AnyEvent

Yet Another XMPP Notify Plagger Plugin using AnyEvent::*
Perl
6
star
71

hono-with-preact

TypeScript
6
star
72

ListPod-App-Lite

Convert YouTube Playlist to Podcast feed. It is branche of listpod.tv.
Perl
6
star
73

my-first-constellation

TypeScript
6
star
74

yusu.ke

My website
TypeScript
6
star
75

remix-on-nextjs

Remix runs on Next.js Edge API Routes
TypeScript
6
star
76

hono-ssg-example

TypeScript
6
star
77

js-rpc-examples

TypeScript
6
star
78

hono-htmx-websockets

Hono+HTMX+WebSockets
TypeScript
6
star
79

Daramen

ラーメン画像をひたすらダラダラ見るWebアプリ
CSS
5
star
80

plainrouter

Fast and simple routing engine for Ruby
Ruby
5
star
81

cloudflare-workshop

MDX
5
star
82

chat-gpt-todos

Creating ChatGPT plugin with Zod OpenAPI Hono
TypeScript
5
star
83

FMTube

Listen and watch YouTube videos with recommendations from Last.fm
CSS
5
star
84

miniflare-get-bindings-examples

TypeScript
5
star
85

Rumi

Yet Anothor Girl, just web application framework.
Perl
5
star
86

PerlTamago

Perl初心者向けのごくごくシンプルなサンプルスクリプト集
5
star
87

fastly-compute-slack-command

Slack echo command example for Fastly Compute@Edge.
JavaScript
5
star
88

WebService-Simple

Simple Interface To Web Services APIs
Perl
5
star
89

BigConcept

A Big Concept for Next Society. This is just POC.
5
star
90

hono-jsx-dom-with-vite

TypeScript
5
star
91

testing-d1-app-with-types

Testing a D1 Application with Types
TypeScript
5
star
92

cloudflare-workshop-examples

[WIP] Cloudflare Workers + Hono Workshop
TypeScript
5
star
93

Aoi

Web App for sharing, stocking, and finding our plain/markdown texts
CSS
4
star
94

Nopaste

Nopaste application making with Mojolicious.
CSS
4
star
95

Twitter-Ikamusume

イカ娘タイムライン的な何か
Perl
4
star
96

miyagawanize2

about purple thing
Perl
4
star
97

Shodo

Auto-generate documents from HTTP::Request and HTTP::Response
Perl
4
star
98

my-first-chatgpt-plugin

TypeScript
4
star
99

hono-jsx-todos

TypeScript
4
star
100

vercel-zod-openapi

TypeScript
4
star