TypeScript 运行时验证:Zod 在 API 和表单中的实战应用

TypeScript 只在编译时有类型检查,运行时数据仍需验证。详解 Zod 在 API 请求验证、表单校验、环境变量类型安全、Prisma 数据校验中的完整用法。

TypeScript 的类型在编译后完全消失,运行时 incoming 数据(API 请求、表单输入、环境变量)可能是任意值。Zod 是在 TypeScript 生态中最流行的运行时类型验证库——它与 TS 类型系统天然配合,用声明式的方式定义数据schema,并自动推断出对应的 TS 类型。


一、Zod 基础

npm install zod
import { z } from 'zod';

// 定义 schema
const UserSchema = z.object({
  id: z.number(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).optional(),
  role: z.enum(['user', 'admin']),
});

// 自动推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;
// 等同于:
// type User = { id: number; name: string; email: string; age?: number; role: 'user' | 'admin' }

// 运行时验证
const result = UserSchema.safeParse(unknownData);
if (!result.success) {
  console.log(result.error.issues); // 详细的验证错误
} else {
  const user: User = result.data;
}

二、API 请求验证

// Express 中间件
import { Request, Response, NextFunction } from 'express';
import { z, ZodError } from 'zod';

const CreatePostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(1),
  tags: z.array(z.string()).max(10),
});

function validateBody<T extends z.ZodType>(schema: T) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({
        error: 'Validation failed',
        details: result.error.issues,
      });
    }
    req.body = result.data;
    next();
  };
}

app.post('/posts', validateBody(CreatePostSchema), (req, res) => {
  // req.body 已验证为 CreatePost 类型
  const { title, content, tags } = req.body;
  // ...
});

三、环境变量类型安全

// env.ts
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  PORT: z.string().transform(Number).default('3000'),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  REDIS_URL: z.string().url().optional(),
});

export const env = envSchema.parse(process.env);
// 如果缺少必需变量或格式错误,启动时直接抛出清晰的错误

四、React Hook Form + Zod

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('请输入有效的邮箱'),
  password: z.string().min(8, '密码至少8位'),
});

type FormData = z.infer<typeof schema>;

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
      <input type="password" {...register('password')} />
      <button type="submit">登录</button>
    </form>
  );
}

相关阅读

下一篇 →

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章