tRPC gives you type safety but locks you into its ecosystem. GraphQL gives you flexibility but adds schema complexity. REST gives you simplicity but zero type inference.
What if your REST framework gave you tRPC-level type safety with Express-level simplicity, running at Bun speed?
That's ElysiaJS.
Quick Start
bun create elysia app
cd app && bun run dev
End-to-End Type Safety
// server.ts
import { Elysia, t } from "elysia";
const app = new Elysia()
.post("/users", ({ body }) => {
// body is fully typed: { name: string, email: string }
return { id: crypto.randomUUID(), ...body };
}, {
body: t.Object({
name: t.String(),
email: t.String({ format: "email" }),
}),
})
.get("/users/:id", ({ params: { id } }) => {
return { id, name: "Aleksej", email: "dev@example.com" };
})
.listen(3000);
export type App = typeof app;
// client.ts — automatic type inference
import { treaty } from "@elysiajs/eden";
import type { App } from "./server";
const api = treaty<App>("localhost:3000");
const { data } = await api.users.post({
name: "Aleksej",
email: "dev@example.com",
});
// data is typed: { id: string, name: string, email: string }
const { data: user } = await api.users({ id: "123" }).get();
// user is typed based on the route handler return type
Validation Built In
const app = new Elysia()
.post("/products", ({ body }) => {
return db.products.create(body);
}, {
body: t.Object({
name: t.String({ minLength: 1, maxLength: 100 }),
price: t.Number({ minimum: 0 }),
category: t.Union([
t.Literal("electronics"),
t.Literal("clothing"),
t.Literal("food"),
]),
tags: t.Array(t.String(), { maxItems: 10 }),
}),
response: t.Object({
id: t.String(),
name: t.String(),
price: t.Number(),
}),
});
Invalid requests get automatic 422 responses with detailed error messages. No manual validation code.
Plugin System
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import { jwt } from "@elysiajs/jwt";
const app = new Elysia()
.use(cors())
.use(swagger()) // Auto-generates OpenAPI docs at /swagger
.use(jwt({ name: "jwt", secret: process.env.JWT_SECRET! }))
.post("/login", async ({ jwt, body }) => {
const token = await jwt.sign({ userId: body.userId });
return { token };
})
.get("/protected", async ({ jwt, headers }) => {
const payload = await jwt.verify(headers.authorization);
if (!payload) throw new Error("Unauthorized");
return { userId: payload.userId };
})
.listen(3000);
Performance
| Framework | Requests/sec | Latency (p99) |
|---|---|---|
| ElysiaJS (Bun) | 180,000 | 1.2ms |
| Hono (Bun) | 150,000 | 1.5ms |
| Fastify (Node) | 65,000 | 3.1ms |
| Express (Node) | 30,000 | 8.2ms |
When to Choose ElysiaJS
Choose ElysiaJS when:
- You use Bun and want maximum performance
- End-to-end type safety matters (like tRPC but for REST)
- You want auto-generated OpenAPI/Swagger docs
- Validation should be declarative, not manual
Skip ElysiaJS when:
- You can't use Bun (ElysiaJS is Bun-first)
- You need to deploy to Cloudflare Workers or Deno
- Your team prefers explicit over magic (ElysiaJS is opinionated)
The Bottom Line
ElysiaJS combines the DX of tRPC (type safety), the simplicity of Express (routing), and the speed of Bun (native runtime). If you're building on Bun, it's hard to beat.
Start here: elysiajs.com
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)