DEV Community

Rohit
Rohit

Posted on

Build a Blazing-Fast, Type-Safe URL Shortener in 5 Minutes with ElysiaJS

ElysiaJS is a TypeScript framework for the Bun runtime that prioritizes developer experience and performance. Today, we're going to use it to build a simple yet powerful URL shortener API.

Prerequisites

You'll need to have Bun installed. If you don't, you can install it with:
curl -fsSL https://bun.sh/install | bash

Step 1: Set Up Your Elysia Project

Let's create a new Elysia project. Bun makes this incredibly fast.

# Create a new Elysia app
bun create elysia my-shortener

# Navigate into the new directory
cd my-shortener

# We'll need a library to generate short, unique IDs
bun add nanoid
Enter fullscreen mode Exit fullscreen mode

This will scaffold a simple src/index.ts file. We'll be working in there.

Step 2: Designing the API

Our URL shortener needs two things:

  • A POST endpoint at /shorten to accept a long URL and return a short link.
  • A GET endpoint at /:id to look up a short ID and redirect to the original long URL. To keep this simple, we'll use an in-memory object as our "database."

Step 3: Building the API (src/index.ts)

Open src/index.ts and replace the contents with the following code. We'll break it down below.

// src/index.ts
import { Elysia, t } from 'elysia';
import { nanoid } from 'nanoid';

// Our "database" to store URL mappings
const urlDatabase: Record<string, string> = {};
const BASE_URL = 'http://localhost:3000';

const app = new Elysia()
  .get('/', () => 'Welcome to the URL Shortener!')

  // 1. Endpoint to create a short URL
  .post(
    '/shorten',
    ({ body, set }) => {
      const shortId = nanoid(7); // Generate a 7-character ID
      urlDatabase[shortId] = body.url;

      set.status = 201; // Created
      return {
        shortUrl: `${BASE_URL}/${shortId}`,
      };
    },
    {
      // 2. Built-in validation!
      body: t.Object({
        url: t.String({
          format: 'uri',
          error: 'Please provide a valid URL.',
        }),
      }),
    }
  )

  // 3. Endpoint to handle redirection
  .get('/:id', ({ params: { id }, set }) => {
    const longUrl = urlDatabase[id];

    if (!longUrl) {
      set.status = 404;
      return { error: 'URL not found' };
    }

    // 4. Perform the redirect
    set.redirect = longUrl;
  })

  .listen(3000);

console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);

// This exports the type of our app for the client
export type App = typeof app;
Enter fullscreen mode Exit fullscreen mode

Code Breakdown
POST /shorten: This endpoint takes a JSON body. We generate a short ID using nanoid, store the mapping in our urlDatabase, and return the newly created short URL.
Validation with t.Object: This is where Elysia shines. We define the expected shape of the body. Elysia will automatically validate incoming requests. If the url is missing or not a valid URI format, it will return a 400 Bad Request error with our custom message—all without any if statements from us!
GET /🆔 This dynamic route captures the short ID from the URL.
Redirection: If the ID exists in our database, we use set.redirect to send a 302 Found redirect response to the client's browser.

Step 4: Run and Test the API

Start the server:
bun run dev

Now, open a new terminal and test it with curl:
curl -X POST -H "Content-Type: application/json" -d '{"url": "https://elysiajs.com/"}' http://localhost:3000/shorten

Step 5. Using the Type-Safe Client
This is the coolest part. Elysia automatically provides a client library called Eden Treaty that gives you end-to-end type safety. You don't need to generate anything.
Create a new file src/client.ts to simulate a frontend or another service consuming our API.

// src/client.ts
import { treaty } from '@elysiajs/eden';
import type { App } from './index'; // Import the type of our app

// Create a typed client
const client = treaty<App>('http://localhost:3000');

async function main() {
  console.log('Creating a short URL...');

  // Notice the full type-safety and autocompletion here!
  // It knows .shorten.post exists and expects a body with a `url` property.
  const { data, error } = await client.shorten.post({
    url: 'https://bun.sh',
  });

  if (error) {
    return console.error('Error:', error.value);
  }

  console.log('Success! Short URL created:', data.shortUrl);
  // Example output: Success! Short URL created: http://localhost:3000/abcdefg
}

main();
Enter fullscreen mode Exit fullscreen mode

When you type client. in a modern editor, you'll get autocompletion for shorten and /. If you type client.shorten.post(), it will tell you that the body is required and must contain a url string. This is type safety that flows directly from your API definition to your client code.
Run the client script:
bun run src/client.ts

Conclusion

In just a few minutes, we built a fully functional, validated, and performant URL shortener. More importantly, we saw how ElysiaJS eliminates the friction between backend and frontend development with its magical, zero-config type-safe client.

Top comments (0)