Junius L
October 1, 2023
5 min read
Junius L
October 1, 2023
5 min read
Today, we embark on an exciting journey to create your very own URL shortener. This tutorial will guide you through the process step-by-step, using Bun, Elysia, Turso, and HTMX.
First things first, let's set up our development environment. We'll be using Bun to create our application.
bun create elysia bunshortlyNext, install the necessary dependencies:
bun add drizzle-orm @libsql/client nanoid better-sqlite3
bun add -D drizzle-kitNavigate to the src directory and create an index.html file. Paste the provided HTML and HTMX code into this file. This code sets up a simple and user-friendly interface for our URL shortener.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.8.2/dist/full.css" rel="stylesheet" type="text/css" />
<script src="https://unpkg.com/htmx.org@1.9.6"
integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
crossorigin="anonymous"></script>
<script src="https://cdn.tailwindcss.com"></script>
<title>Bunshortly</title>
</head>
<body class="flex flex-col items-center">
<div class="navbar bg-base-100">
<a class="text-xl normal-case btn btn-ghost">Bunshortly</a>
</div>
<main>
<section class="flex flex-col gap-5 mt-4 md:mt-32 px-5">
<h1 class="md:text-6xl text-center text-3xl font-extrabold">Shorten Your Loooong Links :)</h1>
<p class="text-base text-center">Bunshortly is an efficient and easy-to-use URL shortening service that
streamlines
your
online
experience.</p>
<div class="relative flex items-center mt-6">
<span class="absolute p-5">
<svg width="26" height="19" viewBox="0 0 26 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.5859 9.5L18.7031 14.3828C16.75 16.3359 13.5859 16.3359 11.6719 14.3828C9.79688 12.5078 9.67969 9.53906 11.3984 7.58594L11.6328 7.35156C11.8281 7.07812 12.2578 7.03906 12.4922 7.27344C12.7656 7.50781 12.8047 7.89844 12.5703 8.17188L12.375 8.40625C11.0859 9.89062 11.1641 12.1172 12.5312 13.4844C14.0156 14.9688 16.3594 14.9688 17.8438 13.4844L22.6875 8.64062C24.1719 7.15625 24.1719 4.8125 22.6875 3.32812C21.2422 1.88281 18.8594 1.88281 17.4141 3.32812L16.5156 4.22656C16.2812 4.46094 15.8906 4.46094 15.6172 4.22656C15.3828 3.95312 15.3828 3.5625 15.6172 3.32812L16.5156 2.42969C18.4688 0.476562 21.6328 0.476562 23.5859 2.42969C25.5391 4.38281 25.5391 7.54688 23.5859 9.5ZM2.375 9.5L7.25781 4.65625C9.21094 2.70312 12.3359 2.70312 14.3281 4.65625C16.1641 6.49219 16.2812 9.46094 14.5625 11.4531L14.3281 11.6875C14.1328 11.9609 13.7422 12 13.4688 11.7656C13.1953 11.5312 13.1562 11.1406 13.3906 10.8672L13.625 10.6328C14.875 9.14844 14.7969 6.92188 13.4297 5.55469C11.9453 4.07031 9.60156 4.07031 8.11719 5.55469L3.27344 10.3984C1.78906 11.8828 1.78906 14.2266 3.27344 15.7109C4.71875 17.1562 7.10156 17.1562 8.54688 15.7109L9.44531 14.8125C9.67969 14.5781 10.0703 14.5781 10.3438 14.8125C10.5781 15.0469 10.5781 15.4766 10.3438 15.7109L9.44531 16.5703C7.49219 18.5234 4.32812 18.5234 2.375 16.5703C0.421875 14.6172 0.421875 11.4531 2.375 9.5Z"
fill="#C9CED6" />
</svg>
</span>
<form class="w-full" hx-post="/shorten" hx-swap="outerHTML" hx-target="#results">
<input name="url" required type="text" placeholder="Enter the link here"
class="input pl-14 h-[58px] rounded-full w-full input-bordered" />
<button
class="btn mt-1 bg-blue-600 font-semibold rounded-[48px] absolute text-white right-1 capitalize">Shorten
now!
</button>
</form>
</div>
</section>
<section id="results"></section>
</main>
<script src="/public/copy.js" defer></script>
</body>
</html>Create a new directory inside src called db. Inside this directory, create index.ts and schema.ts files. These files will handle database setup and schema definition. Follow the provided code snippets to configure your database using Turso and Drizzle.
index.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
const client = createClient({ url: process.env.DB_URL!, authToken: process.env.DB_TOKEN });
export const db = drizzle(client);schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
export const shortener = sqliteTable('shortener', {
id: integer("id", { mode: "number"}).primaryKey({ autoIncrement: true}),
url: text('url').notNull(),
urlId: text('urlId').notNull()
});Create a drizzle.config.ts file in the root directory and configure Drizzle to use Turso as the database driver.
import type { Config} from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
driver: 'turso',
dbCredentials: {
url: process.env.DB_URL!,
authToken: process.env.DB_TOKEN
}
} satisfies ConfigNow run turso command to create the database
turso db create bunshortlyGet the db url
turso db show bunshortlyGet the db token
turso db tokens create bunshortlyRemember to add your database URL and token in the .env file.
Update the index.ts file in the src directory with the following code. This code handles URL shortening requests, redirection, and copying shortened URLs.
import { Elysia, t } from "elysia";
import { html } from '@elysiajs/html'
import { staticPlugin } from '@elysiajs/static'
import { nanoid } from 'nanoid';
import {db} from './db'
import { shortener } from './db/schema';
import { eq } from 'drizzle-orm';
const server = new Elysia().use(html()).use(staticPlugin());
server.get('/', () => {
return new Response(Bun.file('./src/index.html'))
});
server.post('/shorten', async ({body: {url}}) => {
if(!url) {
throw new Error('URL is required')
}
const urlId = nanoid(7);
await db.insert(shortener).values({ url, urlId})
return `<section id="results"
class="flex mt-32 w-full border-slate-200 overflow-hidden border pl-2 rounded-full items-center justify-between">
<p id="url">https://bunshortly.fly.dev/shorten/${urlId}</p>
<div class="bg-slate-200 py-3 px-6 cursor-pointer" hx-on="click: copyText()">
Copy
</div>
</section>`
}, {
body: t.Object({
url: t.String()
})
})
server.get('/shorten/:id', async ({params: {id}, set}) => {
const url = await db.select().from(shortener).where(eq(shortener.urlId, id)).get()
if (!url) {
throw new Error('URL is required')
}
set.redirect = url.url
});
server.listen(3000)
console.log(
`🦊 Elysia is running at ${server.server?.hostname}:${server.server?.port}`
);
Before running the application, push your schema to Turso using the following command:
bunx drizzle-kit push:sqliteNow, you're all set! Test your application locally by running:
bun devVisit http://localhost:3000/ in your browser to see the result.
Feeling confident? Let's deploy your URL shortener on https://fly.io.
fly auth login
fly launch
fly deploy