Skip to content

从 Hono 到 Elysia

¥From Hono to Elysia

本指南面向 Hono 用户,帮助他们了解 Elysia 与 Hono 之间的差异(包括语法),以及如何通过示例将应用从 Hono 迁移到 Elysia。

¥This guide is for Hono users who want to see a differences from Elysia including syntax, and how to migrate your application from Hono to Elysia by example.

Hono 是基于 Web 标准构建的快速轻量级框架。它与 Deno、Bun、Cloudflare Workers 和 Node.js 等多种运行时广泛兼容。

¥Hono is a fast and lightweight built on Web Standard. It has broad compatibility with multiple runtime like Deno, Bun, Cloudflare Workers, and Node.js.

Elysia 是一个符合人机工程学的 Web 框架。设计符合人机工程学且方便开发者使用,重点关注可靠的类型安全性和性能。

¥Elysia is an ergonomic web framework. Designed to be ergonomic and developer-friendly with a focus on sound type safety and performance.

这两个框架都基于 Web 标准 API 构建,语法略有不同。Hono 提供了与多种运行时的更高兼容性,而 Elysia 则专注于特定的一组运行时。

¥Both frameworks are built on top of Web Standard API, and have slightly different syntax. Hono offers more compatibility with multiple runtimes while Elysia focuses on a specific set of runtimes.

性能

¥Performance

得益于静态代码分析,Elysia 的性能相比 Hono 有显著提升。

¥Elysia has significant performance improvements over Hono thanks to static code analysis.

  1. Elysia
    1,837,294 reqs/s
  2. Hono

    740,451

Measured in requests/second. Result from TechEmpower Benchmark Round 23 (2025-02-24) in JSON serialization

路由

¥Routing

Hono 和 Elysia 的路由语法相似,使用 app.get()app.post() 方法来定义路由,路径参数语法也相似。

¥Hono and Elysia has similar routing syntax, using app.get() and app.post() methods to define routes and similar path parameters syntax.

两者都使用单个 Context 参数来处理请求和响应,并直接返回响应。

¥Both use a single Context parameters to handle request and response, and return a response directly.

ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
    return c.text('Hello World')
})

app.post('/id/:id', (c) => {
	c.status(201)
    return c.text(req.params.id)
})

export default app

Hono 使用辅助函数 c.textc.json 返回响应

ts
import { Elysia } from 'elysia'

const app = new Elysia()
    .get('/', 'Hello World')
    .post(
    	'/id/:id',
     	({ status, params: { id } }) => {
      		return status(201, id)
      	}
    )
    .listen(3000)

Elysia 使用单个 context 并直接返回响应

Hono 使用 c.textc.json 来扭曲响应,而 Elysia 会自动将值映射到响应。

¥While Hono use a c.text, and c.json to warp a response, Elysia map a value to a response automatically.

样式指南略有不同,Elysia 建议使用方法链和对象解构。

¥There is a slight different in style guide, Elysia recommends usage of method chaining and object destructuring.

Hono 的端口分配取决于运行时和适配器,而 Elysia 使用单一的 listen 方法启动服务器。

¥Hono port allocation is depends on runtime, and adapter while Elysia use a single listen method to start the server.

处理程序

¥Handler

Hono 使用函数手动解析查询、标头和正文,而 Elysia 自动解析属性。

¥Hono use a function to parse query, header, and body manually while Elysia automatically parse properties.

ts
import { Hono } from 'hono'

const app = new Hono()

app.post('/user', async (c) => {
	const limit = c.req.query('limit')
    const { name } = await c.body()
    const auth = c.req.header('authorization')

    return c.json({ limit, name, auth })
})

Hono 会自动解析请求体,但不适用于查询和请求头。

ts
import { Elysia } from 'elysia'

const app = new Elysia()
	.post('/user', (ctx) => {
	    const limit = ctx.query.limit
	    const name = ctx.body.name
	    const auth = ctx.headers.authorization

	    return { limit, name, auth }
	})

Elysia 使用静态代码分析来分析要解析的内容

Elysia 使用静态代码分析来确定要解析的内容,并且只解析所需的属性。

