cheat sheet
elysia
Package-level reference for Elysia — end-to-end type safety, plugins, Eden RPC client, schema validation, and Bun-first deployment.
elysia
What it is
elysia is a Bun-native HTTP framework with end-to-end TypeScript inference. Route handlers are typed inputs to typed outputs, and the framework derives a complete type contract that the companion @elysiajs/eden client consumes to give callers compile-time-checked RPC. It leans on Bun's fast runtime (sub-millisecond startup, native HTTP server) for performance and pairs well with Bun's bundler, test runner, and SQLite.
Reach for Elysia when your deployment target is Bun, end-to-end type safety is a hard requirement, and Hono's portability to non-Bun runtimes isn't worth giving up Bun-specific perks. Reach for Hono if you need to run unchanged on Cloudflare Workers / Vercel Edge; reach for Fastify for the mature Node-ecosystem plugin catalogue.
Install
bun add elysia
Output: installed elysia
bun create elysia my-app
Output: scaffolds a minimal Elysia + Bun project
For non-Bun runtimes (less common):
npm install elysia
Output: added elysia to dependencies (run on Node via the Node adapter or on Bun-only)
pnpm add elysia
Output: added elysia
The Eden client lives separately:
bun add @elysiajs/eden
Output: installed @elysiajs/eden
Versioning & Node support
Current line is elysia@1.x — the 1.0 release in 2024 was the API-stability milestone.
- Bun 1.0+ is the primary target. Node support exists but is secondary.
- ESM-only.
- TypeScript types bundled — heavy use of TypeScript's inference engine; older TS versions are unsupported.
- Semver observed;
1.xminor releases ship features additively.
Package metadata
- Maintainer: Sapphi Aria (saltyaom)
- Project home: github.com/elysiajs/elysia
- Docs: elysiajs.com
- npm: npmjs.com/package/elysia
- License: MIT
- First released: 2022
- Downloads: hundreds of thousands weekly — the de facto Bun framework.
Peer dependencies & extras
Elysia uses TypeBox (@sinclair/typebox) for its runtime schema model — bundled as a direct dep, no install needed.
Common ecosystem packages:
@elysiajs/eden— end-to-end-typed RPC client@elysiajs/swagger— OpenAPI docs@elysiajs/cors— CORS@elysiajs/jwt— JWT helpers@elysiajs/cookie— typed cookies (deprecated/folded into core in recent versions)@elysiajs/static— static file serving@elysiajs/bearer— bearer-token middleware@elysiajs/html— JSX SSR@elysiajs/cron— scheduled tasks
Alternatives
| Package | Trade-off |
|---|---|
hono | Web-standards-based; runs on Cloudflare Workers, Bun, Node, Deno. Less Bun-specific. |
fastify | Node-first, deepest plugin ecosystem. CJS-friendly. |
express | Classic Node; minimal type inference, large legacy ecosystem. |
koa | Older Node middleware framework. |
Bun's built-in Bun.serve | Zero deps; you re-implement routing, validation, parsing. |
nitro (Nuxt's server engine) | Universal server with Vue-flavoured ergonomics. |
Real-world recipes
Minimal API
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello!")
.get("/json", () => ({ ok: true }))
.listen(3000);
console.log(`Listening on ${app.server?.port}`);
Output: bun run src/index.ts boots the server; GET / returns "Hello!", GET /json returns the object.
Typed route with schema
import { Elysia, t } from "elysia";
const app = new Elysia()
.post(
"/posts",
({ body }) => ({ created: body }),
{
body: t.Object({ title: t.String({ minLength: 1 }) }),
},
)
.listen(3000);
Output: invalid JSON returns 422 with the validation issue; valid JSON reaches the handler with body typed as { title: string }.
Plugin (logger)
A plugin is just a function returning an Elysia instance — types flow through.
import { Elysia } from "elysia";
const logger = new Elysia().onRequest(({ request }) => {
console.log(request.method, request.url);
});
const app = new Elysia()
.use(logger)
.get("/", () => "Hi");
Output: every request logs method + URL before reaching the handler.
Eden RPC client
// server.ts
import { Elysia, t } from "elysia";
export const app = new Elysia()
.get("/users/:id", ({ params }) => ({ id: params.id, name: "Alice" }), {
params: t.Object({ id: 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>("http://localhost:3000");
const { data } = await client.users({ id: "42" }).get();
console.log(data?.name);
Output: the call is fully typed — data.name is string | undefined because the server returns that shape; misspellings fail at compile time.
Streaming response
import { Elysia } from "elysia";
new Elysia()
.get("/stream", async function* () {
yield "first\n";
await Bun.sleep(500);
yield "second\n";
})
.listen(3000);
Output: the async generator's yields stream to the client; works because Bun's HTTP server natively supports ReadableStream.
CORS
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
new Elysia()
.use(cors({ origin: "https://example.com" }))
.get("/api/health", () => ({ ok: true }))
.listen(3000);
Output: preflight OPTIONS is handled automatically; production origins controlled via the origin option.
Swagger docs
import { Elysia } from "elysia";
import { swagger } from "@elysiajs/swagger";
new Elysia()
.use(swagger())
.get("/posts", () => [], { detail: { tags: ["posts"] } })
.listen(3000);
Output: OpenAPI spec is generated from the route schemas; Swagger UI mounted at /swagger.
Production deployment
Elysia is Bun-first; deployment usually means a Bun runtime.
- Bun on bare-metal / VPS.
bun run src/index.tsunder a process manager (PM2 with the Bun executable, or systemd). Reverse proxy via Nginx / Caddy. - Docker. Use the official
oven/bunimage. Multi-stage builds withbun install --productionkeep image size down. - Railway, Fly.io, Render. All support Bun. Set the start command to
bun run src/index.ts. - AWS Lambda. No official Bun runtime; a Lambda Layer with Bun + a custom bootstrap exists, or use a Node adapter and run on the Node runtime.
- Cloudflare Workers. Not currently a target — Elysia depends on Node-compatible APIs Bun provides but Workers does not. Use Hono on Workers.
- Vercel. Bun is supported in Build Output API; community adapters exist.
A typical Dockerfile:
FROM oven/bun:1 AS build
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build src/index.ts --outfile dist/server.js --target bun
FROM oven/bun:1
WORKDIR /app
COPY --from=build /app/dist /app/dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "dist/server.js"]
Output: small image (~80 MB), starts in <100 ms.
Performance tuning
- Bun's HTTP server is already fast. Most of Elysia's perf advantage comes from compile-time route resolution.
- Static-analysis routes. Routes whose handlers Elysia can analyse statically run via a generated dispatch function —
.deriveand dynamic logic that block analysis disable this. Stick to declarative patterns where possible. - Compile schemas once. TypeBox validators are cached per route; avoid re-creating schemas inside handlers.
- Avoid per-request object allocations. Use Elysia's
deriveonce at startup for global resources. - Use the
ahead-of-timemode. Elysia precompiles validators at startup — observe startup time and warm the dispatch table before traffic hits. @elysiajs/staticuses Bun'sBunFilefor zero-copy file serving — significantly faster than reading file contents into a buffer.
Version migration guide
The big jumps:
| From | To | Key changes |
|---|---|---|
elysia@0.7 | elysia@0.8 | Schema-validation defaults moved from Ajv-style to TypeBox; body validation issues format changed. |
elysia@0.x | elysia@1.0 | API stabilisation; t.Object defaults tightened; some hook names normalised. Type inference dramatically improved. |
elysia@1.0 | elysia@1.x | Additive — minor releases add features without breaking handlers. Read release notes for plugin compatibility. |
Before (0.x):
import { Elysia, t } from "elysia";
new Elysia()
.post("/", ({ body }) => body, { schema: { body: t.Object({ name: t.String() }) } });
After (1.x):
import { Elysia, t } from "elysia";
new Elysia()
.post("/", ({ body }) => body, { body: t.Object({ name: t.String() }) });
Output: the schema: wrapper was dropped — validation fields are at the route-options top level.
Migration checklist:
- Bump
elysiaand all@elysiajs/*plugins together — plugins follow core's major. - Run TypeScript — most breaks surface as type errors.
- Audit
schema: {}blocks — flatten into top-level route options. - Test the Eden client against the new server — RPC type shape may have changed if your routes use newer schema features.
Security considerations
- CORS defaults.
@elysiajs/corsdefaults to*— restrict in production with explicit origins. - CSRF. No built-in CSRF middleware in core; for cookie-auth endpoints add a token check or use header-based auth.
- Schema validation order. Validators run before the handler — but only on the bodies/params you declare. Don't read
request.bodydirectly inside the handler. - JWT verification.
@elysiajs/jwtverifies signature but does not enforce audience/issuer by default. Configure both. - Prototype pollution. Avoid
Object.assign(req.body, …)patterns — schema validation prevents most of these. - Information leakage in errors. Production should map validation errors to user-facing messages, not echo internal schema details.
Testing & CI integration
Unit test with Bun test
// app.test.ts
import { describe, it, expect } from "bun:test";
import { app } from "./app";
describe("GET /", () => {
it("returns hello", async () => {
const res = await app.handle(new Request("http://localhost/"));
expect(res.status).toBe(200);
expect(await res.text()).toBe("Hello!");
});
});
Output: bun test runs in milliseconds; app.handle simulates a request without binding a port.
Type-test the Eden client
// type-only test
import { treaty } from "@elysiajs/eden";
import type { App } from "./server";
const client = treaty<App>("http://localhost:3000");
// uncomment to type-fail: client.users({ id: 42 }) — id must be string
Output: tsc --noEmit surfaces RPC shape mismatches at build time.
CI pipeline
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun test
- run: bun build src/index.ts --outfile dist/server.js --target bun
Ecosystem integrations
| Package | Role |
|---|---|
@elysiajs/eden | End-to-end-typed RPC client |
@elysiajs/swagger | OpenAPI / Swagger UI |
@elysiajs/cors | CORS middleware |
@elysiajs/jwt | JWT signing / verification |
@elysiajs/static | Static file serving via BunFile |
@elysiajs/cron | Scheduled tasks |
@elysiajs/html | JSX SSR |
drizzle-orm | Database layer |
lucia-auth | Sessions |
bun:sqlite (Bun built-in) | Embedded SQLite |
Troubleshooting common errors
Cannot find module 'elysia' after bun add — confirm package.json lists it; some IDEs need a TypeScript server restart after install.
Type instantiation is excessively deep — Elysia's inference hit TypeScript's budget. Split the app into smaller composed plugins; each plugin tightens types locally.
Validation errors return 422 but you expected 400 — Elysia uses 422 for schema failures by default. Override with onError middleware to remap.
Eden client shows any for response — server route doesn't have a return type or has a response schema mismatch. Add a response: option to the route or ensure return types are inferable.
Routes run on Node but not on Bun (or vice versa) — some Bun-specific APIs (Bun.file, Bun.serve) don't exist on Node; use the Node adapter or feature-detect.
@elysiajs/swagger 404 — UI mounts at /swagger by default; check the option for path:.
Hot reload breaks state — Bun's --hot flag re-executes the entry module; module-level singletons reset. Move state into a separate file imported via a stable path.
When NOT to use this
- Deploying to Cloudflare Workers / Vercel Edge / Deno Deploy. Use Hono.
- Need Node 16 support. Elysia targets modern runtimes only.
- Existing Express-middleware-heavy codebase. Elysia uses its own middleware signature; Express compat shims exist but are niche.
- Large team unfamiliar with Bun-specific tooling. The skill premium can be a real cost in collaboration; pick what your team already runs.
See also
- JavaScript: bun — runtime fundamentals
- Concept: http — request/response semantics