Node.js中Joi的详细用法

2025年6月8日 Mr 焦 199

Joi 在 Node.js 中的详细用法

Joi 是一个强大的 JavaScript 对象模式验证库,常用于 Node.js 应用中验证和转换数据。下面详细介绍 Joi 的用法。

安装

npm install joi

基本用法

1. 基本验证

const Joi = require('joi');

// 定义 schema
const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
  repeat_password: Joi.ref('password'),
  email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
}).with('password', 'repeat_password');

// 验证数据
const { error, value } = schema.validate({
  username: 'abc',
  password: 'password123',
  repeat_password: 'password123',
  email: 'abc@example.com'
});

if (error) {
  console.error(error.details);
} else {
  console.log('验证通过', value);
}

2. 数据类型验证

字符串验证

Joi.string()
  .alphanum()    // 只允许字母数字字符
  .min(3)        // 最小长度3
  .max(30)       // 最大长度30
  .required()    // 必填字段
  .trim()        // 自动去除前后空格
  .lowercase()   // 转换为小写
  .uppercase()   // 转换为大写
  .regex(/^[a-z]+$/) // 正则匹配

数字验证

Joi.number()
  .integer()     // 必须是整数
  .min(1)        // 最小值1
  .max(10)       // 最大值10
  .precision(2)  // 小数点后2位
  .positive()    // 必须是正数
  .negative()    // 必须是负数

布尔值验证

Joi.boolean()
  .truthy('yes') // 将'yes'视为true
  .falsy('no')   // 将'no'视为false

日期验证

Joi.date()
  .min('1-1-2000')  // 最小日期
  .max('now')       // 最大日期为现在
  .iso()            // ISO格式

数组验证

Joi.array()
  .items(Joi.string(), Joi.number()) // 数组元素可以是字符串或数字
  .length(5)       // 数组长度必须为5
  .min(1)          // 最小长度1
  .max(10)         // 最大长度10
  .unique()        // 元素必须唯一

对象验证

Joi.object({
  name: Joi.string(),
  age: Joi.number()
})
  .and('name', 'age')  // name和age必须同时存在
  .or('name', 'age')   // name或age必须存在一个
  .xor('name', 'age')  // name或age必须存在一个但不能同时存在
  .pattern(/^a/, Joi.string()) // 所有以a开头的属性必须是字符串

3. 高级验证

条件验证

const schema = Joi.object({
  isAdmin: Joi.boolean(),
  accessLevel: Joi.when('isAdmin', {
    is: true,
    then: Joi.number().valid(1, 2, 3),
    otherwise: Joi.number().valid(4, 5)
  })
});

自定义验证

const schema = Joi.object({
  password: Joi.string().custom((value, helpers) => {
    if (value.length < 8) {
      return helpers.error('password.too.short');
    }
    if (!/[A-Z]/.test(value)) {
      return helpers.error('password.no.uppercase');
    }
    return value;
  }, '密码验证')
}).messages({
  'password.too.short': '密码长度至少8个字符',
  'password.no.uppercase': '密码必须包含至少一个大写字母'
});

引用其他字段值

const schema = Joi.object({
  password: Joi.string().required(),
  confirmPassword: Joi.string().valid(Joi.ref('password')).required()
});

4. 错误处理

const { error, value } = schema.validate(data, {
  abortEarly: false, // 不遇到第一个错误就停止,收集所有错误
  allowUnknown: true, // 允许未知键
  stripUnknown: true, // 移除未知键
  convert: true      // 尝试类型转换
});

if (error) {
  // 错误详情
  error.details.forEach((detail) => {
    console.log(detail.message);
    console.log(detail.path); // 错误路径
    console.log(detail.type); // 错误类型
  });

  // 自定义错误消息
  const errors = error.details.map(detail => ({
    field: detail.path.join('.'),
    message: detail.message
  }));
}

5. 扩展和自定义

创建自定义验证规则

const customJoi = Joi.extend((joi) => {
  return {
    type: 'string',
    base: joi.string(),
    messages: {
      'string.oddLength': '{{#label}} 长度必须是奇数',
    },
    validate(value, helpers) {
      if (value.length % 2 === 0) {
        return { value, errors: helpers.error('string.oddLength') };
      }
    }
  };
});

const schema = customJoi.string().oddLength();

6. 实用方法

默认值

Joi.string().default('default value')

可选/必填

Joi.string().optional()
Joi.string().required()

允许/禁止值

Joi.string().valid('value1', 'value2') // 只允许特定值
Joi.string().invalid('value1', 'value2') // 禁止特定值

标签和描述

Joi.string().label('用户名').description('用户的登录名')

7. 数组和对象的高级用法

数组元素验证

Joi.array().items(
  Joi.object({
    id: Joi.number().required(),
    name: Joi.string().required()
  })
)

对象模式匹配

Joi.object({
  a: Joi.string(),
  b: Joi.number()
}).pattern(Joi.string().pattern(/^x/), Joi.boolean())

8. 异步验证

