3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NestJSでzodを使う!基本的な使い方を整理してみた。

Last updated at Posted at 2025-06-13

はじめに

TypeScriptでのフルスタック開発ではzodを使うことがデファクトっぽい。(?)
初めてzodを使ったので、備忘録として基本的な使い方をまとめてみました。

目次

  • スキーマと型の一元管理
  • DTOの自動生成
  • バリデーションの実装
  • PrismaなどのORMとの連携

1. 用語 スキーマと型の違い

用語 役割・目的 使われるタイミング
スキーマ データ構造とバリデーションルール定義 実行時(バリデーション)
データの型安全性を保証 開発時(型チェック)
  • スキーマはZodなどで定義し、実行時にデータの検証や変換を行います。
  • はTypeScriptの型システムで、開発時の型安全性を担保します。

import { z } from 'zod';

const userSchema = z.object({
  id: z.string(),
  name: z.string(),
});

type User = z.infer<typeof userSchema>;

2. DTOとスキーマの違い

  • **DTO(Data Transfer Object)**は、層間やAPIの入出力で使う「データの型定義」。
  • スキーマは、DTOの型を生成しつつ、バリデーションも担う「実行時の検証ルール」。

現代的な設計では、ZodスキーマからDTO型を自動生成し、型とバリデーションを一元管理するのがベストプラクティスです。


3. Zodの便利なメソッド

pick

指定したフィールドだけ抜き出す

const createUserSchema = userSchema.pick({ name: true });
type CreateUserDto = z.infer<typeof createUserSchema>;
// => { name: string }

omit

指定したフィールドを除外

const userWithoutIdSchema = userSchema.omit({ id: true });
type UserWithoutId = z.infer<typeof userWithoutIdSchema>;
// => { name: string }

partial

全フィールドを「任意(optional)」に

const userPartialSchema = userSchema.partial();
type UserPartial = z.infer<typeof userPartialSchema>;
// => { id?: string; name?: string }

4. サンプル:UserとProfileのCRUD API設計

1. スキーマ定義と型生成

userWithProfileSchemaは、User型にprofileプロパティを追加した複合型として定義しています。
profileSchema.omit({ userId: true })で、結合時に不要なuserIdを除外しています。

import { z } from 'zod';

export const userSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

export const profileSchema = z.object({
  id: z.string(),
  userId: z.string(),
  age: z.number().min(0),
  bio: z.string().optional(),
});

export const userWithProfileSchema = userSchema.extend({
  profile: profileSchema.omit({ userId: true }),
});

export type User = z.infer<typeof userSchema>;
export type Profile = z.infer<typeof profileSchema>;
export type UserWithProfile = z.infer<typeof userWithProfileSchema>;

2. DTO生成

APIのリクエストやレスポンスで使うDTO(Data Transfer Object)は、
必要なフィールドだけを抜き出したり、バリデーションルールを追加したりしてスキーマを作成します。

export const createUserProfileSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
  bio: z.string().optional(),
});
export type CreateUserProfileDto = z.infer<typeof createUserProfileSchema>;

export const updateUserProfileSchema = createUserProfileSchema.partial();
export type UpdateUserProfileDto = z.infer<typeof updateUserProfileSchema>;

3. Zodバリデーションパイプ

コントローラーで@UsePipes(new ZodValidationPipe(schema))と使うことで、
そのエンドポイントのリクエストボディがZodスキーマでバリデーションされます。
バリデーションエラー時は自動で400エラーが返ります。

import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { ZodSchema, ZodError } from 'zod';

@Injectable()
export class ZodValidationPipe implements PipeTransform {
  constructor(private schema: ZodSchema<any>) {}

  transform(value: unknown) {
    try {
      return this.schema.parse(value);
    } catch (error) {
      if (error instanceof ZodError) {
        throw new BadRequestException(error.errors.map(e => e.message).join(', '));
      }
      throw error;
    }
  }
}

4. コントローラーの実装サンプル

NestJSのコントローラーで、DTOとバリデーションパイプを使ってCRUD APIを実装するサンプルです。

import { Controller, Get, Post, Body, Param, Patch, Delete, UsePipes } from '@nestjs/common';
import { UserProfileService } from '../services/user-profile.service';
import {
  createUserProfileSchema,
  CreateUserProfileDto,
  updateUserProfileSchema,
  UpdateUserProfileDto,
} from '../dto/user-profile.dto';
import { ZodValidationPipe } from '../pipes/zod-validation.pipe';

@Controller('users')
export class UserProfileController {
  constructor(private readonly service: UserProfileService) {}

  @Post()
  @UsePipes(new ZodValidationPipe(createUserProfileSchema))
  create(@Body() dto: CreateUserProfileDto) {
    return this.service.create(dto);
  }

  @Get()
  findAll() {
    return this.service.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.service.findOne(id);
  }

  @Patch(':id')
  @UsePipes(new ZodValidationPipe(updateUserProfileSchema))
  update(@Param('id') id: string, @Body() dto: UpdateUserProfileDto) {
    return this.service.update(id, dto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.service.remove(id);
  }
}

5. まとめ

基本的な使い方を整理してみました。
いろんな記事を参照しつつAIにサンプルコードを実装してもらうことで効率よく学習ができました。
参考記事はこちら

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?