Skip to content

从 Express 到 Elysia

¥From Express to Elysia

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

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

Express 是一个流行的 Node.js Web 框架,广泛用于构建 Web 应用和 API。它以简洁灵活而闻名。

¥Express is a popular web framework for Node.js, and widely used for building web applications and APIs. It is known for its simplicity and flexibility.

Elysia 是一个符合人机工程学的 Web 框架,适用于 Bun、Node.js 和支持 Web 标准 API 的运行时。设计符合人机工程学且方便开发者使用,重点关注可靠的类型安全性和性能。

¥Elysia is an ergonomic web framework for Bun, Node.js, and runtimes that support Web Standard API. Designed to be ergonomic and developer-friendly with a focus on sound type safety and performance.

性能

¥Performance

得益于原生的 Bun 实现和静态代码分析,Elysia 的性能相比 Express 有显著提升。

¥Elysia has significant performance improvements over Express thanks to native Bun implementation, and static code analysis.

  1. Elysia
    2,454,631 reqs/s
  2. Express

    113,117

Measured in requests/second. Result from TechEmpower Benchmark Round 22 (2023-10-17) in PlainText

路由

¥Routing

Express 和 Elysia 具有相似的路由语法,使用 app.get()app.post() 方法定义路由,并且路径参数语法也相似。

¥Express and Elysia have similar routing syntax, using app.get() and app.post() methods to define routes and similar path parameter syntax.

ts
import express from 'express'

const app = express()

app.get('/', (req, res) => {
    res.send('Hello World')
})

app.post('/id/:id', (req, res) => {
    res.status(201).send(req.params.id)
})

app.listen(3000)

Express 使用 reqres 作为请求和响应对象

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 并直接返回响应

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

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

如果你不需要使用上下文,Elysia 还支持在响应中使用内联值。

¥Elysia also supports an inline value for the response if you don't need to use the context.

处理程序

¥Handler

两者都具有类似的属性,用于访问输入参数,例如 headersqueryparamsbody

¥Both has a simliar property for accessing input parameters like headers, query, params, and body.

ts
import express from 'express'

const app = express()

app.use(express.json())

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

    res.json({ limit, name, auth })
})

Express 需要 express.json() 中间件来解析 JSON 主体。

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 默认解析 JSON、URL 编码数据和表单数据

子路由

¥Subrouter

Express 使用专用的 express.Router() 来声明子路由,而 Elysia 将每个实例视为可即插即用的组件。

¥Express use a dedicated express.Router() for declaring a sub router while Elysia treats every instances as a component that can be plug and play together.

ts
import express from 'express'

const subRouter = express.Router()

subRouter.get('/user', (req, res) => {
	res.send('Hello User')
})

const app = express()

app.use('/api', subRouter)

Express 使用 express.Router() 创建子路由

ts
import { Elysia } from 'elysia'

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

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

Elysia 将每个实例视为一个组件

验证

¥Validation

Elysia 内置了使用 TypeBox 进行请求验证的支持,确保类型安全,并开箱即用地支持标准 Schema,允许你使用你喜欢的库,例如 Zod、Valibot、ArkType、Effect Schema 等等。虽然 Express 没有提供内置验证,并且需要根据每个验证库进行手动类型声明。

¥Elysia has a built-in support for request validation using TypeBox sounds type safety, 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. While Express doesn't offers a built-in validation, and require a manual type declaration based on each validation library.

ts
import express from 'express'
import { z } from 'zod'

const app = express()

app.use(express.json())

const paramSchema = z.object({
	id: z.coerce.number()
})

const bodySchema = z.object({
	name: z.string()
})

app.patch('/user/:id', (req, res) => {
	const params = paramSchema.safeParse(req.params)
	if (!params.success)
		return res.status(422).json(result.error)

	const body = bodySchema.safeParse(req.body)
	if (!body.success)
		return res.status(422).json(result.error)

	res.json({
		params: params.id.data,
		body: body.data
	})
})

Express 需要外部验证库(例如 zodjoi)来验证请求主体

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 'valibot'
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。

文件上传

¥File upload

Express 使用外部库 multer 来处理文件上传,而 Elysia 内置了对文件和表单数据的支持,并使用声明式 API 进行 mimetype 验证。

¥Express use an external library multer to handle file upload, while Elysia has a built-in support for file and formdata, mimetype valiation using declarative API.

ts
import express from 'express'
import multer from 'multer'
import { fileTypeFromFile } from 'file-type'
import path from 'path'

const app = express()
const upload = multer({ dest: 'uploads/' })

app.post('/upload', upload.single('image'), async (req, res) => {
	const file = req.file

	if (!file)
		return res
			.status(422)
			.send('No file uploaded')

	const type = await fileTypeFromFile(file.path)
	if (!type || !type.mime.startsWith('image/'))
		return res
			.status(422)
			.send('File is not a valid image')

	const filePath = path.resolve(file.path)
	res.sendFile(filePath)
})