¥Elysia use static code analysis to determine what to parse, and only parse the required properties.

这对于性能和类型安全非常有用。

¥This is useful for performance and type safety.

子路由

¥Subrouter

两者都可以继承另一个实例作为路由,但 Elysia 将每个实例都视为可用作子路由的组件。

¥Both can inherits another instance as a router, but Elysia treat every instances as a component which can be used as a subrouter.

ts
import { Hono } from 'hono'

const subRouter = new Hono()

subRouter.get('/user', (c) => {
	return c.text('Hello User')
})

const app = new Hono()

app.route('/api', subRouter)

Hono require 用于分隔子路由的前缀

¥a prefix to separate the subrouter

ts
import { Elysia } from 'elysia'

const subRouter = new Elysia({ prefix: '/api' })
	.get('/user', 'Hello User')

const app = new Elysia()
	.use(subRouter)

Elysia 使用可选的前缀构造函数来定义一个

Hono 需要前缀来分隔子路由,而 Elysia 无需前缀。

¥While Hono requires a prefix to separate the subrouter, Elysia doesn't require a prefix to separate the subrouter.

验证

¥Validation

Hono 通过外部包支持各种验证器,而 Elysia 内置了使用 TypeBox 的验证功能,并支持开箱即用的标准 Schema,让你无需额外依赖库即可使用你常用的库,例如 Zod、Valibot、ArkType、Effect Schema 等。Elysia 还提供与 OpenAPI 的无缝集成,并在后台进行类型推断。

¥While Hono supports for various validator via external package, Elysia has a built-in validation using TypeBox, and support for Standard Schema out of the box allowing you to use your favorite library like Zod, Valibot, ArkType, Effect Schema and so on without additional library. Elysia also offers seamless integration with OpenAPI, and type inference behind the scene.

ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

app.patch(
	'/user/:id',
	zValidator(
		'param',
		z.object({
			id: z.coerce.number()
		})
	),
	zValidator(
		'json',
		z.object({
			name: z.string()
		})
	),
	(c) => {
		return c.json({
			params: c.req.param(),
			body: c.req.json()
		})
	}
)

崩坏 3 使用基于管道

ts
import { 
Elysia
,
t
} from 'elysia'
const
app
= new
Elysia
()
.
patch
('/user/:id', ({
params
,
body
}) => ({
params
,
body
}), {
params
:
t
.
Object
({
id
:
t
.
Number
()
}),
body
:
t
.
Object
({
name
:
t
.
String
()
}) })
ts
import { 
Elysia
} from 'elysia'
import {
z
} from 'zod'
const
app
= new
Elysia
()
.
patch
('/user/:id', ({
params
,
body
}) => ({
params
,
body
}), {
params
:
z
.
object
({
id
:
z
.
number
()
}),
body
:
z
.
object
({
name
:
z
.
string
()
}) })
ts
import { 
Elysia
} from 'elysia'
import * as
v
from 'zod'
const
app
= new
Elysia
()
.
patch
('/user/:id', ({
params
,
body
}) => ({
params
,
body
}), {
params
:
v
.
object
({
id
:
v
.
number
()
}),
body
:
v
.
object
({
name
:
v
.
string
()
}) })

Elysia 使用 TypeBox 进行验证,并自动强制类型转换。同时支持使用相同语法的各种验证库,例如 Zod、Valibot。

两者都自动提供从模式到上下文的类型推断。

¥Both offers type inference from schema to context automatically.

文件上传

¥File upload

Hono 和 Elysia 都使用 Web 标准 API 来处理文件上传,但 Elysia 内置了声明式文件验证功能,使用 file-type 来验证 mimetype。

¥Both Hono, and Elysia use Web Standard API to handle file upload, but Elysia has a built-in declarative support for file validation using file-type to validate mimetype.

ts
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

import { fileTypeFromBlob } from 'file-type'

const app = new Hono()

