[Next.js] Zod 라이브러리 활용하여 데이터 유효성 검사하기

문지은·2024년 1월 8일
2

Next.js - App Router

목록 보기
8/20
post-thumbnail

클라이언트가 전송한 객체는 항상 유효성을 검증해야 한다. 간단한 if문을 사용할
수 있지만, 애플리케이션이 복잡해지면 중첩된 if문이 많아질 수 있다.

데이터 유효성 검증 라이브러리(예: Zod)를 사용하면 객체의 구조를 간단한 문법으로 정의할 수 있으며, 데이터 유효성 검증의 복잡성을 관리할 수 있다.

Zod 라이브러리를 활용하여 데이터 유효성 검사를 하는 방법에 대해 알아보자.

Zod

  • 스키마 선언 및 유효성 검사 라이브러리
  • Zod 공식문서를 보면 이메일, 패스워드, 대소문자 등 다양한 유효성 검사를 함수 한줄로 처리할 수 있도록 지원하는 것을 알 수 있다.

  • 이를 앞서 작성한 PUT 메소드 함수에 적용해보겠다.

app/api/users/[id]/route.tsx

import { NextRequest, NextResponse } from "next/server";

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json();

  if (!body.name) {
    return NextResponse.json({ error: "Name is Required" }, { status: 404 });
  }

  const userData = [
    { id: 1, name: "Jieun" },
    { id: 2, name: "Hansol" },
  ];

  const requestedId = parseInt(params.id);

  const user = userData.find((user) => user.id === requestedId);

  if (!user) {
    return NextResponse.json({ error: "USER NOT FOUND" }, { status: 404 });
  }

  return NextResponse.json({ id: requestedId, name: body.name });
}

라이브러리 설치

  • Zod 모듈 설치
npm install zod
  • Zod 를 사용할 때는 가급적 타입스크립트는 strict 모드로 사용

tsconfig.json

{
  "compilerOptions": {
    "strict": true
  }
}
  • 이제 zod 패키지에서 z 를 불러올 수 있으며, Zod 의 모든 기능을 사용할 수 있다.
import { z } from "zod";

스키마 정의

  • Zod 를 사용할 때 제일 먼저 해야하는 것은 스키마(schema)를 정의하는 것이다.
    • 스키마란 쉽게 말해서 데이터의 형태와 구조를 뜻한다.
    • 예를 들어, 이메일, 나이, 활성화 여부로 이루어진 사용자 객체를 스키마로 정의해보자.
    • 이름은 최소 3글자, 이메일은 이메일 형태로 구성되고, 나이는 숫자로 구성되도록 작성
const schema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number(),
});
  • 이제 작성한 스키마를 사용하기 위해 프로젝트로 돌아가 API 폴더의 users 폴더에 schema.ts 파일을 생성한다.
    • 해당 파일에는 유효성 검사 대상에 대한 정보를 명시한다.
    • 먼저 Zod를 z 로 가져오고, 그 다음 object 메소드를 호출하여 해당 메소드의 인자 값으로 사용자 객체의 형태를 정의한다.

app/api/users/schema.ts

import { z } from "zod";

const schema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number(),
});

export default schema;

유효성 검사

  • Zod 스키마를 정의하고 나면, 두 종류(parse, safeParse)의 스키마 함수를 통해 유효성 검사를 할 수 있다.
    • 함수에 검증하고 싶은 값을 넘겨서 호출하면 검증 결과를 확인할 수 있다.
schema.parse({
	name: "jieun",
	email: "email@email.com",
	age: 20,
});  // 통과

schema.parse({
	name: "jieun",
	email: "email@email.com",
});  // 검증 실패

parse vs safeParse

  • parse
    • 오류가 발생하는 경우 오류 메시지와 함께 throw 함수를 호출해 서버가 중단된다.
  • safeParse
    • 오류가 발생하는 경우, 서버 중단 없이 결과 객체에 오류 메시지 및 여러 정보를 전달하고, 개발자는 이 객체에서 프로퍼티를 추출해 검증 로직을 작성할 수 있다.

유효성 검증 적용하기

