Joi logo

Joi

最强大的 JavaScript 对象 Schema 校验库,以描述式 API 和丰富的内置校验规则著称,源自 Hapi.js 生态,由 Sideway Inc 独立维护。

表单校验Schema验证Node.js描述式 API

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
  • GitHubhttps://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+ 内置校验规则:覆盖字符串、数字、日期、数组、对象、二进制等几乎所有数据类型,同类库中内置规则最丰富
  • 描述式链式 APIJoi.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官方文档站
在线 Playgroundhttps://joi.dev/tester/在线测试 Schema 和校验结果

框架集成

包名说明
celebrateExpress.js 的 Joi 中间件(校验 req.body、req.query、req.params)
express-joi-validation轻量级 Express Joi 校验中间件
@hapi/joi@fastify/joiFastify 框架的 Joi 适配器
joi-to-jsonJoi Schema 与 JSON Schema 互转工具
joi-to-typescript从 Joi Schema 生成 TypeScript 类型定义
@nestjs/joiNest.js 集成(通过 Joi 进行 DTO 校验)

社区工具

包名说明
joi-i18n错误消息国际化
joi-password密码强度校验扩展
joi-phone-number电话号码格式校验
joi-otpOTP 验证码校验
react-joi-validationReact 表单校验集成
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() vs validateAsync():两者在处理大量异步校验规则时行为不同,validate() 同步模式下不支持异步规则,混用可能导致意外行为
  • 版本迁移注意:从 @hapi/joi 迁移到 joi 时,部分 API 有变化(如 Joi.validate() 已弃用,改用 schema.validate()

性能优化建议

  • 在 Schema 中合理使用 stripUnknown: true 减少后续数据处理量
  • 对于频繁调用的校验,考虑缓存编译后的 Schema(Joi 内部会缓存部分校验逻辑)
  • 避免在 .pattern() 中使用复杂正则,优先使用 Joi 内置的 .email().uri() 等方法
  • 批量校验时,使用 validateAsync() 异步并发处理多个校验任务

技术对比

特性JoiAjvZodYupValibot
设计哲学描述式链式 APIJSON 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 的调试体验:长链式调用在出错时,堆栈信息可能不够直观

学习资源

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 等桥接工具的需求将持续存在,以弥补类型推导的缺失