@sunshj/server
v0.1.0-beta.20
Published
一个轻量级、高性能的 Web 服务器框架
Readme
@sunshj/server
一个轻量级、高性能的 Web 服务器框架,结合了 Express.js、Hono.js 和 h3 的优秀特性。
✨ 特性
- 🚀 极致性能: 基于 Node.js 原生 HTTP 模块,零外部依赖
- 🔷 TypeScript 优先: 完整的类型定义和智能提示,路径参数自动类型推断
- 🔗 线性链式中间件: 简洁高效的中间件系统,支持早期返回和错误处理
- 🎯 现代 API: 简洁直观,开发体验优秀
- 📦 轻量级: 仅 36.12KB (CJS),比 Express 小 5 倍
- 🔧 灵活路由: 动态参数、通配符、嵌套路由、类型化路由组
- ⚡ 智能响应: 自动推断响应类型,按需解析
- 📤 文件上传: 原生支持 FormData 和文件上传解析
- 🌊 流式响应: 原生支持 ReadableStream,可轻松实现 SSE
- 🪝 钩子系统: 灵活的生命周期钩子(beforeRequest、beforeResponse、afterResponse、error、notFound)
- 🌐 Web Standards: 基于 Web 标准 API,兼容多种运行时(Node.js、Deno、Bun、Cloudflare Workers)
🚀 快速开始
安装
npm install @sunshj/server
# 或者
pnpm add @sunshj/server
# 或者
yarn add @sunshj/server基础使用
import { bodyParser, createApp } from '@sunshj/server'
const app = createApp()
// 注入 body parser 中间件(必须在访问请求体之前注册)
app.use(bodyParser())
// 基础路由
app.get('/', ctx => {
return { message: 'Hello World!' }
})
// 动态路由参数
app.get('/users/:id', ctx => {
const { id } = ctx.params
return { user: { id, name: `User ${id}` } }
})
// 启动服务器
app.listen(3000, server => {
console.log('🚀 Server running on http://localhost:3000')
console.log('Server address:', server.address())
})📚 核心概念
Context 上下文
每个请求处理函数都会接收一个 Context 对象,包含请求信息和响应方法:
app.get('/api/users/:id', ctx => {
// 路径参数
const { id } = ctx.params
// 查询参数
const { page = '1', limit = '10' } = ctx.query
// 请求头 (Web Standard Headers)
const userAgent = ctx.headers.get('user-agent')
// 智能响应
return ctx.status(200).json({
user: { id },
pagination: { page: Number(page), limit: Number(limit) },
userAgent
})
})路由定义
支持所有标准 HTTP 方法:
app.get('/users', getAllUsers)
app.post('/users', createUser)
app.put('/users/:id', updateUser)
app.delete('/users/:id', deleteUser)
app.patch('/users/:id', patchUser)
// 链式路由
app.route('/users/:id').get(getUser).put(updateUser).delete(deleteUser)
// 支持多种 HTTP 方法
app.get('/users', getAllUsers)
app.post('/users', createUser)
app.put('/users/:id', updateUser)
app.delete('/users/:id', deleteUser)
app.patch('/users/:id', patchUser)
app.head('/users/:id', checkUser)
app.options('/users', getUserOptions)
// 通配符路由
app.get('/static/*', serveStaticFiles)
// 匹配所有方法
app.all('/api/*', authMiddleware)中间件系统
框架采用线性链式中间件模型(类似 Express),简洁高效且易于理解。
中间件基础概念
中间件是一个可以访问请求上下文 (Context) 的函数,可以:
- 在请求到达路由处理器之前执行预处理逻辑
- 通过调用
await ctx.next()将控制权传递给下一个处理器 - 通过返回
Response提前终止请求处理链
重要特性:
- 中间件和路由处理器使用相同的
RouteHandler类型 - 中间件按注册顺序依次执行(线性模型)
await ctx.next()现在是可选的:中间件如果不调用ctx.next()且没有返回值,会自动继续执行下一个处理器await ctx.next()之后的代码不会执行(这是设计特性,非 bug)- 如需响应后逻辑(如日志、清理),请使用
afterResponsehook
全局中间件
应用于所有请求的中间件:
// 日志中间件 - ctx.next() 现在是可选的
app.use(ctx => {
console.log(`→ ${ctx.method} ${ctx.url.pathname}`)
// ✅ 不调用 ctx.next() 也会自动继续到下一个处理器
})
// 也可以显式调用 ctx.next()
app.use(async ctx => {
console.log(`→ ${ctx.method} ${ctx.url.pathname}`)
await ctx.next() // 显式继续到下一个处理器
// ⚠️ 注意:这之后的代码不会执行(线性链式模型的设计)
// 如需响应后逻辑,请使用 afterResponse hook
})
// 计时中间件
app.use(ctx => {
const start = Date.now()
// 可以省略 ctx.next(),会自动继续
// ⚠️ 注意:这里无法计算响应时间,应该使用 afterResponse hook
})
// 正确的计时方式:使用 afterResponse hook
app.on('afterResponse', (request, response, ctx, duration) => {
console.log(`${ctx.method} ${ctx.url.pathname} - ${duration}ms`)
})路径中间件
只对特定路径生效的中间件:
// API 认证中间件
app.use('/api/*', async ctx => {
const token = ctx.headers.get('authorization')
if (!token) {
return ctx.error(401, 'Missing authorization header')
}
// 验证 token
try {
const user = await verifyToken(token)
ctx.user = user // 扩展 context
// ctx.next() 是可选的,这里会自动继续
} catch {
return ctx.error(401, 'Invalid token')
}
})
// 管理员权限验证
app.use('/admin/*', ctx => {
if (!ctx.user?.isAdmin) {
return ctx.error(403, 'Admin access required')
}
// 不需要 ctx.next(),会自动继续
})中间件早期返回
中间件可以通过返回 Response 来终止请求处理链:
// 认证中间件示例
function authMiddleware(ctx) {
if (!ctx.headers.get('authorization')) {
return ctx.error(401, 'Unauthorized') // 🚀 直接返回,停止执行链
}
// 不需要 ctx.next(),会自动继续到下一个处理器
}
// 使用多个中间件
app.post(
'/protected',
authMiddleware, // 第一个中间件:认证
async ctx => {
// 第二个中间件:验证请求体
const data = await ctx.readBody()
if (!data.name) {
return ctx.error(400, 'Name is required')
}
// ctx.next() 是可选的
},
async ctx => {
// 最终的路由处理器
const data = await ctx.readBody()
return { message: 'Success', data }
}
)路由级中间件
多个中间件可以作为参数传入路由方法:
// 定义中间件函数 - ctx.next() 是可选的
function logMiddleware(ctx) {
console.log(`Processing: ${ctx.url.pathname}`)
// 不需要 ctx.next(),会自动继续
}
function validateMiddleware(ctx) {
const contentType = ctx.headers.get('content-type')
if (!contentType?.includes('application/json')) {
return ctx.error(400, 'Content-Type must be application/json')
}
// ctx.next() 是可选的
}
// 应用多个中间件到特定路由
app.get('/protected', authMiddleware, logMiddleware, ctx => {
return { message: 'Protected route accessed', user: ctx.user }
})
app.post('/data', validateMiddleware, logMiddleware, async ctx => {
const data = await ctx.readBody()
return { received: data }
})中间件执行流程
// 执行顺序示例 - ctx.next() 现在是可选的
app.use(ctx => {
console.log('1. 全局中间件')
// 不需要 ctx.next(),会自动继续
})
app.use('/api/*', ctx => {
console.log('2. 路径中间件')
// ctx.next() 是可选的
})
app.get(
'/api/users',
ctx => {
console.log('3. 第一个路由中间件')
// 可以省略 ctx.next()
},
ctx => {
console.log('4. 第二个路由中间件')
// 自动继续到下一个处理器
},
ctx => {
console.log('5. 最终路由处理器')
return { message: 'Done' }
}
)
// 请求 GET /api/users 时的执行顺序:
// 1. 全局中间件
// 2. 路径中间件
// 3. 第一个路由中间件
// 4. 第二个路由中间件
// 5. 最终路由处理器中间件作用域
框架通过 routerId 实现中间件作用域隔离。由于 App extends Router,App 和 Router 实例都有唯一的 routerId:
// 1. App 级全局中间件 (routerId === app.id, 无 path)
// 应用到所有路由,包括挂载的 Router 路由
app.use(ctx => {
console.log('App middleware')
// ctx.next() 是可选的
})
// 2. App 级路径中间件 (routerId === app.id, 有 path)
// 应用到匹配路径的所有路由
app.use('/api/*', ctx => {
console.log('API middleware')
// 不需要 ctx.next()
})
// 3. Router 级全局中间件 (routerId === apiRouter.id, 无 path)
// 只应用到该 Router 的路由
const apiRouter = createRouter()
apiRouter.use(ctx => {
console.log('Router middleware - 只对 apiRouter 的路由生效')
// 自动继续
})
apiRouter.get('/users', ctx => ({ users: [] }))
// 4. Router 级路径中间件 (routerId === apiRouter.id, 有 path)
// 只应用到该 Router 中匹配路径的路由
apiRouter.use('/admin/*', ctx => {
console.log('Router path middleware - 只对 apiRouter 的 /admin/* 路由生效')
// ctx.next() 可选
})
apiRouter.get('/admin/users', ctx => ({ adminUsers: [] }))
// 挂载路由器
app.use('/api', apiRouter)
// 请求 GET /api/users 的中间件执行顺序:
// 1. App 全局中间件 (routerId === app.id)
// 2. App 路径中间件 /api/* (routerId === app.id)
// 3. Router 全局中间件 (routerId === apiRouter.id)
// 4. 路由处理器 /users
// 请求 GET /api/admin/users 的中间件执行顺序:
// 1. App 全局中间件 (routerId === app.id)
// 2. App 路径中间件 /api/* (routerId === app.id)
// 3. Router 全局中间件 (routerId === apiRouter.id)
// 4. Router 路径中间件 /admin/* (routerId === apiRouter.id)
// 5. 路由处理器 /admin/users条件中间件
根据条件动态执行逻辑:
// 认证检查 - ctx.next() 是可选的
function requireAuth(ctx) {
if (!ctx.headers.get('authorization')) {
return ctx.error(401, 'Authentication required')
}
// 不需要 ctx.next(),会自动继续
}
// 角色检查
function requireRole(role: string) {
return ctx => {
if (ctx.user?.role !== role) {
return ctx.error(403, `Role ${role} required`)
}
// ctx.next() 可选
}
}
// 使用条件中间件
app.get('/user/profile', requireAuth, ctx => {
return { user: ctx.user }
})
app.get('/admin/settings', requireAuth, requireRole('admin'), ctx => {
return { settings: {} }
})中间件最佳实践
// ✅ 推荐:使用 afterResponse hook 处理响应后逻辑
app.on('afterResponse', (request, response, ctx, duration) => {
// 记录日志
logger.info({
method: ctx.method,
path: ctx.url.pathname,
status: response.status,
duration
})
})
// ❌ 不推荐:在中间件的 ctx.next() 之后写代码(不会执行)
app.use(async ctx => {
await ctx.next()
console.log('这段代码不会执行') // ❌
})
// ✅ 推荐:中间件只做请求预处理,ctx.next() 是可选的
app.use(ctx => {
ctx.startTime = Date.now()
// 不需要 ctx.next(),会自动继续
})
// ✅ 推荐:明确的早期返回
app.use(ctx => {
if (condition) {
return ctx.error(400, 'Bad request') // 明确终止
}
// ctx.next() 是可选的,会自动继续执行
})路由分组
使用路由器创建模块化的路由:
import { createRouter } from '@sunshj/server'
// API 路由
const apiRouter = createRouter()
apiRouter.get('/users', getAllUsers)
apiRouter.post('/users', createUser)
apiRouter.get('/users/:id', getUser)
// 管理员路由
const adminRouter = createRouter()
adminRouter.use(adminAuthMiddleware) // 路由级中间件
adminRouter.get('/stats', getStats)
adminRouter.get('/logs', getLogs)
// 挂载路由器
app.use('/api/v1', apiRouter)
app.use('/admin', adminRouter)智能响应
框架会自动推断响应类型,让开发更简单:
// 自动 JSON 响应
app.get('/api/user', ctx => {
return { id: 1, name: 'John' } // → Content-Type: application/json
})
// 自动文本响应
app.get('/health', ctx => {
return 'OK' // → Content-Type: text/plain
})
// 自动 HTML 响应
app.get('/home', ctx => {
return '<h1>Welcome!</h1>' // → Content-Type: text/html
})
// HTML 模板字符串语法
app.get('/profile', ctx => {
const name = 'John'
return ctx.html`<div><h1>Hello, ${name}!</h1></div>`
})
// 显式响应方法
app.get('/explicit', ctx => {
return ctx.json({ success: true })
// 或 ctx.text('Hello')
// 或 ctx.html('<h1>Hello</h1>')
// 或 ctx.redirect('/login')
})
// 直接返回 Response 对象(用于高级场景)
app.get('/custom', ctx => {
return new Response('Custom response', {
status: 200,
headers: {
'Content-Type': 'text/plain',
'X-Custom-Header': 'value'
}
})
})
// 链式调用设置状态码
app.post('/create', ctx => {
return ctx.status(201).json({ created: true, id: 123 })
})
// 错误响应
app.get('/error', ctx => {
return ctx.error(404, 'Resource not found')
})请求体处理
现代化的请求体处理,智能且高效:
import { bodyParser, createApp } from '@sunshj/server'
const app = createApp()
// 1. 注入 body parser 中间件(必须)
app.use(
bodyParser({
maxSize: 10 * 1024 * 1024, // 全局最大 10MB
limits: {
json: 1 * 1024 * 1024, // JSON 限制 1MB
urlencoded: 1 * 1024 * 1024, // URL-encoded 限制 1MB
multipart: 50 * 1024 * 1024, // Multipart 限制 50MB
text: 1 * 1024 * 1024 // Text 限制 1MB
},
strict: false, // JSON 解析失败时降级为文本(默认 false)
allowEmpty: true // 允许空请求体(默认 true)
})
)
// 2. 使用 ctx.readBody() 读取请求体(推荐)
app.post('/api/users', async ctx => {
const userData = await ctx.readBody() // ✅ 推荐:更好的类型推断
// 数据验证
if (!userData.name || !userData.email) {
return ctx.error(400, 'Name and email are required')
}
const user = await createUser(userData)
return ctx.status(201).json(user)
})
// ctx.body 已废弃,但仍可用
app.post('/api/data', async ctx => {
const data = await ctx.body // ⚠️ 已废弃,建议使用 ctx.readBody()
return { received: data }
})
// 获取类型化查询参数
app.get('/search', ctx => {
const query = ctx.getQuery<{ q: string; page?: string }>()
return { query: query.q, page: Number(query.page || 1) }
})
// 文件上传示例
app.post('/upload', async ctx => {
// 使用便捷方法获取单个文件
const file = await ctx.file('avatar') // 获取名为 'avatar' 的文件
if (!file) {
return ctx.error(400, 'No file uploaded')
}
// 使用 Web Standards File API
const filename = `${Date.now()}-${file.name}`
const arrayBuffer = await file.arrayBuffer()
// 保存文件
await writeFile(filename, Buffer.from(arrayBuffer))
return {
message: 'File uploaded successfully',
filename,
size: file.size,
type: file.type
}
})
// 多文件上传
app.post('/upload/multiple', async ctx => {
// 使用便捷方法获取多个文件
const files = await ctx.files('photos') // 获取名为 'photos' 的所有文件
if (!files || files.length === 0) {
return ctx.error(400, 'No files uploaded')
}
const uploadedFiles = await Promise.all(
files.map(async file => {
const filename = `${Date.now()}-${file.name}`
const arrayBuffer = await file.arrayBuffer()
await writeFile(filename, Buffer.from(arrayBuffer))
return { filename, size: file.size }
})
)
return {
message: `${files.length} files uploaded successfully`,
files: uploadedFiles
}
})
// 获取表单字段
app.post('/form', async ctx => {
const name = await ctx.field('name') // 获取单个字段
const tags = await ctx.fields('tags') // 获取字段数组
const files = await ctx.files('documents') // 获取文件数组
return { name, tags, fileCount: files.length }
})
// 直接访问完整的 ParsedFormData
app.post('/upload/advanced', async ctx => {
const body = await ctx.readBody<ParsedFormData>()
// ParsedFormData 提供辅助方法
const singleFile = body.file('avatar') // 获取单个文件
const multipleFiles = body.fileArray('docs') // 获取文件数组
const fieldValue = body.field('name') // 获取单个字段值
const fieldArray = body.fieldArray('tags') // 获取字段数组
return {
file: singleFile?.name,
docs: multipleFiles.map(f => f.name),
name: fieldValue,
tags: fieldArray
}
})Body Parser 特性:
- ✅ 必须注入: 使用
bodyParser()中间件注入解析器 - ✅ 惰性解析: 只在访问
ctx.readBody()或ctx.body时解析 - ✅ 智能检测: 根据 Content-Type 自动选择解析方式
- ✅ 大小限制: 可针对不同类型设置不同的大小限制
- ✅ 类型安全: 完整的 TypeScript 类型支持
- ✅ Web Standards: 使用原生
request.json(),request.formData()等 API - ✅ 便捷方法:
ctx.file(),ctx.files(),ctx.field(),ctx.fields()简化文件和字段访问
🔥 高级用法
Server-Sent Events (SSE)
框架原生支持返回 ReadableStream,可轻松实现 SSE 流式推送:
app.get('/events', () => {
const stream = new ReadableStream({
start(controller) {
let count = 0
const interval = setInterval(() => {
if (count < 10) {
// 发送 SSE 格式的数据
controller.enqueue(`data: ${JSON.stringify({ count, time: Date.now() })}\n\n`)
count++
} else {
clearInterval(interval)
controller.close()
}
}, 1000)
},
cancel() {
console.log('SSE stream cancelled')
}
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
})
})客户端连接:
const eventSource = new EventSource('/events')
eventSource.addEventListener('message', event => {
const data = JSON.parse(event.data)
console.log('Received:', data)
})错误处理
// 全局错误处理中间件
app.use(async ctx => {
try {
await ctx.next()
} catch (error) {
console.error('Request error:', error)
if (error.code === 'VALIDATION_ERROR') {
return ctx.error(400, error.message)
}
return ctx.error(500, 'Internal Server Error')
}
})
// 自定义错误类
class ValidationError extends Error {
constructor(message: string) {
super(message)
this.code = 'VALIDATION_ERROR'
}
}
app.post('/validate', async ctx => {
const data = await ctx.readBody()
if (!data.email) {
throw new ValidationError('Email is required')
}
return { valid: true }
})Web Standards 兼容性
框架基于 Web 标准 API 构建,提供 fetch 方法兼容多种运行时:
import { createApp } from '@sunshj/server'
const app = createApp()
app.get('/hello', ctx => {
return { message: 'Hello, World!' }
})
// 使用 Web Standard Request/Response
const request = new Request('http://localhost:3000/hello')
const response = await app.fetch(request)
const data = await response.json()
console.log(data) // { message: 'Hello, World!' }
// 兼容 Cloudflare Workers、Deno、Bun 等运行时
export default {
fetch: app.fetch
}Web Standards 特性:
- ✅ 使用标准
Request和Response对象 - ✅ 支持
fetch(request)方法调用 - ✅ 兼容 Node.js、Deno、Bun、Cloudflare Workers
- ✅ 完全符合 Web API 规范
Hooks 生命周期
框架提供完整的请求生命周期钩子,用于日志、监控和自定义处理:
// beforeRequest - 请求到达时(无法中断请求)
app.on('beforeRequest', (request, ctx) => {
console.log(`→ ${ctx.method} ${ctx.url.pathname}`)
})
// beforeResponse - 响应发送前(可修改响应)
app.on('beforeResponse', (response, ctx) => {
const headers = new Headers(response.headers)
headers.set('X-Powered-By', '@sunshj/server')
return new Response(response.body, {
status: response.status,
headers
})
})
// afterResponse - 响应发送后(日志、性能统计、清理)
// 这是实现"响应后逻辑"的推荐方式(而不是在中间件的 ctx.next() 之后写代码)
app.on('afterResponse', (request, response, ctx, duration) => {
// 记录完整的请求/响应日志
console.log({
method: ctx.method,
path: ctx.url.pathname,
status: response.status,
duration: `${duration}ms`,
userAgent: request.headers.get('user-agent')
})
// 发送性能指标到监控系统
// metrics.record('http.request.duration', duration, {
// method: ctx.method,
// status: response.status
// })
})
// notFound - 自定义 404 处理
app.on('notFound', ctx => {
return ctx.status(404).json({
error: 'Not Found',
path: ctx.url.pathname,
message: '请求的资源不存在'
})
})
// error - 全局错误处理
app.on('error', (error, ctx) => {
console.error('Error:', error)
return ctx.status(500).json({
error: 'Internal Server Error',
message: error.message
})
})Hooks 执行顺序:
1. beforeRequest → 请求到达
2. 中间件链执行
3. 路由处理器执行
4. notFound → (如果没有匹配的路由)
5. beforeResponse → 修改响应
6. 发送响应到客户端
7. afterResponse → 后台日志/清理(不阻塞响应)
8. error → (如果有错误发生)特别说明:
afterResponse在响应发送后执行,不会阻塞响应afterResponse中的所有注册的处理器都会执行(不会早期返回)afterResponse适合用于日志、监控、资源清理等场景- 这是实现"响应后逻辑"的推荐方式,而不是在中间件的
await ctx.next()之后写代码(那些代码不会执行) - 其他 hooks 会在第一个返回值时停止执行
性能监控
使用 afterResponse hook 实现性能监控(推荐方式):
// 使用 afterResponse hook 记录请求时长
app.on('afterResponse', (request, response, ctx, duration) => {
console.log(`${ctx.method} ${ctx.url.pathname} - ${duration.toFixed(2)}ms`)
})
// 请求计数器和日志
let requestCount = 0
app.on('beforeRequest', (request, ctx) => {
requestCount++
console.log(`[${requestCount}] ${ctx.method} ${ctx.url.pathname}`)
})内容协商
app.get('/api/data', ctx => {
const accept = ctx.headers.get('accept')
const data = { message: 'Hello', timestamp: Date.now() }
if (accept?.includes('application/xml')) {
return new Response(`<data><message>${data.message}</message></data>`, {
headers: { 'Content-Type': 'application/xml' }
})
}
return ctx.json(data) // 默认 JSON
})📖 TypeScript 支持
框架提供完整的 TypeScript 支持和类型推断:
导出的类和函数
// 导入所有可用的类、函数和类型
import {
App, // 应用程序类
BodyParser, // 请求体解析器类
bodyParser, // 创建 body parser 中间件
Context, // 请求上下文类
ContextResponse, // 响应方法类
ContextStore, // 状态管理类
createApp, // 创建应用实例
createLogger, // 创建日志器实例
createRouter, // 创建路由器实例
createRpcClient, // 创建 RPC 客户端
defineRouteHandler, // 定义类型化路由处理器
Logger, // 日志器类
logger, // 默认日志器实例
MiddlewareManager, // 中间件管理器基类
RouteGroup, // 路由组类
RouteHandlerError, // 路由处理器错误
Router, // 路由器类
RpcError, // RPC 调用错误
withRPC, // 为 App 添加 RPC 功能
type Hooks, // 生命周期钩子类型
type HTTPMethod, // HTTP 方法类型
type LogLevel, // 日志级别类型
type ParsedFormData, // 解析后的表单数据类型(包含辅助方法)
type ParsedFormDataResult, // 解析后的表单数据类型(无方法)
type Route, // 路由配置类型
type RouteHandler // 路由处理器类型
} from '@sunshj/server'
// 从 body-parser 模块导入错误类(用于错误处理)
import { BodyParseError, BodySizeError } from '@sunshj/server/body-parser'
// 扩展 Context 类型
declare module '@sunshj/server' {
interface Context {
user?: { id: string; name: string }
}
}
// 类型化的处理函数
const getUser: RouteHandler<any, { id: string }> = async ctx => {
const { id } = ctx.params // id 自动推断为 string
const user = await findUserById(id)
if (!user) {
return ctx.error(404, 'User not found')
}
return { user }
}
// 🔷 路由组完整类型支持
// 路径参数类型自动推断
app.route('/users/:id').get(ctx => {
const { id } = ctx.params // ✅ id 类型为 string
return { user: { id } }
})
app.route('/posts/:postId').get('/comments/:commentId?', ctx => {
const { postId, commentId } = ctx.params // ✅ 完整类型推断: { postId: string, commentId?: string }
return { postId, commentId }
})
// 类型化的中间件(使用 RouteHandler 类型)
// 注意:中间件和路由处理器使用相同的 RouteHandler 类型
const logMiddleware: RouteHandler = ctx => {
console.log(`${ctx.method} ${ctx.url.pathname}`)
// ctx.next() 是可选的,不调用也会自动继续到下一个处理器
}
// 泛型支持
interface CreateUserBody {
name: string
email: string
}
app.post('/users', async (ctx: Context<CreateUserBody>) => {
const userData = await ctx.body // 类型为 CreateUserBody
return { user: userData }
})类型说明:
RouteHandler<Body, Params, Query>- 统一的处理器类型,用于中间件和路由处理器- 中间件可以调用
await ctx.next()显式继续执行链,也可以省略(自动继续) - 路由处理器必须返回值作为响应
- 返回值会自动转换为 Response 对象(通过智能响应检测)
⚡ 与 Express.js 对比
| 特性 | @sunshj/server | Express.js | | ---------- | --------------------- | --------------- | | 包大小 | 36.12KB (CJS) | 197KB | | 依赖数量 | 0 | 20+ | | TypeScript | 原生支持 | 需要 @types 包 | | 响应 API | 现代化,自动推断 | 传统 res.json() | | 性能 | 高性能,原生 HTTP | 较重的抽象层 | | 中间件模型 | 线性执行 | 线性执行 |
🔄 迁移示例
Express.js:
const express = require('express')
const app = express()
app.use(express.json())
app.get('/users/:id', (req, res) => {
res.json({
user: { id: req.params.id }
})
})
app.post('/user', (req, res) => {
const data = req.body
res.json({
user: { data }
})
})
app.listen(3000)@sunshj/server:
import { bodyParser, createApp } from '@sunshj/server'
const app = createApp()
app.use(bodyParser())
app.get('/users/:id', ctx => {
return {
user: { id: ctx.params.id }
}
})
app.post('/user', async ctx => {
const data = await ctx.readBody()
return {
user: { data }
}
})
app.listen(3000)迁移优势:
- ✅ 代码量减少 30%
- ✅ 原生 TypeScript 支持
- ✅ 更现代的 API 设计
📋 API 参考
核心函数
createApp(options?)
创建应用实例。
import { createApp } from '@sunshj/server'
// 基础用法
const app = createApp()
// 带配置选项
const app = createApp({
server: {
timeout: 120000, // 请求超时时间(毫秒)
keepAliveTimeout: 5000, // Keep-Alive 超时时间(毫秒)
maxHeaderSize: 16384 // 最大请求头大小(字节)
}
})createRouter()
创建路由器实例。
import { createRouter } from '@sunshj/server'
const router = createRouter()defineRouteHandler()
定义类型化的路由处理函数,提供完整的类型推断支持。
import { defineRouteHandler } from '@sunshj/server'
// 定义类型化的处理函数
const getUserHandler = defineRouteHandler<
any, // Body type
{ id: string }, // Params type
{ include?: string } // Query type
>(async ctx => {
const { id } = ctx.params // 自动推断为 string
const { include } = ctx.query // 自动推断为 string | undefined
const user = await findUserById(id)
if (!user) {
return ctx.error(404, 'User not found')
}
return { user }
})
app.get('/users/:id', getUserHandler)
// 用于创建可复用的中间件
function zodValidator(schemas: Schema) {
return defineRouteHandler(async ctx => {
// 验证逻辑
await ctx.next()
})
}bodyParser(options?)
创建 body parser 中间件,用于解析请求体。
import { bodyParser } from '@sunshj/server'
// 基础用法
app.use(bodyParser())
// 自定义配置
app.use(
bodyParser({
maxSize: 5 * 1024 * 1024, // 全局最大 5MB
limits: {
json: 1 * 1024 * 1024, // JSON 限制 1MB
urlencoded: 1 * 1024 * 1024, // URL-encoded 限制 1MB
multipart: 10 * 1024 * 1024, // Multipart 限制 10MB
text: 1 * 1024 * 1024 // Text 限制 1MB
},
strict: true, // 严格模式:JSON 解析失败时抛出错误
allowEmpty: true // 允许空请求体
})
)App 属性和方法
创建的应用实例提供以下方法:
| 方法 | 类型 | 描述 |
| ----------------------------- | ------------------- | -------------------------------- |
| app.listen(port, callback?) | void | 启动 HTTP 服务器 |
| app.close() | Promise<void> | 关闭 HTTP 服务器 |
| app.fetch(request) | Promise<Response> | Web Standards 兼容的请求处理方法 |
| app.on(event, handler) | this | 注册生命周期钩子 |
访问底层 HTTP 服务器示例:
const app = createApp()
app.get('/', ctx => ({ message: 'Hello' }))
app.listen(3000, server => {
// 回调函数接收 server 实例作为参数
console.log('Server:', server.address())
})Router 和 MiddlewareManager
Router 类继承自 MiddlewareManager,提供路由注册和中间件管理功能:
// Router 提供的 HTTP 方法
router.get(path, ...handlers)
router.post(path, ...handlers)
router.put(path, ...handlers)
router.delete(path, ...handlers)
router.patch(path, ...handlers)
router.head(path, ...handlers)
router.options(path, ...handlers)
router.all(path, ...handlers) // 匹配所有 HTTP 方法
// 中间件注册
router.use(middleware) // 全局中间件
router.use(path, middleware) // 路径中间件
router.use(path, router) // 挂载子路由器
// 路由组
router.route(basePath) // 返回 RouteGroup 实例Context 属性
| 属性 | 类型 | 描述 |
| ------------- | ------------------------ | ----------------------------------------- |
| ctx.params | Record<string, string> | 路径参数 |
| ctx.query | Record<string, string> | 查询参数 |
| ctx.body | Promise<any> | 请求体 (已废弃,建议使用 ctx.readBody()) |
| ctx.headers | Headers | Web Standard Headers 对象 |
| ctx.method | HTTPMethod | HTTP 方法 |
| ctx.url | URL | URL 对象 |
| ctx.next | () => Promise<void> | 调用下一个中间件/处理器 |
| ctx.store | Map<string, unknown> | 全局共享状态存储 (来自 ContextStore) |
Context 方法
实例方法
| 方法 | 描述 | 示例 |
| ---------------------------- | ------------------------ | ---------------------------------- |
| ctx.json(data) | JSON 响应 | ctx.json({ success: true }) |
| ctx.text(text) | 文本响应 | ctx.text('Hello World') |
| ctx.html(html) | HTML 响应 | ctx.html('<h1>Hello</h1>') |
| ctx.html`template` | HTML 模板字符串响应 | ctx.html`<h1>${name}</h1>` |
| ctx.status(code) | 设置状态码 | ctx.status(201) |
| ctx.redirect(url, status?) | 重定向 | ctx.redirect('/login', 302) |
| ctx.error(status, message) | 错误响应 | ctx.error(404, 'Not Found') |
| ctx.readBody<T>() | 读取类型化请求体(推荐) | await ctx.readBody<UserData>() |
| ctx.getQuery<T>() | 获取类型化查询参数 | ctx.getQuery<{ page: string }>() |
| ctx.file(fieldName) | 获取单个上传文件 | await ctx.file('avatar') |
| ctx.files(fieldName) | 获取多个上传文件 | await ctx.files('photos') |
| ctx.field(fieldName) | 获取单个表单字段 | await ctx.field('name') |
| ctx.fields(fieldName) | 获取多个表单字段 | await ctx.fields('tags') |
| ctx.provide(key, value) | 提供全局共享状态 | ctx.provide('db', dbInstance) |
| ctx.inject<T>(key) | 注入全局共享状态 | ctx.inject<Database>('db') |
静态方法
| 方法 | 描述 | 示例 |
| --------------------------------- | ---------------- | ----------------------------------- |
| Context.json(data, statusCode?) | 创建 JSON 响应 | Context.json({ ok: true }, 200) |
| Context.text(text, statusCode?) | 创建文本响应 | Context.text('Hello', 200) |
| Context.html(html, statusCode?) | 创建 HTML 响应 | Context.html('<h1>Hi</h1>', 200) |
| Context.error(status, message?) | 创建错误响应 | Context.error(404, 'Not Found') |
| Context.provide(key, value) | 提供全局共享状态 | Context.provide('db', dbInstance) |
| Context.inject<T>(key) | 注入全局共享状态 | Context.inject<Database>('db') |
错误处理类
框架提供了三个专门的错误类,用于处理不同类型的错误场景:
BodyParseError
请求体解析错误,当解析失败或验证失败时抛出。
import { BodyParseError, BodySizeError } from '@sunshj/server/body-parser'
try {
const data = await ctx.readBody()
} catch (error) {
if (error instanceof BodyParseError) {
console.error('Parse error:', error.message)
console.error('Raw body:', error.rawBody)
return ctx.error(400, 'Invalid request body')
}
}属性:
message: string- 错误信息rawBody: string- 原始请求体内容
BodySizeError
请求体大小超限错误,当请求体或文件超过配置的大小限制时抛出。
try {
const data = await ctx.readBody()
} catch (error) {
if (error instanceof BodySizeError) {
console.error('Size limit exceeded:', error.actualSize, '>', error.limitSize)
return ctx.error(413, `Request too large: ${error.actualSize} bytes`)
}
}属性:
message: string- 错误信息actualSize: number- 实际大小(字节)limitSize: number- 限制大小(字节)
RouteHandlerError
路由处理器错误,当路由处理器没有返回值时抛出。
import { RouteHandlerError } from '@sunshj/server'
// 框架内部使用,用户一般不需要手动抛出
// 当最后一个路由处理器没有返回响应时会自动抛出属性:
message: string- 错误信息
日志系统
框架提供内置的日志工具,支持日志级别和格式化输出:
import { createLogger, logger, type LogLevel } from '@sunshj/server'
// 使用默认日志器
logger.info('Server started')
logger.error('Something went wrong')
// 创建自定义日志器
const appLogger = createLogger({
level: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
prefix: 'MyApp',
timestamp: true
})
appLogger.debug('Debug message') // 只有 level >= debug 才会输出
appLogger.info('Info message')
appLogger.warn('Warning message')
appLogger.error('Error message')
// 设置日志级别
appLogger.setLevel('warn') // 只输出 warn 和 error
appLogger.setPrefix('NewPrefix')
// 创建子日志器
const childLogger = appLogger.child({ prefix: 'SubModule' })
childLogger.info('Child logger message') // [MyApp:SubModule] ...Logger 方法:
| 方法 | 描述 |
| --------------------- | --------------------------- |
| logger.debug(...) | 输出 debug 级别日志 |
| logger.info(...) | 输出 info 级别日志 |
| logger.warn(...) | 输出 warn 级别日志 |
| logger.error(...) | 输出 error 级别日志 |
| logger.log(...) | info 的别名 |
| logger.setLevel() | 设置日志级别 |
| logger.setPrefix() | 设置日志前缀 |
| logger.child(opts) | 创建子日志器,继承父级配置 |
RPC 系统
框架提供类型安全的 RPC(远程过程调用)系统,支持服务端和客户端:
// 服务端
import { bodyParser, createApp, withRPC } from '@sunshj/server'
const app = withRPC(createApp())
app.use(bodyParser())
// 定义 RPC 函数
const rpcFunctions = {
add(a: number, b: number) {
return a + b
},
getUser(id: string) {
return { id, name: `User ${id}` }
}
}
// 注册 RPC 处理器(默认路径: /__rpc)
app.registerRpcFunctions(rpcFunctions)
app.listen(3000)// 客户端
import { createRpcClient, RpcError } from '@sunshj/server/rpc'
// 创建类型安全的 RPC 客户端
const client = createRpcClient<typeof rpcFunctions>('http://localhost:3000', {
timeout: 30000, // 请求超时(毫秒)
headers: { Authorization: 'Bearer token' } // 自定义请求头
})
// 调用远程函数(完整类型推断)
const sum = await client.add(1, 2) // 类型: number
const user = await client.getUser('123') // 类型: { id: string, name: string }
// 错误处理
try {
await client.unknownMethod()
} catch (error) {
if (error instanceof RpcError) {
console.error(error.code, error.message)
}
}RPC 导出:
| 导出 | 描述 |
| ----------------- | --------------------------------------- |
| withRPC(app) | 为 App 添加 RPC 功能 |
| createRpcClient | 创建类型安全的 RPC 客户端 |
| RpcError | RPC 调用错误类 |
🌐 生态系统
虽然框架本身轻量,但可通过中间件无限扩展:
// CORS 中间件
function cors(options = {}) {
return ctx => {
if (ctx.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': options.origin || '*',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type,Authorization'
}
})
}
// ctx.next() 是可选的,会自动继续
}
}
// JWT 认证中间件
function jwt(secret: string) {
return ctx => {
const token = ctx.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return ctx.error(401, 'Token required')
}
try {
ctx.user = verifyJWT(token, secret)
// 不需要 ctx.next(),会自动继续
} catch {
return ctx.error(401, 'Invalid token')
}
}
}
// 静态文件服务中间件
function serveStatic(dir: string): RouteHandler {
return async ctx => {
const filePath = join(dir, ctx.url.pathname)
try {
const stats = await stat(filePath)
if (!stats.isFile()) return await ctx.next()
const content = await readFile(filePath)
const ext = filePath.split('.').pop()
return new Response(content, {
headers: {
'Content-Type': getContentType(ext),
'Content-Length': stats.size.toString()
}
})
} catch {
return await ctx.next()
}
}
}
// 使用中间件
app.use(cors({ origin: 'https://example.com' }))
app.use('/api/*', jwt('your-secret'))
app.use('/*', serveStatic(join(__dirname, 'public')))