safeParse 를 통해 유효성 검사를 하도록 PUT 메소드를 수정해보자.

  • validation 변수에 담긴 결과가 false 이면 유효성 검사에 실패한 것이므로 404 오류를 처리하고, 오류 메시지는 zod object 에서 제공하는 error 를 출력하도록 한다.
    • 검증에 실패했을 경우 정확히 어떤 부분에서 어떤 검증을 통과하지 못했는지 알 수 있기 때문에 매우 유용하다.

app/api/users/[id]/route.tsx

import { NextRequest, NextResponse } from "next/server";
import schema from "../schema";

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json();

  const validation = schema.safeParse(body);

  if (!validation.success) {
    return NextResponse.json(validation.error.errors, { status: 404 });
  }

  const userData = [
    { id: 1, name: "Jieun", email: "jieun@email.com", age: 29 },
    { id: 2, name: "Hansol", email: "hansol@email.com", age: 27 },
  ];

  const requestedId = parseInt(params.id);

  const user = userData.find((user) => user.id === requestedId);

  if (!user) {
    return NextResponse.json({ error: "USER NOT FOUND" }, { status: 404 });
  }

  return NextResponse.json({
    id: requestedId,
    name: body.name,
    email: body.email,
    age: body.age,
  });
}
  • Postman 에서 테스트하여 유효성 검사가 잘 이루어지는지 확인해보자.
  • 다음과 같이 잘못된 형식의 요청에는 오류를 반환한다.

[
    {
        "code": "too_small",
        "minimum": 3,
        "type": "string",
        "inclusive": true,
        "exact": false,
        "message": "String must contain at least 3 character(s)",
        "path": [
            "name"
        ]
    },
    {
        "validation": "email",
        "code": "invalid_string",
        "message": "Invalid email",
        "path": [
            "email"
        ]
    }
]
  • 올바른 입력 객체에도 잘 작동하는 것을 볼 수 있다.

  • 한 가지 주의해야 할 부분은 검증이 성공했을 경우 parse() 함수가 반환하는 객체에는 검증에 통과한 속성만 포함된다는 것이다.
    • 예를 들어 다음과 같이 스키마에 적용되지 않은 password 속성을 입력 객체에 포함한 경우
const user = schema.parse({
	name: "jieun",
	email: "email@email.com",
	age: 20,
	password : "1234"
});  // 통과

console.log(user)
  • parse() 함수가 반환한 결과 객체에는 해당 속성이 제외된다.
{
	name: "jieun"
  email: "email@email.com",
  age: 20,
}
  • 이는 parse() 함수의 반환 타입이 정의된 스키마에 의해서 결정이 되기 때문이다.
    • 타입에 password 속성이 없는데 값에만 password 속성이 들어있다면 타입 에러가 발생했을 것
    • 이처럼 Zod는 타입스크립트에 아주 친화적으로 설계되어 있어서 견고한 코드를 작성하는데도 도움을 준다.

타입 추론

  • Zod는 스키마를 기준으로 타입스크립트 타입을 알아서 추론할 수도 있다.
  • 이 기능을 잘 활용하면 아예 타입을 따로 작성할 필요가 없어지고 따라서 타입을 스키마와 서로 맞춰 줄 걱정이 사라진다.
  • 예를 들어, 사용자 객체를 입력으로 받는 함수를 타입스크립트로 작성하려면 아래와 같이 입력 타입을 작성해 주어야 한다.
// 내가 직접 타입을 작성
interface User {
  email: string;
  age: number;
  active: boolean;
}

function processUser(user: User) {
  User.parse(user); // 유효성 검증
  // 사용자 처리 로직
}
  • 하지만 Zod의 infer과 자바스크립트의 typeof 연산자를 사용하면 이미 정의한 스키마로 부터 타입을 뽑아낼 수 있다.
// 스카마로부터 타입을 추론
type User = z.infer<typeof User>;

function processUser(user: User) {
  User.parse(user); // 유효성 검증
  // 사용자 처리 로직
}

References

Zod로 유효성 검증과 타입 선언의 두 마리 토끼 잡기

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글