app.post(
	'/upload',
	zValidator(
		'form',
		z.object({
			file: z.instanceof(File)
		})
	),
	async (c) => {
		const body = await c.req.parseBody()

		const type = await fileTypeFromBlob(body.image as File)
		if (!type || !type.mime.startsWith('image/')) {
			c.status(422)
			return c.text('File is not a valid image')
		}

		return new Response(body.image)
	}
)

Hono 需要一个单独的 file-type 库来验证 mimetype

ts
import { Elysia, t } from 'elysia'

const app = new Elysia()
	.post('/upload', ({ body }) => body.file, {
		body: t.Object({
			file: t.File({
				type: 'image'
			})
		})
	})

Elysia 处理文件,并以声明方式进行 mimetype 验证

由于 Web 标准 API 不验证 mimetype,信任客户端提供的 content-type 存在安全风险,因此 Hono 需要外部库,而 Elysia 使用 file-type 自动验证 mimetype。

¥As Web Standard API doesn't validate mimetype, it is a security risk to trust content-type provided by the client so external library is required for Hono, while Elysia use file-type to validate mimetype automatically.

中间件

¥Middleware

Hono 中间件使用类似于 Express 的基于队列的单线程,而 Elysia 使用基于事件的生命周期提供更精细的控制。

¥Hono middleware use a single queue-based order similar to Express while Elysia give you a more granular control using an event-based lifecycle.

Elysia 的生命周期事件如下图所示。

¥Elysia's Life Cycle event can be illustrated as the following.

Elysia Life Cycle Graph

点击图片放大

虽然 Hono 按顺序具有单个请求管道流,但 Elysia 可以拦截请求管道中的每个事件。

¥While Hono has a single flow for request pipeline in order, Elysia can intercept each event in a request pipeline.

ts
import { Hono } from 'hono'

const app = new Hono()

// Global middleware
app.use(async (c, next) => {
	console.log(`${c.method} ${c.url}`)

	await next()
})

app.get(
	'/protected',
	// Route-specific middleware
	async (c, next) => {
	  	const token = c.headers.authorization

	  	if (!token) {
			c.status(401)
	   		return c.text('Unauthorized')
		}

	  	await next()
	},
	(req, res) => {
  		res.send('Protected route')
	}
)

Hono 使用基于队列的单向执行中间件

ts
import { Elysia } from 'elysia'

const app = new Elysia()
	// Global middleware
	.onRequest(({ method, path }) => {
		console.log(`${method} ${path}`)
	})
	// Route-specific middleware
	.get('/protected', () => 'protected', {
		beforeHandle({ status, headers }) {
  			if (!headers.authorizaton)
     			return status(401)
		}
	})

Elysia 在请求管道中的每个点使用特定的事件拦截器

虽然 Hono 有一个 next 函数来调用下一个中间件,但 Elysia 没有。

¥While Hono has a next function to call the next middleware, Elysia does not has one.

听起来类型安全

¥Sounds type safety

Elysia 的设计注重类型安全。

¥Elysia is designed to be sounds type safety.

例如,你可以使用 deriveresolve 以类型安全的方式自定义上下文,而 Hono 则不行。

¥For example, you can customize context in a type safe manner using derive and resolve while Hono doesn't.

