Why You Should Switch from Express to an Elysia API for Better Performance and Scalability
I started my journey as a full-stack web developer working with React and Express. Over the years, I’ve experimented with various frontend…

I started my journey as a full-stack web developer working with React and Express. Over the years, I’ve experimented with various frontend and backend frameworks, but Express has always remained my go-to choice. It offered many benefits at the time, such as:
- Fast Development: Express made building APIs quick and easy.
- Open Source: It had a vibrant, active community.
- JavaScript Ecosystem: It fit seamlessly into the JS/Node ecosystem.
- Large Community: A huge number of tutorials, plugins, and support options.
But as the years have gone by, I’ve realized that Express is no longer the best choice for some of the modern challenges we face. While Express is stable and still widely used, it no longer leads the pack. It’s still not actively maintained, and it has its own set of limitations:
- JavaScript out-of-the-box: TypeScript is becoming the standard, but Express doesn’t natively support it well.
- Limited Built-in Features: Many features (like validation) require third-party packages.
- Asynchronous Code Handling: Handling async code elegantly can get messy, especially with callbacks and promises
That’s when I decided to look for something better. Let me introduce you to Elysia.js — a modern, ergonomic framework designed for better performance, type safety, and a smoother developer experience.
Enter Elysia: TypeScript and Performance Power
When I first came across Elysia, I’ll admit, I was skeptical. The framework promises a lot:
“TypeScript with End-to-End Type Safety, type integrity, and exceptional developer experience. Supercharged by Bun.”
A framework that promises 21x faster performance than Express and 6x faster than Fastify? It sounded too good to be true. But after experimenting with it on a new project, I was convinced.
A Simple Elysia Example: Similar to Express but with More Power
Let’s start with a simple example of how easy it is to get started with Elysia:
import { Elysia } from 'elysia'
new Elysia()
.get('/', 'Hello Elysia')
.get('/user/:id', ({ params: { id }}) => id)
.post('/form', ({ body }) => body)
.listen(3000)
At first glance, it looks very similar to Express. But don’t be fooled by the simplicity. Elysia offers End-to-End Type Safety, validation, lifecycle hooks, and more — right out of the box.
Built-in Type Safety and Validation
Elysia introduces Elysia.t
, a schema builder for validating types and values at both runtime and compile-time. This feature is incredibly useful, saving hours of manual validation and type generation. Instead of using third-party validation libraries like Yup or Express-validator, Elysia has built-in solutions.
For example, let’s enhance our simple example with validation:
import { Elysia, t } from 'elysia'
new Elysia()
.get('/', 'Hello Elysia')
.get('/user/:id', ({ params: { id } }) => id, {
params: t.Object({
id: t.Numeric()
})
})
.post('/form', ({ body }) => body)
.listen(3000)
Here, we’ve ensured that the id
parameter is numeric, and Elysia will validate this automatically, providing type safety. No more manually checking types or writing custom validators!
Lifecycle Hooks: Flexibility at Your Fingertips
Elysia takes things a step further with lifecycle hooks, allowing you to execute code at various stages of request handling. For example, you can add rate-limiting logic like this:
import { Elysia } from 'elysia'
new Elysia()
.use(rateLimiter)
.onRequest(({ rateLimiter, ip, set, error }) => {
if(rateLimiter.check(ip)) return error(420, 'Enhance your calm')
})
.onBeforeHandle(() => {
console.log('1')
})
.onAfterHandle(() => {
console.log('3')
})
.get('/', () => 'hi', {
beforeHandle() {
console.log('2')
}
})
.listen(3000)
In the example above:
onRequest
: Runs before any endpoint logic, perfect for things like rate-limiting, caching, or logging.onBeforeHandle
andonAfterHandle
: Execute before and after each route handler, giving you full control over the request lifecycle.
These hooks add a level of flexibility that was previously difficult to achieve with Express.
Deriving Context Easily
In Express, adding custom context to requests (like authorization data) often requires manual middleware and type extensions. Elysia simplifies this with its .derive()
method, allowing you to append custom data to the request context:
import { Elysia } from 'elysia'
new Elysia()
.derive(({ headers }) => {
const auth = headers['Authorization']
return { bearer: auth?.startsWith('Bearer ') ? auth.slice(7) : null }
})
.get('/', ({ bearer }) => bearer)
With this, the authorization token is extracted and made available throughout the request lifecycle. This is particularly useful for authentication middleware where you need user info on multiple endpoints.
Global Error Handling Made Simple
Handling errors globally can be tedious in Express, but Elysia provides an elegant solution with the onError
lifecycle hook:
import { Elysia } from 'elysia'
new Elysia()
.onError(({ code, error }) => {
return new Response(error.toString())
})
.get('/', () => {
throw new Error('Server is during maintenance')
})
Here, any errors thrown within the API are automatically caught by the onError
hook, so you don't need to manually set up error-handling middleware for each route.
Elysia Eden: End-to-End Type Safety Across the Stack
Elysia takes TypeScript support even further with Elysia Eden, which provides end-to-end type safety between the server and client. Here’s how it works:
- Backend (server.ts): Export your app’s types.
- Frontend (client.ts): Use the
treaty
client to call the API with full TypeScript support.
// server.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: t.Object({
id: t.Number(),
name: t.String()
})
})
.listen(3000)
export type App = typeof app
// client.ts
import { treaty } from '@elysiajs/eden'
import type { App } from './server'
const client = treaty<App>('localhost:3000')
// Make API calls with TypeScript auto-completion
const { data: index } = await client.index.get()
const { data: id } = await client.id({ id: 1895 }).get()
const { data: nendoroid } = await client.mirror.post({
id: 1895,
name: 'Skadi'
})
This level of integration makes it incredibly easy to keep types consistent across the entire stack, ensuring your API calls are type-safe and reducing errors.
Downsides of Elysia
No framework is perfect, and Elysia has its own limitations:
- Limited Response Type Access in Eden: When using Eden for client-server communication, you can’t access the response types directly, which could lead to a bit of extra manual work when mapping over arrays or passing complex objects to components.
- Uses
fetch
Instead of Axios: Elysia Eden usesfetch
under the hood, which means you lose some features that come with Axios, such as interceptors.
Conclusion: The Future of API Development
After six months of working with Elysia and three months of running a production API, I can confidently say that switching from Express to Elysia was the right choice.
The major advantages of Elysia include:
- Performance Boost: It’s significantly faster than Express and even Fastify.
- End-to-End Type Safety: From frontend to backend, TypeScript auto-completion keeps things consistent and reduces errors.
- Built-in Features: Things like validation, lifecycle hooks, and context derivation are built-in and make development smoother.
- Familiar Syntax: If you’re familiar with Express, transitioning to Elysia is easy.
While there are a couple of trade-offs (like using fetch
and limited response type access), the pros outweigh the cons. If you’re an Express or Fastify developer looking for a modern, high-performance, and TypeScript-friendly alternative, I highly recommend giving Elysia a try!
Try BanKan Board — The Project Management App Made for Developers, by Developers
If you’re tired of complicated project management tools with story points, sprints, and endless processes, BanKan Board is here to simplify your workflow. Built with developers in mind, BanKan Board lets you manage your projects without the clutter.
Key Features:
- No complicated processes: Focus on what matters without the overhead of traditional project management systems.
- Claude AI Assistant: Get smart assistance to streamline your tasks and improve productivity.
- Free to Use: Start using it without any upfront cost.
- Premium Features: Upgrade to unlock advanced functionality tailored to your team’s needs.
Whether you’re building a side project, managing a team, or collaborating on open-source software, BanKan Board is designed to make your life easier. Try it today!