Express 需要 express.json() 中间件来解析 JSON 主体。

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 验证

由于 multer 不验证 mimetype,你需要使用 file-type 或类似的库手动验证 mimetype。

¥As multer doesn't validate mimetype, you need to validate the mimetype manually using file-type or similar library.

Elysia 验证文件上传,并使用 file-type 自动验证 mimetype。

¥Elysia validate file upload, and use file-type to validate mimetype automatically.

中间件

¥Middleware

Express 中间件使用基于队列的单一顺序,而 Elysia 使用基于事件的生命周期提供更精细的控制。

¥Express middleware use a single queue-based order 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

点击图片放大

虽然 Express 按顺序为请求管道提供单一流程,但 Elysia 可以拦截请求管道中的每个事件。

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

ts
import express from 'express'

const app = express()

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

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

	  	if (!token)
	   		return res.status(401).send('Unauthorized')

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

Express 对按顺序执行的中间件使用单个基于队列的顺序。

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 以类型安全的方式自定义上下文,而 Express 则不行。

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

ts
import 
express
from 'express'
import type {
Request
,
Response
} from 'express'
const
app
=
express
()
const
getVersion
= (
req
:
Request
,
res
:
Response
,
next
: Function) => {
// @ts-ignore
req
.version = 2
next
()
}
app
.
get
('/version',
getVersion
, (
req
,
res
) => {
res
.
send
(
req
.
version
)
Property 'version' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
}) const
authenticate
= (
req
:
Request
,
res
:
Response
,
next
: Function) => {
const
token
=
req
.
headers
.
authorization
if (!
token
)
return
res
.
status
(401).
send
('Unauthorized')
// @ts-ignore
req
.token =
token
.
split
(' ')[1]
next
()
}
app
.
get
('/token',
getVersion
,
authenticate
, (
req
,
res
) => {
req
.
version
Property 'version' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
res
.
send
(
req
.
token
)
Property 'token' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
})

Express 对按顺序执行的中间件使用单个基于队列的顺序。

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 在请求管道中的每个点使用特定的事件拦截器

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

¥While Express can, use declare module to extend the Request 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 'express' {
  	interface Request {
    	version: number
  		token: string
  	}
}

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

中间件参数

¥Middleware parameter

Express 使用函数返回一个插件来定义可复用的特定路由中间件,而 Elysia 使用 macro 来定义自定义钩子。

¥Express use a function to return a plugin to define a reusable route-specific middleware, while Elysia use macro to define a custom hook.

ts
import 
express
from 'express'
import type {
Request
,
Response
} from 'express'
const
app
=
express
()
const
role
= (
role
: 'user' | 'admin') =>
(
req
:
Request
,
res
:
Response
,
next
: Function) => {
const
user
=
findUser
(
req
.
headers
.
authorization
)
if (
user
.
role
!==
role
)
return
res
.
status
(401).
send
('Unauthorized')
// @ts-ignore
req
.user =
user
next
()
}
app
.
get
('/token',
role
('admin'), (
req
,
res
) => {
res
.
send
(
req
.
user
)
Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
})

Express 使用函数回调来接受中间件的自定义参数

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

Express 对所有路由使用单个错误处理程序,而 Elysia 对错误处理提供更精细的控制。

¥Express use a single error handler for all routes, while Elysia provides a more granular control over error handling.

ts
import express from 'express'

const app = express()

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

// global error handler
app.use((error, req, res, next) => {
	if(error instanceof CustomError) {
		res.status(500).json({
			message: 'Something went wrong!',
			error
		})
	}
})

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

Express 使用中间件处理错误,所有路由使用同一个错误处理程序。

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 提供更精细的错误处理控制和作用域机制

虽然 Express 使用中间件提供错误处理,但 Elysia 提供:

¥While Express offers error handling using middleware, 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

Express 中间件是全局注册的,而 Elysia 通过显式的作用域机制和代码顺序允许你控制插件的副作用。

¥Express middleware is registered globally, while Elysia give you a control over side-effect of a plugin via explicit scoping mechanism, and order-of-code.

ts
import express from 'express'

const app = express()

app.get('/', (req, res) => {
	res.send('Hello World')
})

const subRouter = express.Router()

subRouter.use((req, res, next) => {
	const token = req.headers.authorization

	if (!token)
		return res.status(401).send('Unauthorized')

	next()
})

app.use(subRouter)

// has side-effect from subRouter
app.get('/side-effect', (req, res) => {
	res.send('hi')
})

Express 不处理中间件的副作用,需要使用前缀来分隔副作用。

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 会将副作用封装到插件中。

默认情况下,Elysia 会将生命周期事件和上下文封装到所使用的实例中,因此除非明确说明,否则插件的副作用不会影响父实例。

