You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript+Mongoose实现CRUD:请求查询参数校验最佳实践

控制器中校验请求参数并符合Mongoose文档定义的最佳实践

问题描述

我希望在控制器中先校验所有req.query是否符合IGroupDocument的定义,确认无误后再向数据库集合中添加文档。请问实现该需求的最佳实践是什么?

相关代码定义

IGroupDocument 接口

import { Document, Model } from "mongoose";

export interface IGroup {
    firstName: string;
    lastName: string;
    age?: number;
    email: string;
    dateOfEntry?: Date;
}

export interface IGroupDocument extends IGroup, Document {}

原始控制器代码

function create(req: Request, res: Response) {
// req.query validation: if firstName, lastName and email exist and type string, and then make a document from req.query call newGroup.
    GroupModel.create(newGroup)
    res.send(`${req.query.name} created`)
}

最佳实践实现方案

核心思路

采用分层校验的思路:控制器层负责请求参数的格式、类型、必填项校验,Mongoose层负责数据库层面的规则校验(如字段类型、格式约束),同时结合TypeScript的类型系统保障编译时的类型安全,避免运行时类型错误。

方案一:自定义类型守卫 + Mongoose Schema校验

适合小型项目或不想引入额外依赖的场景

1. 定义匹配接口的Mongoose Schema

// group.schema.ts
import mongoose, { Schema } from 'mongoose';
import { IGroupDocument } from './group.interface';

const GroupSchema: Schema = new Schema({
  firstName: { type: String, required: true, trim: true },
  lastName: { type: String, required: true, trim: true },
  age: { type: Number, min: 0 },
  email: { type: String, required: true, trim: true, match: /^\S+@\S+\.\S+$/ },
  dateOfEntry: { type: Date }
});

export const GroupModel = mongoose.model<IGroupDocument>('Group', GroupSchema);

2. 控制器中实现类型守卫和校验

import { Request, Response } from 'express';
import { GroupModel } from './group.schema';
import { IGroup } from './group.interface';

// 自定义类型守卫,验证req.query是否符合IGroup类型
function isGroupQuery(query: any): query is IGroup {
  return (
    typeof query.firstName === 'string' && query.firstName.trim() !== '' &&
    typeof query.lastName === 'string' && query.lastName.trim() !== '' &&
    typeof query.email === 'string' && /^\S+@\S+\.\S+$/.test(query.email) &&
    (query.age === undefined || (!isNaN(Number(query.age)) && Number(query.age) >= 0)) &&
    (query.dateOfEntry === undefined || !isNaN(Date.parse(query.dateOfEntry)))
  );
}

async function create(req: Request, res: Response) {
  try {
    // 第一步:运行时校验请求参数格式
    if (!isGroupQuery(req.query)) {
      return res.status(400).send('参数错误:firstName、lastName为必填非空字符串,email需符合邮箱格式,age为非负数字,dateOfEntry为合法日期格式');
    }

    // 转换参数类型(req.query所有值都是字符串,需转成接口定义的类型)
    const groupData: IGroup = {
      firstName: req.query.firstName.trim(),
      lastName: req.query.lastName.trim(),
      email: req.query.email.trim(),
      ...(req.query.age && { age: Number(req.query.age) }),
      ...(req.query.dateOfEntry && { dateOfEntry: new Date(req.query.dateOfEntry) })
    };

    // 第二步:Mongoose自动校验Schema规则,通过后创建文档
    const newGroup = await GroupModel.create(groupData);
    res.status(201).send(`${newGroup.firstName} ${newGroup.lastName} 创建成功`);
  } catch (err) {
    // 捕获Mongoose校验错误或其他异常
    const errorMsg = err instanceof Error ? err.message : '未知错误';
    res.status(400).send(`创建失败:${errorMsg}`);
  }
}

方案二:使用class-validator + class-transformer(大型项目推荐)

通过数据传输对象(DTO)统一管理校验规则,代码更易维护和扩展

1. 安装依赖

npm install class-validator class-transformer

2. 定义DTO并添加校验规则

// group.dto.ts
import { IsString, IsEmail, IsOptional, IsNumber, Min, IsDateString, Trim } from 'class-validator';

export class CreateGroupDto {
  @IsString()
  @Trim()
  firstName: string;

  @IsString()
  @Trim()
  lastName: string;

  @IsOptional()
  @IsNumber()
  @Min(0)
  age?: number;

  @IsEmail()
  @Trim()
  email: string;

  @IsOptional()
  @IsDateString()
  dateOfEntry?: Date;
}

3. 控制器中使用DTO校验

import { Request, Response } from 'express';
import { GroupModel } from './group.schema';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { CreateGroupDto } from './group.dto';

async function create(req: Request, res: Response) {
  try {
    // 将req.query转换为DTO实例,自动完成类型转换
    const groupDto = plainToInstance(CreateGroupDto, req.query);
    
    // 执行校验
    const errors = await validate(groupDto);
    if (errors.length > 0) {
      // 格式化错误信息返回给前端
      const errorMessages = errors.map(err => Object.values(err.constraints!).join(', ')).join('; ');
      return res.status(400).send(`参数错误:${errorMessages}`);
    }

    // DTO已校验通过,直接传入Mongoose创建文档
    const newGroup = await GroupModel.create(groupDto);
    res.status(201).send(`${newGroup.firstName} ${newGroup.lastName} 创建成功`);
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : '未知错误';
    res.status(400).send(`创建失败:${errorMsg}`);
  }
}

关键注意事项

  • 不要跳过Mongoose校验:即使控制器层做了校验,Mongoose的Schema校验依然能防止非法数据进入数据库(比如直接调用服务层的场景)
  • 处理类型转换:req.query的所有值都是字符串类型,必须手动或通过DTO转换为接口定义的类型(数字、日期等)
  • 返回清晰的错误信息:把校验失败的具体原因返回给前端,方便调试和用户理解

内容的提问来源于stack exchange,提问作者YSH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 04:01:41