DEV Community

Alex Spinov
Alex Spinov

Posted on

ElysiaJS Has a Free Framework: End-to-End Type Safety at Bun Speed — 6x Faster Than Express

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(),
    }),
  });
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)