¥By default, Elysia will encapsulate lifecycle events and context to the instance that is used, so that the side-effect of a plugin will not affect parent instance unless explicitly stated.

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 - 影响所有实例

虽然 Express 可以通过添加前缀来限定中间件副作用的范围,但这并不是真正的封装。副作用仍然存在,但与任何以该前缀开头的路由分开,这给开发者增加了记住哪个前缀具有副作用的心理负担。

¥While Express can scope the middleware side-effect by adding a prefix, it isn't a true encapsulation. The side-effect is still there but separated to any routes starts with said prefix, adding a mental overhead to the developer to memorize which prefix has side-effect.

你可以执行以下操作:

¥Which you can do the following:

  1. 移动代码顺序,但前提是存在具有副作用的单个实例。
  2. 添加前缀,但副作用仍然存在。如果其他实例具有相同的前缀,则会产生副作用。

由于 Express 并未提供真正的封装,这会导致调试过程变得非常困难。

¥This can leads to a nightmarish scenario to debug as Express doesn't offers true encapsulation.

Express 使用外部库 cookie-parser 来解析 Cookie,而 Elysia 内置了对 Cookie 的支持,并使用基于信号的方法来处理 Cookie。

¥Express use an external library cookie-parser to parse cookies, while Elysia has a built-in support for cookie and use a signal-based approach to handle cookies.

ts
import express from 'express'
import cookieParser from 'cookie-parser'

const app = express()

app.use(cookieParser('secret'))

app.get('/', function (req, res) {
	req.cookies.name
	req.signedCookies.name

	res.cookie('name', 'value', {
		signed: true,
		maxAge: 1000 * 60 * 60 * 24
	})
})

Express 使用 cookie-parser 解析 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

Express 需要单独的 OpenAPI、验证和类型安全配置,而 Elysia 内置了对 OpenAPI 的支持,并使用架构作为单一事实来源。

¥Express require a separate configuration for OpenAPI, validation, and type safety while Elysia has a built-in support for OpenAPI using schema as a single source of truth.

ts
import express from 'express'

import swaggerUi from 'swagger-ui-express'

const app = express()
app.use(express.json())

app.post('/users', (req, res) => {
	// TODO: validate request body
	res.status(201).json(req.body)
})

const swaggerSpec = {
	openapi: '3.0.0',
	info: {
		title: 'My API',
		version: '1.0.0'
	},
	paths: {
		'/users': {
			post: {
				summary: 'Create user',
				requestBody: {
					content: {
						'application/json': {
							schema: {
								type: 'object',
								properties: {
									name: {
										type: 'string',
										description: 'First name only'
									},
									age: { type: 'integer' }
								},
								required: ['name', 'age']
							}
						}
					}
				},
				responses: {
					'201': {
						description: 'User created'
					}
				}
			}
		}
	}
}

app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))

Express 需要单独的 OpenAPI、验证和类型安全配置

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 使用模式作为单一事实来源

Elysia 将根据你提供的模式生成 OpenAPI 规范,并根据该模式验证请求和响应,并自动推断类型。

¥Elysia will generate OpenAPI specification based on the schema you provided, and validate the request and response based on the schema, and infer type automatically.

Elysia 还将在 model 中注册的模式附加到 OpenAPI 规范中,允许你在 Swagger 或 Scalar UI 的专用部分中引用该模型。

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

测试

¥Testing

Express 使用单个 supertest 库来测试应用,而 Elysia 构建于 Web 标准 API 之上,因此可以与任何测试库一起使用。

¥Express use a single supertest library to test the application, while Elysia is built on top of Web Standard API allowing it be used with any testing library.

ts
import express from 'express'
import request from 'supertest'
import { describe, it, expect } from 'vitest'

const app = express()

app.get('/', (req, res) => {
	res.send('Hello World')
})

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

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

Express 使用 supertest 库来测试应用

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

Elysia 内置了端到端类型安全支持,无需使用 Eden 进行代码生成,而 Express 不提供此功能。

¥Elysia offers a built-in support for end-to-end type safety without code generation using Eden, Express doesn't offers one.

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 是正确的选择。

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


Elysia 提供了更符合人机工程学且对开发者更友好的体验,注重性能、类型安全和简洁性。Express 是 Node.js 的流行 Web 框架,但在性能和简洁性方面存在一些局限性。

¥Elysia offers a more ergonomic and developer-friendly experience with a focus on performance, type safety, and simplicity while Express is a popular web framework for Node.js, but it has some limitations when it comes to performance and simplicity.

如果你正在寻找一个易于使用、具有良好的开发体验并且基于 Web 标准 API 构建的框架,那么 Elysia 是你的正确选择。

¥If you are looking for a framework that is easy to use, has a great developer experience, and is built on top of Web Standard API, Elysia is the right choice for you.

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

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