I’ve been thinking about how we build web services lately. It feels like we’re at a turning point. For years, Node.js and Express have been the default choice, but something new is happening. The tools are getting faster, simpler, and more focused. I want to show you a different way to build APIs that might just change how you think about backend development.
Have you ever waited for a serverless function to start up? That delay is real. What if you could make it almost disappear?
Let’s talk about Bun. It’s not just another JavaScript runtime. It’s built from the ground up to be fast. Really fast. When I first tried it, the startup time caught me off guard. My API was ready before I could blink. For serverless environments, where cold starts can ruin user experience, this matters.
Then there’s Elysia. It’s a web framework designed for Bun. It feels light, almost effortless. You write your routes, and it just works. The type safety is something special. Your editor knows what you’re trying to do before you finish typing.
But an API needs data. That’s where PostgreSQL comes in. It’s reliable, powerful, and when paired correctly, incredibly fast. The secret? Connection pooling. Instead of opening a new database connection for every request, you keep a pool ready. It’s like having a team of database workers waiting for instructions.
Why does this combination work so well? Each piece solves a specific problem. Bun handles the execution speed. Elysia manages the request flow with elegance. PostgreSQL stores the data securely. The connection pool makes sure they talk to each other efficiently.
Here’s how you start. First, install Bun. It’s a single command.
curl -fsSL https://bun.sh/install | bash
Create a new project directory and initialize it.
mkdir my-api && cd my-api
bun init -y
Now add the essential packages. Notice how few dependencies we need.
bun add elysia
bun add pg pg-pool
Let’s set up our database connection. This is where the pooling happens. Create a file called database.ts.
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
min: 2,
idleTimeoutMillis: 30000
});
export const db = {
query: (text: string, params?: any[]) => pool.query(text, params)
};
Simple, right? We create a pool with minimum and maximum connections. The pool manages everything for us. When a request comes in, it grabs an available connection. When the request finishes, the connection goes back to the pool.
Now let’s build our API with Elysia. Create an index.ts file.
import { Elysia } from 'elysia';
import { db } from './database';
const app = new Elysia()
app.get('/users', async () => {
const result = await db.query('SELECT * FROM users LIMIT 100');
return result.rows;
})
app.get('/users/:id', async ({ params }) => {
const result = await db.query('SELECT * FROM users WHERE id = $1', [params.id]);
return result.rows[0] || null;
})
app.post('/users', async ({ body }) => {
const { name, email } = body as any;
const result = await db.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[name, email]
);
return result.rows[0];
})
app.listen(3000);
console.log('Server running on http://localhost:3000');
Look at how clean that is. Each route is a simple function. The database calls are straightforward. But we can do better. What about validation? What about error handling?
Elysia has a beautiful way to handle schemas. Let me show you.
import { Elysia, t } from 'elysia';
const app = new Elysia()
app.post('/users', async ({ body }) => {
// body is automatically validated
const result = await db.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[body.name, body.email]
);
return result.rows[0];
}, {
body: t.Object({
name: t.String({ minLength: 2 }),
email: t.String({ format: 'email' })
})
})
The validation happens before your function even runs. If the data doesn’t match the schema, Elysia returns an error automatically. Your function only receives valid data.
But what happens when something goes wrong? The database might be down. A query might fail. We need to handle these cases.
app.onError(({ code, error }) => {
console.error('Error:', error);
if (code === 'VALIDATION') {
return { error: 'Invalid data provided' };
}
return { error: 'Something went wrong' };
});
This catches errors across your entire application. You can customize the response based on the error type. Users get helpful messages instead of technical details.
Now, let’s think about production. In a serverless environment, your function might be shut down between requests. We need to handle database connections carefully.
let pool: Pool | null = null;
export const getDb = () => {
if (!pool) {
pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 1, // Single connection for serverless
idleTimeoutMillis: 30000
});
}
return pool;
};
Notice the max: 1 setting. In serverless, each function instance gets its own connection. This prevents connection limits from being exceeded. When the function stops, the connection closes automatically.
What about testing? Bun comes with a test runner built in.
import { describe, expect, test } from 'bun:test';
import { app } from './index';
describe('Users API', () => {
test('GET /users returns array', async () => {
const response = await app.handle(
new Request('http://localhost/users')
);
const data = await response.json();
expect(Array.isArray(data)).toBe(true);
});
});
You can run tests with bun test. It’s fast. Really fast. The same speed benefits apply to your test suite.
Deployment is straightforward too. Most platforms support Bun now. Here’s a basic Dockerfile:
FROM oven/bun:latest
WORKDIR /app
COPY package.json .
COPY bun.lockb .
RUN bun install --production
COPY . .
CMD ["bun", "run", "src/index.ts"]
Build it, push it, and you’re done. The image is small because Bun includes everything you need.
But is this actually better than the traditional approach? Let’s compare. With Node.js and Express, you might use several middleware packages. Each adds overhead. With Bun and Elysia, you get more with less. The performance difference is noticeable, especially in serverless environments.
The real question is: what are you building? If it’s a simple API that needs to scale, this stack works beautifully. If you need real-time features or complex websocket handling, you might need additional tools. But for most REST APIs, it’s more than enough.
I’ve built several production services with this combination. The developer experience is excellent. The performance is outstanding. The simplicity is refreshing. You spend less time configuring and more time building.
Remember that connection pool we set up? It’s doing important work behind the scenes. Managing connections efficiently. Preventing timeouts. Handling errors gracefully. All while you focus on your business logic.
What surprised me most was how little code I needed. The frameworks get out of the way. The database interactions feel natural. The whole system feels cohesive, like each part was designed to work with the others.
Try it for your next project. Start small. Build a single endpoint. See how it feels. The setup is quick, and the results might surprise you.
I’d love to hear what you build with this approach. Share your experiences in the comments below. If you found this helpful, please pass it along to someone who might benefit from it. Let’s build better APIs, together.
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