const schema = Joi.object({
  username: Joi.string().external(async (value, helpers) => {
    const exists = await checkUsernameExists(value);
    if (exists) {
      return helpers.error('username.exists');
    }
    return value;
  })
});

try {
  const value = await schema.validateAsync({ username: 'test' });
} catch (err) {
  console.error(err);
}

实际应用示例

用户注册验证

分页查询参数验证

const userSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
    .label('用户名')
    .messages({
      'string.empty': '用户名不能为空',
      'string.min': '用户名长度不能少于3个字符',
      'string.max': '用户名长度不能超过30个字符'
    }),

  password: Joi.string()
    .pattern(new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})'))
    .required()
    .label('密码')
    .messages({
      'string.pattern.base': '密码必须包含大小写字母、数字和特殊字符,且长度至少8位'
    }),

  email: Joi.string()
    .email({ minDomainSegments: 2 })
    .required()
    .label('电子邮箱'),

  birth_year: Joi.number()
    .integer()
    .min(1900)
    .max(2023)
    .label('出生年份'),

  roles: Joi.array()
    .items(Joi.string().valid('user', 'admin', 'editor'))
    .default(['user'])
    .label('角色'),

  metadata: Joi.object()
    .unknown()
    .label('元数据'),

  createdAt: Joi.date()
    .default(Date.now)
    .label('创建时间')
}).options({ abortEarly: false });
const paginationSchema = Joi.object({
  page: Joi.number()
    .integer()
    .min(1)
    .default(1)
    .label('页码'),

  pageSize: Joi.number()
    .integer()
    .min(1)
    .max(100)
    .default(10)
    .label('每页数量'),

  sort: Joi.string()
    .valid('asc', 'desc')
    .default('desc')
    .label('排序方式'),

  search: Joi.string()
    .trim()
    .max(100)
    .allow('')
    .label('搜索关键词')
});

最佳实践

  1. 复用验证模式:将常用的验证模式提取为可复用的组件
  2. 详细错误消息:提供有意义的错误消息
  3. 尽早验证:在请求处理流程的早期进行验证
  4. 区分开发和生产:在生产环境中可能不需要返回详细的验证错误
  5. 结合Express中间件:创建验证中间件
function validate(schema, property = 'body') {
  return (req, res, next) => {
    const { error, value } = schema.validate(req[property], {
      abortEarly: false,
      allowUnknown: false
    });

    if (error) {
      const errors = error.details.map(detail => ({
        field: detail.path.join('.'),
        message: detail.message
      }));
      return res.status(400).json({ errors });
    }

    req[property] = value;
    next();
  };
}

// 使用示例
router.post('/users', validate(userSchema), userController.create);

Joi 提供了强大而灵活的验证功能,可以帮助你确保应用程序数据的完整性和一致性。

分类:
标签:
版权属于Mr 焦
本文链接:https://www.mtsws.cn/post-16.html
评论
暂无评论数据
相关推荐
react-native-reanimated用法清单
以下是 react-native-reanimated 的用法清单和核心文档说明,涵盖主要功能、API 和使用示例: 一、核心概念 工作线程:动画在 UI 线程执行(非 JS 线程),避免卡顿 共享值(Shared Values):动画的驱动数据(代替 Animated.Value) 动画修饰器:定义动画行为(如 withTiming, withSpring...
Mr 焦 2952025年5月29日
浏览器架构
浏览器架构 计算机核心元素 为了了解浏览器运行的环境,我们需要了解几个计算机部件以及它们的作用。 CPU 第一个需要了解的计算机部件是 中央处理器(Central Processing Unit),或简称为 CPU。CPU 可以看作是计算机的大脑。一个 CPU 核心如图中的办公人员,可以逐一解决很多不同任务。它可以在解决从数学到艺术一切任务的同时还知道如何响...
Mr 焦 2432025年5月27日
Expo Router的核心用法汇总
以下是 Expo Router 的核心用法汇总,涵盖路由配置、导航、参数传递等关键功能(基于 Expo SDK 49+): 🗺️ 路由配置(文件系统路由) app/ ├── (tabs)/ # 嵌套布局组 │ ├── index.js # /tabs/ │ └── [user].js # /tabs/{user} ├─...
Mr 焦 2452025年5月30日 react native
Linux 系统-基本概要
基本概要 抽象级别和层次 最底层是硬件系统,包括内存和中央处理器(用于计算和从内存中读写数据),此外硬盘和网络接口也是硬件系统的一部分。 硬件系统之上是 内核,它是操作系统的核心。内核是运行在内存中的软件,它向中央处理器发送指令。内核管理硬件系统,是硬件系统和应用程序之间进行通信的接口。 进程 是指计算机中运行的所有程序,由内核统一管理,它们组成了最顶层,称...
Mr 焦 1272025年5月30日 Linux
react native 语音转文字
在 React Native 中实现语音转文字(Speech-to-Text)功能,可以通过以下几种方式来实现。以下是详细的实现步骤和说明: 1. 使用 React Native 社区库 React Native 社区提供了许多开源库来处理语音识别功能,其中最常用的是 react-native-voice 和 @react-native-community/...
Mr 焦 1422025年5月30日 react native