iTranslated by AI
A Technical Comparison of Lightweight and Type-Safe APIs (tRPC / Hono / Elysia)
Nice to meet you, I'm _mino!
In this article, I have summarized a comparison of libraries and frameworks for implementing lightweight and type-safe APIs based on Next.js.
Since this is based on a comparative study I conducted for a recent project, I hope you find it helpful! 💪
🚀 Target Frameworks and Libraries
tRPC
A library that leverages TypeScript type inference to share types between the server and the client. It allows you to call APIs as type-safely as if you were using an SDK.
Adoption Examples
・Netflix (Video streaming / Production use is explicitly stated in their official FAQ)
・Cal.com (Open source / Scheduling)
Hono
A fast, lightweight web framework that uses only Web Standard APIs. The same code runs on multiple runtimes such as Cloudflare Workers, Deno, Bun, and Node.js.
Adoption Examples
・Cloudflare (Adopted for internal APIs such as D1, Workers Logs, KV, and Queues)
・Mastra (Adopted as the server infrastructure)
Elysia.js
A fast, lightweight backend framework optimized for Bun. It provides integrated request validation, type inference, OpenAPI document generation, and end-to-end type safety with a schema as a single source of truth.
Adoption Examples
・Introduced as a guide in the official Bun documentation
・Jetfuel (Adopted as the backend for Server Driven UI)
It still feels like it's in the developing stages, but it's a framework with high expectations.
This video covering Elysia.js was quite interesting 👇
📚 Trends and Popularity
GitHub Stars