ts
import { 
Hono
} from 'hono'
import {
createMiddleware
} from 'hono/factory'
const
app
= new
Hono
()
const
getVersion
=
createMiddleware
(async (
c
,
next
) => {
c
.
set
('version', 2)
await
next
()
})
app
.
use
(
getVersion
)
app
.
get
('/version',
getVersion
, (
c
) => {
return
c
.
text
(
c
.
get
('version') + '')
No overload matches this call. Overload 1 of 2, '(key: never): unknown', gave the following error. Argument of type '"version"' is not assignable to parameter of type 'never'. Overload 2 of 2, '(key: never): never', gave the following error. Argument of type '"version"' is not assignable to parameter of type 'never'.
}) const
authenticate
=
createMiddleware
(async (
c
,
next
) => {
const
token
=
c
.
req
.
header
('authorization')
if (!
token
) {
c
.
status
(401)
return
c
.
text
('Unauthorized')
}
c
.
set
('token',
token
.
split
(' ')[1])
await
next
()
})
app
.
post
('/user',
authenticate
, async (
c
) => {
c
.
get
('version')
No overload matches this call. Overload 1 of 2, '(key: never): unknown', gave the following error. Argument of type '"version"' is not assignable to parameter of type 'never'. Overload 2 of 2, '(key: never): never', gave the following error. Argument of type '"version"' is not assignable to parameter of type 'never'.
return
c
.
text
(c.get('token'))
No overload matches this call. Overload 1 of 2, '(key: never): unknown', gave the following error. Argument of type '"token"' is not assignable to parameter of type 'never'. Overload 2 of 2, '(key: never): never', gave the following error. Argument of type '"token"' is not assignable to parameter of type 'never'.
No overload matches this call. Overload 1 of 2, '(text: string, status?: ContentfulStatusCode | undefined, headers?: HeaderRecord | undefined): Response & TypedResponse<string, ContentfulStatusCode, "text">', gave the following error. Argument of type 'unknown' is not assignable to parameter of type 'string'. Overload 2 of 2, '(text: string, init?: ResponseOrInit<ContentfulStatusCode> | undefined): Response & TypedResponse<string, ContentfulStatusCode, "text">', gave the following error. Argument of type 'unknown' is not assignable to parameter of type 'string'.
})

Hono 使用中间件来扩展上下文,但类型不安全

ts
import { 
Elysia
} from 'elysia'
const
app
= new
Elysia
()
.
decorate
('version', 2)
.
get
('/version', ({
version
}) =>
version
)
.
resolve
(({
status
,
headers
: {
authorization
} }) => {
if(!
authorization
?.
startsWith
('Bearer '))
return
status
(401)
return {
token
:
authorization
.
split
(' ')[1]
} }) .
get
('/token', ({
token
,
version
}) => {
version
return
token
})

Elysia 在请求管道中的每个点使用特定的事件拦截器

虽然 Hono 可以使用 declare module 扩展 ContextVariableMap 接口,但它是全局可用的,并且不具备良好的类型安全性,并且不能保证该属性在所有请求处理程序中都可用。

¥While Hono can, use declare module to extend the ContextVariableMap interface, it is globally available and doesn't have sounds type safety, and doesn't garantee that the property is available in all request handlers.

ts
declare module 'hono' {
  	interface ContextVariableMap {
    	version: number
  		token: string
  	}
}

这是上述 Hono 示例运行所必需的,它不提供声音类型安全。

中间件参数

¥Middleware parameter

Hono 使用回调函数定义可复用的特定路由中间件,而 Elysia 使用 macro 函数定义自定义钩子。

¥Hono use a callback function to define a reusable route-specific middleware, while Elysia use macro to define a custom hook.

ts
import { 
Hono
} from 'hono'
import {
createMiddleware
} from 'hono/factory'
const
app
= new
Hono
()
const
role
= (
role
: 'user' | 'admin') =>
createMiddleware
(async (
c
,
next
) => {
const
user
=
findUser
(
c
.
req
.
header
('Authorization'))
if(
user
.
role
!==
role
) {
c
.
status
(401)
return
c
.
text
('Unauthorized')
}
c
.
set
('user',
user
)
await
next
()
})
app
.
get
('/user/:id',
role
('admin'), (
c
) => {
return
c
.
json
(
c
.
get
('user'))
No overload matches this call. Overload 1 of 2, '(key: never): unknown', gave the following error. Argument of type '"user"' is not assignable to parameter of type 'never'. Overload 2 of 2, '(key: never): never', gave the following error. Argument of type '"user"' is not assignable to parameter of type 'never'.
})

Hono 使用回调返回 createMiddleware 来创建可重用的中间件,但类型不安全

ts
import { 
Elysia
} from 'elysia'
const
app
= new
Elysia
()
.
macro
({
role
: (
role
: 'user' | 'admin') => ({
resolve
({
status
,
headers
: {
authorization
} }) {
const
user
=
findUser
(
authorization
)
if(
user
.
role
!==
role
)
return
status
(401)
return {
user
} } }) }) .
get
('/token', ({
user
}) =>
user
, {
role
: 'admin'
})

