js

Build Fast, Type-Safe APIs with Bun, Elysia.js, and Drizzle ORM

Learn how to create high-performance, type-safe APIs using Bun, Elysia.js, and Drizzle ORM with clean architecture and instant feedback.

Build Fast, Type-Safe APIs with Bun, Elysia.js, and Drizzle ORM

I’ve been thinking about building APIs lately. Not just any APIs, but ones that feel fast, safe, and easy to work with. You know the feeling when your code just clicks together, and you get helpful hints as you type? That’s what I wanted. So, I started looking at the tools that could make this happen. I found a powerful combination: Bun as the engine, Elysia.js as the framework, and Drizzle to talk to the database. Let me show you how they work together to create something really solid.

Why does this matter? Well, have you ever been frustrated by slow server startup or confusing type errors between your database and your API? This stack aims to fix that. It’s built with performance and clarity from the ground up.

First, we need to set up our project. Make sure you have Bun installed. It’s a single command. Then, we can create our project and install what we need.

bun init -y
bun add elysia @elysiajs/swagger drizzle-orm postgres
bun add -d drizzle-kit @types/pg

The structure is clean. We’ll have folders for our database schemas, our API routes, and our business logic. This keeps things organized as our project grows.

Now, let’s connect to a database. We’ll use Drizzle ORM because it’s simple and stays out of our way. We define our tables using a clear, SQL-like syntax right in TypeScript.

Here’s how you might define a user table.

// src/db/schema/users.ts
import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  username: varchar('username', { length: 100 }).notNull(),
  createdAt: timestamp('created_at').defaultNow(),
});

See how that looks? It’s very readable. Drizzle uses this to give us full TypeScript types for every query we write. No more guessing what shape your data is in.

But what about the API itself? This is where Elysia.js shines. It’s a web framework built for Bun. It’s incredibly fast and has type safety woven into its core. You define a route, and it automatically understands the types of the request and response.

Let’s create a simple server and a route to fetch users.

// src/index.ts
import { Elysia, t } from 'elysia';
import { db } from './config/database';
import { users } from './db/schema';

const app = new Elysia()
  .get('/', () => 'API is running')
  .get('/users', async () => {
    const allUsers = await db.select().from(users);
    return allUsers;
  })
  .listen(3000);

console.log(`Server is ready at http://localhost:${app.server?.port}`);

Run this with bun run src/index.ts. Your server starts almost instantly. That’s Bun’s speed. Now, if you visit localhost:3000/users, you’ll get a list of users. The response will be a JSON array, and its TypeScript type matches our users table definition exactly.

This is good, but we can add validation. What if someone tries to create a user with a bad email? Elysia makes this easy with schemas.

Let’s build a POST route to create a new user. We’ll define exactly what we expect.

// Add to our app
.post('/users', async ({ body }) => {
    const newUser = await db.insert(users).values(body).returning();
    return newUser[0];
  },
  {
    body: t.Object({
      email: t.String({ format: 'email' }),
      username: t.String({ minLength: 3 }),
    })
  }
)

Now, the route will automatically check the incoming request. If the email isn’t valid or the username is too short, Elysia sends a clear error back before our code even runs. It’s a great layer of protection.

How do we handle more complex operations, like finding a user’s posts? This is where relations in Drizzle are helpful. We define how our tables connect.

// In src/db/schema/posts.ts
import { relations } from 'drizzle-orm';
import { users } from './users';

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));

This tells Drizzle that a post has one author, linked by the authorId field. We can then write a query that joins this data efficiently.

.get('/posts/:id', async ({ params }) => {
    const postWithAuthor = await db.query.posts.findFirst({
      where: (posts, { eq }) => eq(posts.id, params.id),
      with: {
        author: true,
      },
    });
    return postWithAuthor;
  }
)

The with clause is the magic. It fetches the related author data in the same query. The return type? It’s fully known to TypeScript, so you get autocomplete for postWithAuthor.author.email.

What about errors? A good API handles failures gracefully. We can add a simple error handler to our Elysia app.

// Add an onError hook
app.onError(({ code, error, set }) => {
  if (code === 'NOT_FOUND') {
    set.status = 404;
    return { error: 'Resource not found' };
  }
  // Log unexpected errors
  console.error(error);
  set.status = 500;
  return { error: 'Something went wrong' };
});

This catches errors and sends back consistent responses. It makes debugging easier and gives users a better experience.

Another fantastic feature is automatic API documentation. Remember the @elysiajs/swagger package we installed? We can plug it in with one line.

import { swagger } from '@elysiajs/swagger';

const app = new Elysia()
  .use(swagger()) // Add this
  .get('/', () => 'API is running')
  // ... all other routes

Now, if you go to localhost:3000/swagger, you’ll see a full, interactive documentation page for all your routes. It’s generated from your code and your validation schemas. This saves so much time and keeps your docs in sync.

As your project gets bigger, you’ll want to split routes into separate files. Elysia handles this beautifully with its plugin system.

// src/routes/users.ts
import { Elysia, t } from 'elysia';
import { userService } from '../services/user.service';

export const userRoutes = new Elysia({ prefix: '/users' })
  .get('/', async () => {
    return userService.getAll();
  })
  .get('/:id', async ({ params }) => {
    return userService.getById(params.id);
  });

// Then in src/index.ts
import { userRoutes } from './routes/users';
app.use(userRoutes);

This keeps your main file clean and organizes logic by feature. The prefix option means all routes in this file automatically start with /users.

Finally, let’s talk about going live. Deploying a Bun app is straightforward. You can build it and run it just like any other Node.js-compatible app. Many platforms support it directly. The key is to set your environment variables, like your database connection string, securely.

Building with these tools feels different. The feedback loop is tight. The types guide you. The performance is noticeable. It removes a lot of the friction in API development.

I hope this walkthrough gives you a clear path to build your own type-safe, high-performance APIs. What part of this stack are you most excited to try? Have you run into challenges with other tools that this approach might solve?

If you found this guide helpful, please share it with other developers who might be looking for a modern API stack. I’d love to hear about what you build—leave a comment below with your experiences or questions


As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!


📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!


Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Keywords: bun,elysiajs,drizzle orm,typescript api,web development



Similar Posts
Blog Image
Complete Guide to Integrating Next.js with Prisma for Type-Safe Full-Stack Development

Learn to integrate Next.js with Prisma for type-safe full-stack development. Build modern web apps with seamless database operations and React frontend.

Blog Image
Building a Distributed Rate Limiting System with Redis and Node.js: Complete Implementation Guide

Learn to build scalable distributed rate limiting with Redis and Node.js. Implement Token Bucket, Sliding Window algorithms, Express middleware, and production deployment strategies.

Blog Image
Create Real-Time Analytics Dashboard with Node.js, ClickHouse, and WebSockets

Learn to build a scalable real-time analytics dashboard using Node.js, ClickHouse, and WebSockets. Master data streaming, visualization, and performance optimization for high-volume analytics.

Blog Image
How to Integrate Stripe Payments into Your Express.js App Securely

Learn how to securely accept payments in your Express.js app using Stripe, with step-by-step code examples and best practices.

Blog Image
Complete Guide to Integrating Svelte with Firebase: Build Real-Time Apps Fast in 2024

Learn how to integrate Svelte with Firebase for powerful real-time web apps. Step-by-step guide covering authentication, database setup, and reactive UI updates.

Blog Image
Complete Guide to Next.js Prisma Integration: Build Type-Safe Full-Stack Applications with Modern ORM

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web applications. Discover seamless database operations and performance optimization. Start building today!