tRPC is the oldest of the three libraries. While its growth has stabilized recently, it still feels like it is widely used in various projects, cases, and OSS.
Hono started to spread around 2023 and seemed to gather quite a bit of popularity in 2025. Due to its lightness, performance, and great DX, it seems likely to grow the most in the future.
Elysia is the newest framework, built specifically for Bun. As Bun has become more stable, Elysia has been attracting a lot of attention. Based on articles and other sources, it seems to focus heavily on performance, making it one of the tools I'm personally watching this year.
tRPC, Hono, and Elysia are all on an upward trend, and given their good compatibility with frontends like Next.js, they are likely to continue gaining popularity.
📊 Feature and Characteristic Comparison
I compared them from the perspectives of "Performance," "Runtime," "Validation and Type Safety," and "Built-in Features."
Performance
| Framework | Characteristics |
|---|---|
| Hono | Lightweight and fast on Cloudflare Workers, Deno, and Bun. Since Node.js involves conversion to Web Standard APIs, it is slower than on other environments. |
| Elysia | Designed specifically for Bun and is the fastest in Bun environments. |
| tRPC | Since it is not a standalone HTTP server but runs on top of Express, Fastify, Hono, etc., its performance depends on the host framework. |
Reference Values
This table shows the average number of requests measured in three patterns: simple text return, URL parameter processing, and JSON processing.
| Framework | Runtime | Average (req/s) |
|---|---|---|
| Elysia | Bun | 397,259 |
| Hono | Bun | 253,646 |
| Hono | Deno | 201,548 |
| Fastify | Node | 142,695 |
| Hono | Node | 129,234 |
| Express | Node | 25,079 |
*req/s = number of requests processed per second
*Since these benchmarks were conducted by the author of Elysia, conditions may be favorable to Elysia.
Related Links
- Hono Benchmarks (Official)
- Elysia At Glance (Official)
- How Hono and Elysia Are Challenging Express and Fastify
Runtime Support
All of these frameworks support basic runtime environments (Node.js, Bun, Deno, Cloudflare Workers, etc.), with Hono notably supporting the widest range of runtimes.
Regarding the Bun environment, Elysia is optimized as it was specifically designed for Bun.
Validation and Type Safety
tRPC
By using @trpc/client, server-side type definitions can be automatically utilized on the client. It also features extensive integration with TanStack Query.
Zod is commonly used for validation, ensuring type safety at both runtime and compile time by defining input schemas.
Hono
By using RPC mode, type-safe API calls are possible via hono/client. This enables end-to-end type safety while maintaining RESTful URLs.
Validation supports multiple libraries such as Zod and Valibot. Since it supports Standard Schema, you can choose your preferred library.
Elysia
By installing and setting up "Eden," you can achieve end-to-end type safety with the client.
For validation, it comes standard with its own schema builder "Elysia.t" (based on TypeBox), and libraries that support Standard Schema like Zod and Valibot are also available.
Built-in Features
Each framework provides its own unique built-in features and plugins, but here we compare them based on the major features commonly used in practice.
| Feature | tRPC (Next.js) | Hono | Elysia |
|---|---|---|---|
| OpenAPI / Swagger | △ Separate package | △ Separate package | ✅ |
| WebSocket / SSE | ✅ | ✅ | ✅ |
| Middleware | ✅ | ✅ | ✅ |
| JWT Authentication | △ Manual / NextAuth | ✅ | ✅ |
| Bearer Authentication | △ Manual implementation | ✅ | ✅ |
| Cookie | ✅ | ✅ | ✅ |
| GraphQL | × | △ Separate package | ✅ |
| Logger | △ Manual implementation | ✅ | △ Separate package |
- ✅ Officially supported / △ Separate package or manual / × Not supported
🏖️ Implementation Code Comparison
As a premise, the sample code is summarized assuming it will be hosted as a backend on Next.js.
Writing with tRPC
1. Creating a tRPC Instance
Create and export the base for the router and procedures.
import { initTRPC } from "@trpc/server";
import superjson from "superjson";
const t = initTRPC.create({ transformer: superjson });
// For router definition
export const router = t.router;
// Base for public APIs
export const publicProcedure = t.procedure;
2. Consolidating the Router
Combine each feature's router and export the AppRouter type. This type is used for client-side type inference.
import { router } from "./init";
import { productsRouter } from "./routers/products";
export const appRouter = router({
products: productsRouter,
});
export type AppRouter = typeof appRouter;
3. Connecting with Next.js
Connect tRPC to the Next.js Route Handler using fetchRequestHandler.
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "@/server/trpc/router";
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
});
export { handler as GET, handler as POST };
4. Defining the API
Define the logic using .query() or .mutation(), with validation via .input() based on publicProcedure.
import { router, publicProcedure } from "../init";
export const productsRouter = router({
// Define APIs like list, byId, etc.
});
5. Calling from the Client
By passing the AppRouter type, endpoint names and inputs/outputs become type-safe.
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "@/server/trpc/router";
export const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: "/api/trpc", transformer: superjson })],
});
// Call example
const products = await trpc.products.list.query({ limit: 10 });
Writing with Hono
1. App Creation and Route Registration
Register routes with app.route(). Export the type from routes for type inference.
import { Hono } from "hono";
import { productsRoute } from "./routes/products";
const app = new Hono().basePath("/api");
const routes = app.route("/products", productsRoute);
export { app };
export type AppType = typeof routes;
2. Connecting with Next.js
Connect to the Next.js Route Handler using handle from hono/vercel.
import { handle } from "hono/vercel";
import { app } from "@/server/app";
export const GET = handle(app);
export const POST = handle(app);
3. Defining the API
Define .get() and .post() using an Express-like chain API.
import { Hono } from "hono";
export const productsRoute = new Hono()
// Define APIs like .get(), .post(), etc.
[Supplement] Validation Definition
Use vValidator() from @hono/valibot-validator as middleware. Since query parameters are all strings in the HTTP specification, numeric conversion is performed manually.
import { Hono } from "hono";
import { vValidator } from "@hono/valibot-validator";
import * as v from "valibot";
new Hono()
.get("/", vValidator("query", v.object({ limit: v.optional(v.string()) })), (c) => {
const { limit } = c.req.valid("query");
return c.json({ limit: limit ? Number(limit) : 20 });
})
.post("/", vValidator("json", v.object({ name: v.string() })), (c) => {
const { name } = c.req.valid("json");
return c.json({ message: `Hello ${name}!` });
})
📖 Official Documentation: https://hono.dev/docs/guides/validation
4. Calling from the Client
Since it is based on the Fetch API, the response needs to be parsed with res.json().
import { hc } from "hono/client";
import type { AppType } from "@/server/app";
export const api = hc<AppType>("/").api;
// Call example
const res = await api.products.$get({ query: { limit: "10" } });
const products = await res.json();
Writing with Elysia
1. App Creation and Route Registration
Register routes like plugins with .use().
import { Elysia } from "elysia";
import { productsRoute } from "./routes/products";
const app = new Elysia({ prefix: "/api" })
.use(productsRoute);
export { app };
export type App = typeof app;
2. Connecting with Next.js
Directly export app.fetch.
import { app } from "@/server/app";
export const GET = app.fetch;
export const POST = app.fetch;
3. Defining the API
Define .get() and .post() using a chain API.
import { Elysia } from "elysia";
export const productsRoute = new Elysia({ prefix: "/products" })
// Define APIs like .get(), .post(), etc.
[Supplement] Validation Definition
Define validation as the third argument. Automatic string-to-number conversion is also possible with t.Numeric().
import { Elysia, t } from "elysia";
new Elysia()
.get("/id/:id", ({ params }) => params.id, {
params: t.Object({ id: t.Numeric() }),
})
.post("/user", ({ body }) => `Hello ${body.name}!`, {
body: t.Object({ name: t.String(), age: t.Number() }),
})
📖 Official Documentation: https://elysiajs.com/essential/validation
4. Calling from the Client
Responses are returned in { data, error } format, making error handling straightforward.
import { treaty } from "@elysiajs/eden";
import type { App } from "@/server/app";
export const api = treaty<App>("/").api;
// Call example
const { data: products, error } = await api.products.get({ query: { limit: 10 } });
📌 Personal Impressions
While tRPC, Hono, and Elysia have differences in performance and built-in features, their coding style and functionality feel quite similar.
tRPC is the oldest among these and has a mature ecosystem. As a result, it feels easy to adopt and has a low learning curve. However, I personally don't quite like the fact that endpoints end up in a format like /api/trpc/users.list.
Hono is lightweight and supports multiple runtimes, offering attractive flexibility that works in any environment. Since it has high stability and a rich ecosystem, I think it is the most solid choice at this point.
Elysia may have lower name recognition, but it excels in performance and DX, and has excellent compatibility with Bun. It is a framework I want to keep watching while expecting future growth.
📗 Extra: Notable Libraries and Frameworks
These are other libraries and frameworks I'm interested in as trends and want to keep an eye on.
oRPC
An RPC library that adds OpenAPI support to tRPC. It allows you to adhere to OpenAPI standards while maintaining end-to-end type safety.
This video was easy to understand 👇
ts-rest
A REST API framework with a "Contract-First" approach. You define the API contract first and share it between the client and server.
👀 Wrap Up
Thank you for reading to the end! ☺️
I hope this article helps you in your development, even if just a little!
Discussion