Elysia 使用宏将自定义参数传递给自定义中间件

错误处理

¥Error handling

Hono 提供了一个适用于所有路由的 onError 函数,而 Elysia 则提供了更精细的错误处理控制。

¥Hono provide a onError function which apply to all routes while Elysia provides a more granular control over error handling.

ts
import { Hono } from 'hono'

const app = new Hono()

class CustomError extends Error {
	constructor(message: string) {
		super(message)
		this.name = 'CustomError'
	}
}

// global error handler
app.onError((error, c) => {
	if(error instanceof CustomError) {
		c.status(500)

		return c.json({
			message: 'Something went wrong!',
			error
		})
	}
})

// route-specific error handler
app.get('/error', (req, res) => {
	throw new CustomError('oh uh')
})

Hono 使用 onError 函数处理错误,所有路由使用同一个错误处理程序

ts
import { 
Elysia
} from 'elysia'
class
CustomError
extends
Error
{
// Optional: custom HTTP status code
status
= 500
constructor(
message
: string) {
super(
message
)
this.
name
= 'CustomError'
} // Optional: what should be sent to the client
toResponse
() {
return {
message
: "If you're seeing this, our dev forgot to handle this error",
error
: this
} } } const
app
= new
Elysia
()
// Optional: register custom error class .
error
({
CUSTOM
:
CustomError
,
}) // Global error handler .
onError
(({
error
,
code
}) => {
if(
code
=== 'CUSTOM')
return {
message
: 'Something went wrong!',
error
} }) .
get
('/error', () => {
throw new
CustomError
('oh uh')
}, { // Optional: route specific error handler
error
({
error
}) {
return {
message
: 'Only for this route!',
error
} } })

Elysia 提供更精细的错误处理控制和作用域机制

Hono 提供类似中间件的错误处理,而 Elysia 提供:

¥While Hono offers error handling using middleware-like, Elysia provide:

  1. 全局和路由特定的错误处理程序
  2. 用于映射 HTTP 状态的简写,以及用于将错误映射到响应的 toResponse
  3. 为每个错误提供自定义错误代码

错误代码对于日志记录和调试非常有用,并且在区分扩展同一类的不同错误类型时非常重要。

¥The error code is useful for logging and debugging, and is important when differentiating between different error types extending the same class.

封装

¥Encapsulation

Hono 封装了插件的副作用,而 Elysia 通过显式的作用域机制和代码顺序让你可以控制插件的副作用。

¥Hono encapsulate plugin side-effect, while Elysia give you a control over side-effect of a plugin via explicit scoping mechanism, and order-of-code.

ts
import { Hono } from 'hono'

const subRouter = new Hono()

subRouter.get('/user', (c) => {
	return c.text('Hello User')
})

const app = new Hono()

app.route('/api', subRouter)

Hono 封装了插件的副作用

ts
import { Elysia } from 'elysia'

const subRouter = new Elysia()
	.onBeforeHandle(({ status, headers: { authorization } }) => {
		if(!authorization?.startsWith('Bearer '))
			return status(401)
   	})

const app = new Elysia()
    .get('/', 'Hello World')
    .use(subRouter)
    // doesn't have side-effect from subRouter
    .get('/side-effect', () => 'hi')

除非明确说明,否则 Elysia 会封装插件的副作用。

两者都具有插件的封装机制,以防止副作用。

¥Both has a encapsulate mechanism of a plugin to prevent side-effect.

然而,Elysia 可以通过声明作用域来明确指定哪个插件应该具有副作用,而 Fastify 始终会对其进行封装。

¥However, Elysia can explicitly stated which plugin should have side-effect by declaring a scoped while Fastify always encapsulate it.

ts
import { Elysia } from 'elysia'

const subRouter = new Elysia()
	.onBeforeHandle(({ status, headers: { authorization } }) => {
		if(!authorization?.startsWith('Bearer '))
			return status(401)
   	})
	// Scoped to parent instance but not beyond
	.as('scoped') 

