Joi
最强大的 JavaScript 对象 Schema 校验库,以描述式 API 和丰富的内置校验规则著称,源自 Hapi.js 生态,由 Sideway Inc 独立维护。
Joi 是 JavaScript 生态中功能最全面的对象 Schema 校验库,提供 150+ 内置校验规则和直观的链式描述式 API,源自 Hapi.js 框架,是 Node.js 后端数据校验的首选方案之一。
Joi
最强大的 JavaScript 对象 Schema 校验库,以描述式 API 和丰富的内置校验规则著称,源自 Hapi.js 生态,由 Sideway Inc 独立维护。
技术简介说明
Joi 是 JavaScript 生态中最成熟、功能最全面的对象 Schema 校验库,由 Eran Hammer(OAuth 1.0 作者、OAuth 2.0 规范原编辑者)于 2012–2014 年间创建,最初作为 Hapi.js 框架的内置校验模块。Joi 的核心设计理念是描述式 API——开发者通过链式调用来描述数据的形状和约束条件,代码读起来几乎如同英语自然语言:Joi.string().email().min(5).required()。这种 API 风格使得校验规则的意图一目了然,极大降低了代码的阅读和维护门槛。
2020 年,Hapi.js 框架宣布停止维护,Joi 随之脱离 Hapi 生态,作为独立项目继续发展(从 @hapi/joi 迁移至 joi)。在 Sideway Inc(Eran Hammer 的公司)的持续维护下,Joi 并未因 Hapi 的消亡而衰落,反而在 Express、Fastify、Nest.js 等主流框架中得到广泛应用。Joi 提供了超过 150 个内置校验方法,覆盖字符串、数字、日期、数组、对象、二进制、替代类型(alternatives)等几乎所有数据类型,是同类库中内置规则最丰富的方案。
进入 2025–2026 年,Joi 已发布至 v18.x 版本,npm 年下载量超过 6.27 亿次(2025 全年),周下载量约 1,910 万次,GitHub star 数达 21,200+。尽管面临 Zod、Valibot 等新一代 TypeScript-first 库的竞争,Joi 凭借其极其丰富的内置规则、稳定的 API 和庞大的存量用户基础,依然是 Node.js 后端数据校验的首选方案之一。
基本信息
- 官网:https://joi.dev
- GitHub:https://github.com/hapijs/joi
- License:BSD-3-Clause
- 最新版本:18.2.3(2026-06-17 发布)
- 主要维护者:Sideway Inc.(Eran Hammer)
- GitHub Stars:~21,200(2026 年)
- npm 周下载量:~1,910 万(2026 年,Snyk 数据)
- npm 年下载量:~6.27 亿(2025 全年)
- 首次发布:2014 年(作为 Hapi.js 内置模块)
- 语言:JavaScript(纯 JS,非 TypeScript 原生)
- 运行环境:Node.js(主要)+ 浏览器(需额外配置)
- 历史:原属 Hapi.js 框架(2012–2020),2020 年独立为单独项目
快速上手
安装
# npm
npm install joi
# yarn
yarn add joi
# pnpm
pnpm add joi注意:Joi 主要面向 Node.js 环境。如需在浏览器端使用,需要额外配置(如
joi-browser或使用 bundler 处理 Node.js 特有依赖)。
基础配置
Joi 是零配置的,安装后直接导入使用:
// CommonJS
const Joi = require('joi')
// ES Module
import Joi from 'joi'最小示例
import Joi from 'joi'
// 定义校验规则
const userSchema = Joi.object({
name: Joi.string().min(2).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150),
role: Joi.string().valid('admin', 'user', 'guest').default('user'),
})
// 校验数据
const { error, value } = userSchema.validate({
name: '张三',
email: '[email protected]',
age: 28,
})
if (error) {
console.log('校验失败:', error.details)
// [{ message: '...', path: [...], type: '...', context: {...} }]
} else {
console.log('校验通过:', value)
// { name: '张三', email: '[email protected]', age: 28, role: 'user' }
}核心概念与架构
描述式 API
Joi 采用链式描述式 API,每个类型(string、number、date 等)都有一系列方法用于描述约束条件:
const schema = Joi.object({
// 链式描述:字符串 → 最小长度 3 → 最大长度 50 → 正则匹配
username: Joi.string().min(3).max(50).pattern(/^[a-zA-Z0-9_]+$/),
// 描述数值范围
price: Joi.number().precision(2).min(0).max(99999.99),
// 描述日期约束
birthDate: Joi.date().max('now'),
// 描述数组元素
tags: Joi.array().items(Joi.string()).min(1).max(10).unique(),
})校验模式
Joi 提供多种校验 API,适应不同场景:
// 1. validate() — 返回 { error, value },默认同步
const { error, value } = schema.validate(data)
// 2. validateAsync() — 异步版本,支持异步校验规则
const value = await schema.validateAsync(data)
// 3. attempt() — 校验失败时直接抛出异常,成功时返回转换后的值
const value = schema.attempt(data)
// 4. isValid() — 仅返回布尔值,不返回转换后的数据
const isValid = schema.isValid(data)类型系统
Joi 的类型系统层次清晰:
any
├── string
│ ├── guid
│ ├── email
│ ├── uri
│ ├── ip
│ └── ...
├── number
│ ├── integer
│ ├── precision
│ └── ...
├── boolean
├── date
│ ├── min
│ ├── max
│ └── timestamp
├── array
│ ├── items
│ ├── unique
│ └── ordered
├── object
│ ├── keys
│ ├── pattern
│ ├── and/or/xor/nand
│ └── ...
├── binary
├── alternatives
│ ├── try
│ ├── conditional
│ └── match
├── link(Schema 引用)
└── function(已弃用,推荐 any.function())
核心特性
- 150+ 内置校验规则:覆盖字符串、数字、日期、数组、对象、二进制等几乎所有数据类型,同类库中内置规则最丰富
- 描述式链式 API:
Joi.string().email().required()风格直观易读,代码即文档 - 类型转换(Coercion):自动将字符串
"123"转换为数字123,字符串"true"转换为布尔值true等 - 默认值填充:通过
.default()为缺失字段填充默认值 - 条件校验:支持基于其他字段值的动态校验规则(
when().then().otherwise()) - Schema 组合与引用:通过
Joi.link('#schemaId')实现 Schema 自引用和交叉引用(如树形结构校验) - 对象关系校验:
.and()、.or()、.xor()、.nand()等方法描述字段间的逻辑关系 - 自定义错误消息:每条规则都支持自定义错误消息,支持国际化
- 异步校验:
validateAsync()支持异步自定义校验规则(如数据库查询验证唯一性) - 浏览器端支持:虽然主要面向 Node.js,但通过
joi-browser或适当的 bundler 配置可在浏览器中使用 - 替代类型(Alternatives):通过
Joi.alternatives()描述同一字段可能的多种类型(联合类型) - Schema 扩展:通过
Joi.extend()创建自定义类型和校验规则
生态图
官方生态
| 包名 | 说明 |
|---|---|
joi | 核心校验库 |
@hapi/joi-date | 高级日期格式扩展(支持自定义日期格式解析) |
joi.dev | 官方文档站 |
| 在线 Playground:https://joi.dev/tester/ | 在线测试 Schema 和校验结果 |
框架集成
| 包名 | 说明 |
|---|---|
celebrate | Express.js 的 Joi 中间件(校验 req.body、req.query、req.params) |
express-joi-validation | 轻量级 Express Joi 校验中间件 |
@hapi/joi → @fastify/joi | Fastify 框架的 Joi 适配器 |
joi-to-json | Joi Schema 与 JSON Schema 互转工具 |
joi-to-typescript | 从 Joi Schema 生成 TypeScript 类型定义 |
@nestjs/joi | Nest.js 集成(通过 Joi 进行 DTO 校验) |
社区工具
| 包名 | 说明 |
|---|---|
joi-i18n | 错误消息国际化 |
joi-password | 密码强度校验扩展 |
joi-phone-number | 电话号码格式校验 |
joi-otp | OTP 验证码校验 |
react-joi-validation | React 表单校验集成 |
joi-extract-type | 从 Joi Schema 提取 TypeScript 类型 |
知名下游项目
- Fastify:内置支持 Joi 作为请求/响应校验器
- Strapi:CMS 管理面板的数据校验
- KeystoneJS:数据模型校验
- Nest.js:通过
@nestjs/joi集成 - LoopBack:IBM 的 Node.js 框架,支持 Joi
适用场景
- Node.js API 请求校验(Express/Fastify/Koa 等框架中校验 req.body、req.query、req.params)
- 环境变量校验(使用
Joi.object()校验.env配置,确保启动时所有必要的环境变量都存在且格式正确) - 表单数据校验(后端接收的表单数据二次校验,防止前端绕过限制)
- 配置文件校验(校验 YAML/JSON 配置文件的结构和类型)
- 数据模型校验(在 ORM 层之前或之后进行数据格式验证)
- CLI 工具参数校验(校验命令行参数的格式和约束)
- 文档生成:通过
joi-to-json将 Joi Schema 转换为 JSON Schema,进而生成 OpenAPI 文档
开发与工程化
Schema 组织最佳实践
src/
├── validators/
│ ├── user.validator.js
│ │ └── 定义 UserSchema, CreateUserSchema, UpdateUserSchema
│ ├── order.validator.js
│ │ └── 定义 OrderSchema, CreateOrderSchema
│ └── common/
│ ├── pagination.validator.js
│ │ └── 分页参数 Schema(page, pageSize, sort, order)
│ └── response.validator.js
│ └── 通用响应包装 Schema
├── middleware/
│ └── validate.js
│ └── Express/Fastify 校验中间件
Express 集成示例
import express from 'express'
import Joi from 'joi'
const app = express()
app.use(express.json())
// 通用校验中间件
const validate = (schema, property = 'body') => {
return (req, res, next) => {
const { error, value } = schema.validate(req[property], {
abortEarly: false, // 返回所有错误
stripUnknown: true, // 移除未定义的字段
})
if (error) {
return res.status(400).json({
message: '请求参数校验失败',
details: error.details.map(d => ({
field: d.path.join('.'),
message: d.message,
})),
})
}
req[property] = value // 使用转换/清洗后的数据
next()
}
}
// 使用
const createUserSchema = Joi.object({
name: Joi.string().min(2).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).optional(),
})
app.post('/api/users', validate(createUserSchema), (req, res) => {
res.json({ message: '创建成功', data: req.body })
})环境变量校验
import Joi from 'joi'
import dotenv from 'dotenv'
dotenv.config()
const envSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').required(),
PORT: Joi.number().port().default(3000),
DATABASE_URL: Joi.string().uri().required(),
REDIS_URL: Joi.string().uri().optional(),
JWT_SECRET: Joi.string().min(32).required(),
SMTP_HOST: Joi.string().hostname().optional(),
}).unknown() // 允许其他未定义的 env 变量
const { error, value: env } = envSchema.validate(process.env)
if (error) {
console.error('环境变量校验失败:', error.message)
process.exit(1)
}
export default env性能与安全
性能特点
| 场景 | Joi 表现 |
|---|---|
| 简单对象校验 | 良好(非最快,但足够实用) |
| 复杂嵌套对象 | 良好 |
| 大量规则链式组合 | 中等(规则越多开销越大) |
| 与 Ajv 对比 | 慢 5–20 倍(Ajv 代码生成优势) |
| 与 Zod 对比 | 相近(两者性能在同一数量级) |
安全注意事项
- 输入清洗(Strip Unknown):生产环境建议开启
stripUnknown: true,自动移除 Schema 未定义的字段,防止意外数据注入 - 避免原型污染:Joi 内部对原型污染有防护,但仍建议配合
Object.create(null)或Map使用 - 正则表达式 ReDoS:使用
.pattern()自定义正则时,注意避免灾难性回溯,可使用safe-regex库预先检查 validate()vsvalidateAsync():两者在处理大量异步校验规则时行为不同,validate()同步模式下不支持异步规则,混用可能导致意外行为- 版本迁移注意:从
@hapi/joi迁移到joi时,部分 API 有变化(如Joi.validate()已弃用,改用schema.validate())
性能优化建议
- 在 Schema 中合理使用
stripUnknown: true减少后续数据处理量 - 对于频繁调用的校验,考虑缓存编译后的 Schema(Joi 内部会缓存部分校验逻辑)
- 避免在
.pattern()中使用复杂正则,优先使用 Joi 内置的.email()、.uri()等方法 - 批量校验时,使用
validateAsync()异步并发处理多个校验任务
技术对比
| 特性 | Joi | Ajv | Zod | Yup | Valibot |
|---|---|---|---|---|---|
| 设计哲学 | 描述式链式 API | JSON Schema 标准实现 | TypeScript-first | 链式 API(表单友好) | 函数式 pipeline |
| 内置规则数量 | 150+ | 取决于 JSON Schema 版本 | 50+ | 50+ | 模块化按需 |
| 校验速度 | 中等 | 极快(代码生成) | 中等 | 中等 | 快 |
| TypeScript 类型推导 | 无原生支持 | 有限 | 原生完整支持 | 有限 | 原生支持 |
| 包体积 | 较大(~50 KB) | 中等(~60 KB) | 中等(~12–15 KB) | 中等(~18–30 KB) | 极小(~1–3 KB) |
| 浏览器支持 | 需额外配置 | 完整支持 | 完整支持 | 完整支持 | 完整支持 |
| 异步校验 | 原生支持 | 自定义关键字支持 | 通过 refine() | 支持 | 通过 pipe |
| 条件校验 | 强大(when/then/otherwise) | JSON Schema if/then/else | 通过 refine/discriminatedUnion | 支持 | 通过条件函数 |
| 学习曲线 | 低(描述式 API) | 高(需学 JSON Schema) | 低 | 低 | 中等 |
| 生态成熟度 | 极其成熟 | 极其成熟 | 快速增长 | 成熟(维护趋缓) | 新兴 |
| 适用场景 | Node.js API 校验 | API 校验、配置、数据管道 | TypeScript 全栈 | 前端表单 | 边缘函数、极致包体积 |
何时选 Joi
- 需要大量丰富的内置校验规则(电话号码、IP 地址、信用卡号等)
- Node.js 后端 API 校验(与 Express、Fastify 等框架集成成熟)
- 团队更习惯描述式 API 而非 JSON Schema 或 TypeScript-first 方案
- 需要强大的条件校验(
when/then/otherwise)能力 - 项目已有 Joi 技术栈积累,迁移成本高
何时不选 Joi
- 需要 TypeScript 类型推导(选 Zod)
- 包体积极度敏感(选 Valibot)
- 需要标准 JSON Schema 兼容(选 Ajv)
- 需要浏览器端原生支持而不想额外配置(选 Zod、Valibot、Yup)
最佳实践
1. 复用 Schema 定义
// ✅ 正确:抽离公共字段
const baseUserFields = {
name: Joi.string().min(2).max(30).required(),
email: Joi.string().email().required(),
}
const createUserSchema = Joi.object({
...baseUserFields,
password: Joi.string().min(8).required(),
})
const updateUserSchema = Joi.object({
...baseUserFields,
password: Joi.string().min(8).optional(),
}).partial() // 所有字段变为可选2. 使用 abortEarly: false 收集所有错误
const { error } = schema.validate(data, { abortEarly: false })
// 一次校验返回所有字段的错误,避免用户逐一修正3. 条件校验的清晰表达
const paymentSchema = Joi.object({
type: Joi.string().valid('credit_card', 'paypal').required(),
cardNumber: Joi.string()
.when('type', {
is: 'credit_card',
then: Joi.string().creditCard().required(),
otherwise: Joi.forbidden(),
}),
paypalEmail: Joi.string()
.when('type', {
is: 'paypal',
then: Joi.string().email().required(),
otherwise: Joi.forbidden(),
}),
})4. 自定义错误消息
const schema = Joi.object({
password: Joi.string()
.min(8).message('密码至少 8 个字符')
.pattern(/[A-Z]/).message('密码必须包含至少一个大写字母')
.pattern(/[0-9]/).message('密码必须包含至少一个数字'),
})5. 环境变量校验放在启动阶段
在应用启动时第一时间校验所有环境变量,快速失败(fail-fast),避免运行时出现难以定位的配置错误。
6. 校验中间件统一封装
将校验逻辑封装为通用中间件,避免在每个路由处理器中重复编写校验代码。
技术局限与边界
- 无原生 TypeScript 类型推导:与 Zod 的核心差距——Joi Schema 无法自动推导出 TypeScript 类型,需借助
joi-to-typescript等第三方工具,且推导精度有限 - 包体积较大:~50 KB(gzip 后),不适合前端 bundle 或边缘函数场景
- 浏览器支持非一等公民:主要面向 Node.js 设计,浏览器端使用需额外处理 Node.js 特有 API(如
Buffer) - 纯 JavaScript 编写:非 TypeScript 原生,类型定义来自
@types/joi(社区维护),偶有类型定义与实际行为不一致 - 校验速度中等:相比 Ajv 的代码生成方案慢 5–20 倍,在高频校验场景中可能成为瓶颈
- 不支持 JSON Schema 标准:Joi Schema 是自有格式,无法直接与 OpenAPI/Swagger 等标准互通(需借助
joi-to-json转换) - 维护节奏稳定但保守:相比 Zod 的活跃迭代,Joi 的更新频率较低,新特性引入谨慎
- 链式 API 的调试体验:长链式调用在出错时,堆栈信息可能不够直观
学习资源
- 官方文档:https://joi.dev(最权威的参考)
- API 参考:https://joi.dev/api/18.x.x(最新版 API 文档)
- 在线 Playground:https://joi.dev/tester/(实时测试 Schema)
- GitHub 仓库:https://github.com/hapijs/joi
- DevDocs 镜像:https://devdocs.io/joi/(离线查阅)
- Medium 完整指南:https://medium.com/@artemkhrenov/the-complete-guide-to-joi-validation-in-production-node-js-applications
- GeeksforGeeks 教程:https://www.geeksforgeeks.org/node-js/npm-joi/
- YouTube 教程:搜索 "Joi validation Node.js" 可找到多个视频教程
- Stack Overflow:搜索
joi标签可找到大量问答(28,000+ 问题)
2026 年现状
最新版本
- v18.2.3(2026-06-17 发布):持续维护中,包含 bug 修复和稳定性改进
- 主要版本演进:v17.x(2020–2024)→ v18.x(2024–至今)
发展趋势
- 独立于 Hapi 稳定发展:虽然源自 Hapi.js,Joi 已完全独立,与 Express、Fastify、Nest.js 等主流框架均有成熟的集成方案
- 面临 TypeScript-first 库竞争:Zod 凭借原生类型推导能力在新项目中越来越受欢迎,对 Joi 的增长形成压力
- 存量市场稳固:Joi 在已有项目中仍有大量使用,迁移成本使得存量用户短期内不会流失
- 功能优势依然明显:150+ 内置规则是同类库中最丰富的,在需要复杂校验规则的场景中仍有独特优势
社区活跃度
- npm 周下载量:约 1,910 万次(2026 年)
- GitHub Stars:约 21,200
- GitHub Forks:约 1,500
- 版本发布节奏:Snyk 报告"在过去 3 个月内至少有一个新版本发布",维护节奏稳定
- Issue 响应:活跃维护,GitHub 上最近 14 天内仍有代码提交
未来展望
- Joi 将继续作为 Node.js 后端校验的重要选择,特别是在非 TypeScript 项目中
- 随着 TypeScript 在前端和全栈项目中的普及,Zod 等 TypeScript-first 库可能在新增项目份额上超过 Joi
- Joi 的核心竞争力在于其无与伦比的内置规则丰富度和描述式 API 的直觉性——这些优势在复杂校验场景中难以被替代
- 社区对
joi-to-typescript等桥接工具的需求将持续存在,以弥补类型推导的缺失