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

bash
bun add elysia

Output: installed elysia

bash
bun create elysia my-app

Output: scaffolds a minimal Elysia + Bun project

For non-Bun runtimes (less common):

bash
npm install elysia

Output: added elysia to dependencies (run on Node via the Node adapter or on Bun-only)

bash
pnpm add elysia

Output: added elysia

The Eden client lives separately:

bash
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.x minor 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

PackageTrade-off
honoWeb-standards-based; runs on Cloudflare Workers, Bun, Node, Deno. Less Bun-specific.
fastifyNode-first, deepest plugin ecosystem. CJS-friendly.
expressClassic Node; minimal type inference, large legacy ecosystem.
koaOlder Node middleware framework.
Bun's built-in Bun.serveZero deps; you re-implement routing, validation, parsing.
nitro (Nuxt's server engine)Universal server with Vue-flavoured ergonomics.

Real-world recipes

Minimal API

typescript
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

typescript
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.

typescript
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

typescript
// 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;
typescript
// 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

typescript
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

typescript
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

typescript
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.ts under a process manager (PM2 with the Bun executable, or systemd). Reverse proxy via Nginx / Caddy.
  • Docker. Use the official oven/bun image. Multi-stage builds with bun install --production keep 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:

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 — .derive and 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 derive once at startup for global resources.
  • Use the ahead-of-time mode. Elysia precompiles validators at startup — observe startup time and warm the dispatch table before traffic hits.
  • @elysiajs/static uses Bun's BunFile for zero-copy file serving — significantly faster than reading file contents into a buffer.

Version migration guide

The big jumps:

FromToKey changes
elysia@0.7elysia@0.8Schema-validation defaults moved from Ajv-style to TypeBox; body validation issues format changed.
elysia@0.xelysia@1.0API stabilisation; t.Object defaults tightened; some hook names normalised. Type inference dramatically improved.
elysia@1.0elysia@1.xAdditive — minor releases add features without breaking handlers. Read release notes for plugin compatibility.

Before (0.x):

typescript
import { Elysia, t } from "elysia";

new Elysia()
  .post("/", ({ body }) => body, { schema: { body: t.Object({ name: t.String() }) } });

After (1.x):

typescript
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:

  1. Bump elysia and all @elysiajs/* plugins together — plugins follow core's major.
  2. Run TypeScript — most breaks surface as type errors.
  3. Audit schema: {} blocks — flatten into top-level route options.
  4. 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/cors defaults 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.body directly inside the handler.
  • JWT verification. @elysiajs/jwt verifies 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

typescript
// 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

typescript
// 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

yaml
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

PackageRole
@elysiajs/edenEnd-to-end-typed RPC client
@elysiajs/swaggerOpenAPI / Swagger UI
@elysiajs/corsCORS middleware
@elysiajs/jwtJWT signing / verification
@elysiajs/staticStatic file serving via BunFile
@elysiajs/cronScheduled tasks
@elysiajs/htmlJSX SSR
drizzle-ormDatabase layer
lucia-authSessions
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