const app = new Elysia()
    .get('/', 'Hello World')
    .use(subRouter)
    // now have side-effect from subRouter
    .get('/side-effect', () => 'hi')

Elysia 提供三种作用域机制:

¥Elysia offers 3 type of scoping mechanism:

  1. local - 仅应用于当前实例,无副作用(默认)
  2. scoped - 作用域副作用作用于父实例,但不会超出父实例的范围
  3. global - 影响所有实例

由于 Hono 不提供作用域机制,我们需要:

¥As Hono doesn't offers a scoping mechanism, we need to either:

  1. 为每个钩子创建一个函数并手动添加它们
  2. 使用高阶函数,并将其应用于需要该效果的实例

然而,如果处理不当,这可能会导致重复的副作用。

¥However, this can caused a duplicated side-effect if not handled carefully.

ts
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'

const middleware = createMiddleware(async (c, next) => {
	console.log('called')

	await next()
})

const app = new Hono()
const subRouter = new Hono()

app.use(middleware)
app.get('/main', (c) => c.text('Hello from main!'))

subRouter.use(middleware)

// This would log twice
subRouter.get('/sub', (c) => c.text('Hello from sub router!'))

app.route('/sub', subRouter)

export default app

在这种情况下,Elysia 提供了插件去重机制,以防止重复的副作用。

¥In this scenario, Elysia offers a plugin deduplication mechanism to prevent duplicated side-effect.

ts
import { Elysia } from 'elysia'

const subRouter = new Elysia({ name: 'subRouter' }) 
	.onBeforeHandle(({ status, headers: { authorization } }) => {
		if(!authorization?.startsWith('Bearer '))
			return status(401)
   	})
	.as('scoped')

const app = new Elysia()
	.get('/', 'Hello World')
	.use(subRouter)
	.use(subRouter) 
	.use(subRouter) 
	.use(subRouter) 
	// side-effect only called once
	.get('/side-effect', () => 'hi')

通过使用唯一的 name,Elysia 只需应用一次插件,不会造成重复的副作用。

¥By using a unique name, Elysia will apply the plugin only once, and will not cause duplicated side-effect.

Hono 在 hono/cookie 下内置了 cookie 实用函数,而 Elysia 使用基于信号的方法来处理 cookie。

¥Hono has a built-in cookie utility functions under hono/cookie, while Elysia use a signal-based approach to handle cookies.

ts
import { Hono } from 'hono'
import { getSignedCookie, setSignedCookie } from 'hono/cookie'

const app = new Hono()

app.get('/', async (c) => {
	const name = await getSignedCookie(c, 'secret', 'name')

	await setSignedCookie(
		c,
		'name',
		'value',
		'secret',
		{
			maxAge: 1000,
		}
	)
})

Hono 使用实用函数来处理 Cookie

ts
import { Elysia } from 'elysia'

const app = new Elysia({
	cookie: {
		secret: 'secret'
	}
})
	.get('/', ({ cookie: { name } }) => {
		// signature verification is handle automatically
		name.value

		// cookie signature is signed automatically
		name.value = 'value'
		name.maxAge = 1000 * 60 * 60 * 24
	})

Elysia 使用基于信号的方法来处理 Cookie

OpenAPI

Hono 需要额外的精力来描述规范,而 Elysia 可以将规范无缝集成到架构中。

¥Hono require additional effort to describe the specification, while Elysia seamless integrate the specification into the schema.

ts
import { Hono } from 'hono'
import { describeRoute, openAPISpecs } from 'hono-openapi'
import { resolver, validator as zodValidator } from 'hono-openapi/zod'
import { swaggerUI } from '@hono/swagger-ui'

import { z } from '@hono/zod-openapi'

const app = new Hono()

const model = z.array(
	z.object({
		name: z.string().openapi({
			description: 'first name only'
		}),
		age: z.number()
	})
)

const detail = await resolver(model).builder()

console.log(detail)

