I have a gripe with Next.js API routes.
Every endpoint needs its own file. A GET /users? That’s app/api/users/route.ts. Need POST /users? Same file, but now you’re juggling methods. Add authentication endpoints, health checks, webhooks — suddenly you’ve got route files scattered across your project with no central view of what your API actually looks like.
I’ve been exploring Elysia.js as a potential solution, and I wanted to share what I’ve found. Fair warning: I haven’t shipped this to production yet. But the developer experience has me genuinely excited.
What’s Elysia?
Elysia is a TypeScript-first API framework built for Bun. It’s not venture-backed or corporate — it’s community-driven and open source. What caught my attention is that it can run inside a Next.js app, giving you a cleaner way to organize API logic without abandoning your existing setup.
The Pattern That Clicked for Me
Instead of scattering route files everywhere, you create a single catch-all route and let Elysia handle the routing:
// app/api/[...slugs]/route.ts
import { Elysia, t } from "elysia";
const app = new Elysia({ prefix: "/api" })
.get("/health", () => "OK")
.get("/user/:id", ({ params }) => getUserById(params.id))
.post("/user", ({ body }) => createUser(body), {
body: t.Object({ name: t.String(), email: t.String() }),
});
export const GET = app.handle;
export const POST = app.handle;
Your entire API definition lives in one place. You can see every route at a glance, and the chaining syntax keeps things readable.
This works because Elysia follows the WinterTC standard — a common API specification across JavaScript runtimes. That compliance means you can deploy to Vercel, Cloudflare Workers, or Deno Deploy without changing your code.
The Type Safety That Sold Me
Here’s the part that genuinely surprised me. Elysia has a companion library called Eden that gives you end-to-end type safety between your API and your frontend.
On the server, you export your app’s type:
export type App = typeof app;
On the client, you import that type:
import { treaty } from "@elysiajs/eden";
import type { App } from "./server";
const api = treaty<App>("localhost:3000");
const { data } = await api.user.post({
name: "Jane",
email: "[email protected]"
});
Now TypeScript knows exactly what your API expects. Change a field name on the backend? Your frontend lights up with errors immediately. No code generation step, no separate schema files to maintain.
If you’ve worked with tRPC, this concept will feel familiar. The difference is that Elysia routes are standard HTTP endpoints — you can still hit them with curl or test them in Postman. You’re not locked into a TypeScript-only ecosystem.
Validation Without Extra Work
Elysia uses TypeBox for schema validation. You define the shape of your request body inline:
app.post(
"/user",
({ body }) => createUser(body),
{
body: t.Object({
name: t.String(),
email: t.String(),
}),
}
);
That schema does double duty: it validates incoming requests at runtime and generates your TypeScript types. One definition, two benefits, zero drift.
Worth a Look
Elysia gives you real performance, end-to-end type safety, a clean developer experience, and deploys anywhere. If you’re building APIs in Next.js and feeling the pain of scattered route files, give it a shot.