使用 itty-spec 构建类型安全 API:契约优先方法
Source: Dev.to
Introduction
itty-spec 是一个强大的库,它为 itty‑router 带来了类型安全、合同优先的 API 开发方式。通过使用标准模式库来定义 API 合同,你可以获得:
- 自动验证
- 完整的 TypeScript 类型推断
- 无缝的 OpenAPI 文档生成
- 与边缘计算环境的兼容性
传统的 API 开发常常面临以下问题:
- 手动验证散落在各个路由处理函数中
- 类型定义与实际运行时行为脱节
- 对无效请求的错误处理不一致
- 文档过时,需要手动维护
- 运行时无法保证处理函数与其文档化的合同匹配
这些问题会导致 bug、安全漏洞以及糟糕的开发者体验。itty-spec 通过将模式定义作为路由、验证、类型和文档的唯一真实来源来解决这些问题。
Contract‑First Approach
itty-spec 遵循合同优先的工作流:
- 路由注册 – 自动根据合同定义创建路由
- 请求验证 – 对所有进入的数据进行模式验证
- 类型推断 – 为处理函数提供完整的 TypeScript 类型
- 响应验证 – 确保处理函数返回的响应符合合同
- 文档生成 – 自动生成 OpenAPI 规范
Typed Request Objects
当你定义合同后,TypeScript 会自动推断以下类型:
- 路径参数 – 从类似
/users/:id的模式中提取 - 查询参数 – 类型化并验证的查询字符串
- 请求头 – 已验证的头对象
- 请求体 – 类型化的请求负载
import { createContract } from 'itty-spec';
import { z } from 'zod';
const contract = createContract({
getUser: {
path: '/users/:id',
query: z.object({
include: z.enum(['posts', 'comments']).optional(),
}),
headers: z.object({
'x-api-key': z.string(),
}),
responses: {
200: { body: z.object({ id: z.string(), name: z.string() }) },
},
},
});
// 在你的处理函数中,一切都是完整类型化的!
const router = createRouter({
contract,
handlers: {
getUser: async (request) => {
// request.params.id 被推断为 string
// request.query.include 被推断为 'posts' | 'comments' | undefined
// request.validatedHeaders['x-api-key'] 被推断为 string
const userId = request.params.id; // string
const include = request.query.include; // enum | undefined
return request.json({ id: userId, name: 'John' });
},
},
});
类型系统可以防止意外访问不存在的属性,并为所有可用字段提供自动补全。
Validation Pipeline
itty-spec 使用中间件管道,在请求到达处理函数 之前 完成验证。验证顺序如下:
- 路径参数 – 提取并根据可选的
pathParams模式进行验证 - 查询参数 – 解析并验证
- 请求头 – 规范化并验证
- 请求体 – 从 JSON 解析并验证(针对 POST/PUT/PATCH)
如果任何一步失败,请求会以 400 状态码被拒绝,并返回详细的错误信息,永远不会进入你的处理函数代码。
const contract = createContract({
createUser: {
path: '/users',
method: 'POST',
request: z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(18).max(120),
}),
responses: {
200: { body: z.object({ id: z.string(), name: z.string() }) },
400: { body: z.object({ error: z.string(), details: z.any() }) },
},
},
});
当请求体与模式不匹配时,验证会自动失败,使你的处理函数能够专注于业务逻辑。
Vendor‑Agnostic Schema Support
itty-spec 基于 Standard Schema V1 规范,允许你使用任何实现该规范的模式库:
- Zod (v4) – 完全支持并可生成 OpenAPI
- Valibot – 支持验证(计划支持 OpenAPI)
- ArkType – 支持验证(计划支持 OpenAPI)
- 任何其他实现 Standard Schema V1 的库
该规范定义了统一接口 (StandardSchemaV1) 包含:
~standard.vendor– 标识使用的模式库~standard.validate()– 标准化的验证方法- 通过 TypeScript 实现的类型推断能力
// 与 Zod 配合使用
import { z } from 'zod';
const zodSchema = z.object({ name: z.string() });
// 与 Valibot 配合使用(待支持时)
import * as v from 'valibot';
const valibotSchema = v.object({ name: v.string() });
// 两者都可以在合同中使用
const contract = createContract({
endpoint: {
path: '/test',
request: zodSchema, // 或者 valibotSchema
responses: { 200: { body: zodSchema } },
},
});
这种方式避免了对特定库的锁定,并简化了迁移过程。
Automatic OpenAPI 3.1 Generation
itty-spec 能够直接从合同生成完整的 OpenAPI 3.1 规范,省去手动维护文档的麻烦。
OpenAPI 生成器的关键特性
- 从所有合同操作中提取模式
- 将
:param路径语法转换为{param}(OpenAPI 标准) - 将响应模式映射为 OpenAPI 响应对象
- 包含头部、查询参数和请求体
- 通过注册表系统对模式进行去重
import { createOpenApiSpecification } from 'itty-spec/openapi';
import { contract } from './contract';
const openApiSpec = createOpenApiSpecification(contract, {
title: 'User Management API',
version: '1.0.0',
description: 'A comprehensive API for managing users',
servers: [
{ url: 'https://api.example.com', description: 'Production' },
{ url: 'https://staging-api.example.com', description: 'Staging' },
],
});
生成的规范可用于:
- 交互式文档(Swagger UI、Stoplight Elements)
- 客户端 SDK 生成(openapi-generator、swagger-codegen)
- API 测试工具(Postman、Insomnia)
- CI/CD 验证
输出完全符合 OpenAPI 3.1,并可在任何支持的工具中使用。
Typed Response Helpers
itty-spec 在请求对象上添加了类型化的帮助方法,以确保响应符合合同定义:
request.json(body, status?, headers?)– 带类型验证的 JSON 响应request.html(html, status?, headers?)– HTML 响应request.error(status, body, headers?)– 错误响应request.noContent(status)– 204 No Content 响应
这些帮助方法强制:
- 只能使用合同中定义的有效状态码
- 响应体必须匹配对应状态码的模式
- 头部必须符合任何可选的头部模式
const contract = createContract({
getUser: {
path: '/users/:id',
responses: {
200: { body: z.object({ id: z.string(), name: z.string() }) },
404: { body: z.object({ error: z.string() }) },
},
},
});
const router = createRouter({
contract,
handlers: {
getUser: async (request) => {
const user = await findUser(request.params.id);
if (!user) {
// TypeScript 确保这里匹配 404 响应模式
return request.error(404, { error: 'User not found' });
}
return request.json({ id: user.id, name: user.name });
},
},
});
Conclusion
通过将模式设为唯一真实来源,itty-spec 带来了:
- 全生命周期的类型安全
- 自动验证,防止恶意或错误的输入
- 零维护文档,通过 OpenAPI 自动生成
- 灵活性,可选择任意兼容 Standard Schema V1 的库
采用 itty-spec 能简化 API 开发,降低 bug 率,并提升边缘环境下的开发者体验。