app.post(
	'/',
	zodValidator('json', model),
	describeRoute({
		validateResponse: true,
		summary: 'Create user',
		requestBody: {
			content: {
				'application/json': { schema: detail.schema }
			}
		},
		responses: {
			201: {
				description: 'User created',
				content: {
					'application/json': { schema: resolver(model) }
				}
			}
		}
	}),
	(c) => {
		c.status(201)
		return c.json(c.req.valid('json'))
	}
)

app.get('/ui', swaggerUI({ url: '/doc' }))

app.get(
	'/doc',
	openAPISpecs(app, {
		documentation: {
			info: {
				title: 'Hono API',
				version: '1.0.0',
				description: 'Greeting API'
			},
			components: {
				...detail.components
			}
		}
	})
)

export default app

Hono 需要额外的精力来描述规范。

ts
import { 
Elysia
,
t
} from 'elysia'
import {
openapi
} from '@elysiajs/openapi'
const
app
= new
Elysia
()
.
use
(
openapi
())
.
model
({
user
:
t
.
Array
(
t
.
Object
({
name
:
t
.
String
(),
age
:
t
.
Number
()
}) ) }) .
post
('/users', ({
body
}) =>
body
, {
body
: 'user',
response
: {
201: 'user' },
detail
: {
summary
: 'Create user'
} })

Elysia 将规范无缝集成到模式中

Hono 有单独的函数来描述路由规范和验证,并且需要一些工作才能正确设置。

¥Hono has separate function to describe route specification, validation, and require some effort to setup properly.

Elysia 使用你提供的模式用于生成 OpenAPI 规范,并验证请求/响应,并从单一数据源自动推断类型。

¥Elysia use schema you provide to generate the OpenAPI specification, and validate the request/response, and infer type automatically all from a single source of truth.

Elysia 还将 model 中注册的 Schema 附加到 OpenAPI 规范中,允许你在 Swagger 或 Scalar UI 的专用部分中引用该模型,同时 Hono 会将 Schema 内联到路由中。

¥Elysia also appends the schema registered in model to the OpenAPI spec, allowing you to reference the model in a dedicated section in Swagger or Scalar UI while Hono inline the schema to the route.

测试

¥Testing

两者都基于 Web 标准 API 构建,因此可以与任何测试库一起使用。

¥Both is built on top of Web Standard API allowing it be used with any testing library.

ts
import { Hono } from 'hono'
import { describe, it, expect } from 'vitest'

const app = new Hono()
	.get('/', (c) => c.text('Hello World'))

describe('GET /', () => {
	it('should return Hello World', async () => {
		const res = await app.request('/')

		expect(res.status).toBe(200)
		expect(await res.text()).toBe('Hello World')
	})
})

Hono 内置了 request 方法来执行请求

ts
import { Elysia } from 'elysia'
import { describe, it, expect } from 'vitest'

const app = new Elysia()
	.get('/', 'Hello World')

describe('GET /', () => {
	it('should return Hello World', async () => {
		const res = await app.handle(
			new Request('http://localhost')
		)

		expect(res.status).toBe(200)
		expect(await res.text()).toBe('Hello World')
	})
})

Elysia 使用 Web 标准 API 处理请求和响应

或者,Elysia 还提供了一个名为 Eden 的辅助库,用于端到端类型安全,允许我们使用自动补全和完全类型安全进行测试。

¥Alternatively, Elysia also offers a helper library called Eden for End-to-end type safety, allowing us to test with auto-completion, and full type safety.

ts
import { 
Elysia
} from 'elysia'
import {
treaty
} from '@elysiajs/eden'
import {
describe
,
expect
,
it
} from 'bun:test'
const
app
= new
Elysia
().
get
('/hello', 'Hello World')
const
api
=
treaty
(
app
)
describe
('GET /', () => {
it
('should return Hello World', async () => {
const {
data
,
error
,
status
} = await
api
.
hello
.
get
()
expect
(
status
).
toBe
(200)
expect
(
data
).
toBe
('Hello World')
}) })

端到端类型安全

¥End-to-end type safety

两者都提供端到端的类型安全,但 Hono 似乎不提供基于状态码的类型安全错误处理。

¥Both offers end-to-end type safety, however Hono doesn't seems to offers type-safe error handling based on status code.

ts
import { 
Hono
} from 'hono'
import {
hc
} from 'hono/client'
import {
z
} from 'zod'
import {
zValidator
} from '@hono/zod-validator'
const
app
= new
Hono
()
.
post
(
'/mirror',
zValidator
(
'json',
z
.
object
({
message
:
z
.
string
()
}) ), (
c
) =>
c
.
json
(
c
.
req
.
valid
('json'))
) const
client
=
hc
<typeof
app
>('/')
const
response
= await
client
.
mirror
.
$post
({
json
: {
message
: 'Hello, world!'
} }) const
data
= await
response
.
json
()
console
.
log
(
data
)

Hono 使用 hc 函数执行请求,并提供端到端的类型安全

ts
import { 
Elysia
,
t
} from 'elysia'
import {
treaty
} from '@elysiajs/eden'
const
app
= new
Elysia
()
.
post
('/mirror', ({
body
}) =>
body
, {
body
:
t
.
Object
({
message
:
t
.
String
()
}) }) const
api
=
treaty
(
app
)
const {
data
,
error
} = await
api
.
mirror
.
post
({
message
: 'Hello World'
}) if(
error
)
throw
error
console
.
log
(
data
)

Elysia 使用 treaty 运行请求,并提供端到端类型安全

虽然两者都提供端到端的类型安全,但 Elysia 提供了基于状态码的更类型安全的错误处理,而 Hono 则没有。

¥While both offers end-to-end type safety, Elysia offers a more type-safe error handling based on status code while Hono doesn't.

使用每个框架相同用途的代码来测量类型推断速度,Elysia 的类型检查速度比 Hono 快 2.3 倍。

¥Using the same purpose code for each framework to measure type inference speed, Elysia is 2.3x faster than Hono for type checking.

Elysia eden type inference performance

Elysia 需要 536 毫秒来推断 Elysia 和 Eden(点击放大)

Hono HC type inference performance

Hono 需要 1.27 秒才能推断出 Hono 和 HC 的错误(已中止)(点击放大)

1.27 秒并不反映推断的整个时长,而是从开始到因错误 "类型实例化过深深度甚至可能无限。" 而中止的时长,当模式过大时会发生这种情况。

¥The 1.27 seconds doesn't reflect the entire duration of the inference, but a duration from start to aborted by error "Type instantiation is excessively deep and possibly infinite." which happens when there are too large schema.

Hono HC code showing excessively deep error

Hono HC 显示过深错误

这是由大型模式引起的,Hono 不支持超过 100 条包含复杂主体的路由和响应验证,而 Elysia 没有这个问题。

¥This is caused by the large schema, and Hono doesn't support over a 100 routes with complex body, and response validation while Elysia doesn't have this issue.

Elysia Eden code showing type inference without error

Elysia Eden 代码展示了无错误的类型推断

Elysia 具有更快的类型推断性能,并且至少在 2,000 条包含复杂主体和响应验证的路由中没有 "类型实例化过深深度甚至可能无限。"。

¥Elysia has a faster type inference performance, and doesn't have "Type instantiation is excessively deep and possibly infinite." at least up to 2,000 routes with complex body, and response validation.

如果端到端类型安全对你来说很重要,那么 Elysia 是正确的选择。

¥If end-to-end type safety is important for you then Elysia is the right choice.


两者都是基于 Web 标准 API 构建的下一代 Web 框架,但略有不同。

¥Both are the next generation web framework built on top of Web Standard API with slight differences.

Elysia 的设计符合人机工程学且对开发者友好,注重声音类型安全,并且性能优于 Hono。

¥Elysia is designed to be ergonomic and developer-friendly with a focus on sounds type safety, and has beter performance than Hono.

虽然 Hono 与多种运行时(尤其是 Cloudflare Workers)具有广泛的兼容性,并且拥有更大的用户群。

¥While Hono offers a broad compatibility with multiple runtimes, especially with Cloudflare Workers, and a larger user base.

或者,如果你来自其他框架,你可以查看:

¥Alternatively, if you are coming from a different framework, you can check out: