Zod 알아보기

Minboy·2024년 3월 17일
3
post-thumbnail

프론트엔드의 Validation

프론트엔드 개발을 하다보면 Validation, 사용자의 입력을 검증해야할 때가 종종 있다.

사실 프론트 단에서 진행하는 유효성 검사는 브라우저의 개발자 도구를 통해 손쉽게 통과하고 악의적인 사용자는 얼마든지 백엔드 서버로 어떤 입력이든 전송할 수 있다. 그렇다면 프론트에서의 유효성 검증은 할 필요가 없는 것인가 하는 의문이 든다.

하지만 프론트에서 유효성을 검사하는 것으로 사용자는 잘못된 입력을 했을 때 서버의 유효성 검증 응답을 기다리지 않아도 되므로 더 좋은 UX를 경험할 수 있고, 서버의 리소스 또한 줄일 수 있게된다.

따라서 프론트엔드의 유효성 검증은 반드시 필요한 단계라고 할 수 있겠다. 그렇다면 프론트엔드의 유효성 검증은 어떻게 구현할 수 있을까?

Zod

프론트엔드의 Validation은 직접 모든 입력을 하나하나 검사할 수도 있고, 정규표현식을 통한 검증 등 굉장히 다양한 방법으로 구현할 수 있다. 그 중에서 나는 최근에 알게된 라이브러리인 Zod를 통해 유효성 검증을 구현하는 것을 즐기고 있다.

Zod의 공식 홈페이지를 확인해보면 해당 라이브러리를 정적 유형 추론을 통한 타입스크립트 친화적 스키마 검증이라고 소개하고 있다. 말이 굉장히 어렵지만 한번 사용해보고 나면 무슨 느낌인지 와닿는 설명이다. 이번 글에서 Zod를 간단하게 알아보고 사용해보자.

Installation

// tsconfig.json
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true
  }
}

우선 프로젝트의 tsconfig.json에서 "strict": true 가 되어있는지 확인하자. 대부분의 경우 잘 되어 있을 것이다.

npm install zod       # npm
yarn add zod          # yarn
bun add zod           # bun
pnpm add zod          # pnpm

이후 터미널을 통해 각자 사용하는 패키지 매니저를 사용하여 Zod를 프로젝트에 추가해주자.

Basic usage

Zod의 유효성 검증은 z 오브젝트를 통해 스키마, 즉 입력의 틀을 선언해두고 사용자의 입력이 스키마와 일치하는지 체크하는 방식으로 이루어진다.

그럼 먼저 스키마를 선언하는 방법을 알아보자.

import { z } from "zod";

const mySchema = z.string();

console.log(mySchema.parse("hello"));
console.log(mySchema.parse(123));

function App() {
    return <div>Zod</div>;
}

export default App;

zod에서 z 오브젝트를 import 해오고 이를 통해 스키마를 선언할 수 있다.

스키마는 위처럼 단순한 문자열에서부터 boolean, number, object 등 원하는 대로 선언할 수 있고, 선언된 스키마의 parse 메소드를 통해 입력을 검증할 수 있다. 위 코드의 결과를 살펴보면

첫번째 입력인 "hello"는 스키마와 일치하기 때문에 원본 그대로가 리턴되었고, 두번째 입력인 123은 ZodError를 throw하였다. 에러를 살펴보면 스키마에서 기대한 타입과 잘못된 입력 타입을 확인할 수 있다.

Form의 입력값들을 검증하는데 유용한 방법인 object형 스키마도 살펴보자.

import { z } from "zod";

const mySchema = z.object({
    name: z.string(),
    password: z.string(),
    isProgrammer: z.boolean(),
    age: z.number(),
});

console.log(
    mySchema.parse({
        name: "Minboy",
        password: "pass123",
        isProgrammer: true,
        age: 25,
    })
);

function App() {
    return <div>Zod</div>;
}

export default App;

object형 스키마는 동일하게 z.object 를 통해 선언하고, 각 필드의 이름과 해당 필드에 들어와야할 값의 타입을 명시해주면된다.

모든 필드의 타입이 스키마와 일치하기 때문에 원본 그대로를 리턴받은 모습이다.


예시에서 확인했듯이 스키마와 입력값이 일치하면 원본 그대로를 리턴받고, 일치하지 않으면 에러를 throw하는 것을 확인할 수 있었다. 하지만 때로는 에러 throw가 아닌 입력값의 유효성을 true/false로 알고 싶을 때가 있는데, 이럴때는 parse 대신 safeParse 를 사용하면 된다.

import { z } from "zod";

const mySchema = z.object({
    name: z.string(),
    password: z.string(),
    isProgrammer: z.boolean(),
    age: z.number(),
});

console.log(
    mySchema.safeParse({
        name: "Minboy",
        password: "pass123",
        isProgrammer: true,
        age: 25,
    })
);
console.log(
    mySchema.safeParse({
        name: 25,
        password: true,
        isProgrammer: "Hi",
        age: "red",
    })
);

function App() {
    return <div>Zod</div>;
}

export default App;

실행 결과를 보면 safeParse 의 결과로 object가 리턴되고, 해당 object 내의 success 필드를 통해 검증을 통과했는지 여부를 true/false 값으로 받아오는 것을 확인할 수 있다. 성공시엔 data 필드에 원본 값이, 실패 시엔 error 필드에 에러가 담겨온다.


Primitives

Zod에서 활용할 수 있는 타입들을 Primitives, 원시 값들이라고 하는데 활용할 수 있는 원시값들은 다음과 같다.

import { z } from "zod";

// primitive values
z.string();
z.number();
z.bigint();
z.boolean();
z.date();
z.symbol();

// empty types
z.undefined();
z.null();
z.void(); // accepts undefined

// catch-all types
// allows any value
z.any();
z.unknown();

// never type
// allows no values
z.never();

응용하기

타입 제약에 더불어 글자수 제한, 숫자 범위, 형식 등을 검증하기 위해 Zod에서는 굉장히 많은 메서드들을 제공해준다. 몇가지 살펴보자.

string에서 활용할 수 있는 메서드들은 다음과 같다.

// validations
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().email();
z.string().url();
z.string().emoji();
z.string().uuid();
z.string().cuid();
z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().datetime(); // ISO 8601; default is without UTC offset, see below for options
z.string().ip(); // defaults to IPv4 and IPv6, see below for options

// transformations
z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase

확인할 수 있다시피 최소/최대 글자제한부터 이메일형식인지, 특정 문자열으로 시작하거나 포함하는지 등을 검증할 수 있다. 또한 regex 메서드를 통해 정규표현식으로 검사도 가능하며, 변환 메서드들도 제공하고 있다. 이 모든 메서드들을 메서드 체이닝을 통해 함께 적용할 수도 있으니 활용도는 무궁무진하다고 할 수 있다.

예를 들어 패스워드를 검증하기 위해 최소 6글자, 최대 20글자에 "@"를 포함하도록 제약하고 싶다면

const passwordSchema = z.string().min(6).max(20).includes("@");

와 같이 작성하면 된다.

string뿐만 아니라 다른 Primitives들도 다양한 검증 메서드들을 제공하니 공식 문서를 확인해보자.

모든 메서드들을 함께 살펴보는 것은 글이 너무 길어지므로 몇가지만 더 살펴보도록 하겠다.


만약 타입만 제약하는 것이 아닌 정확히 일치하는 값인지 검증하고 싶다면 literal 을 활용하면 된다.

const nameSchema = z.literal("Minboy")

스키마에서 특정 필드를 없어도 되는 선택사항으로 만들고 싶다면, optional 메서드를 활용하자. 기본적으로 optional 메서드를 활용하지 않았다면 필수적으로 해당 필드와 값이 존재해야한다.

const mySchema = z.object({
  name: z.string(),
  age: z.number().optional()
})

console.log(mySchema.parse({ name: "Minboy" }); // pass

타입스크립트를 사용하다보면 종종 있는 일인데, 선언한 스키마를 타입스크립트의 타입으로 변환하여 사용하고 싶을 수 있다. 이럴때는 z.infer 를 활용하면된다.

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

type User = z.infer<typeof userSchema>

제약조건들에 조건 연산자도 활용할 수 있다.

const stringOrNumber = z.string().or(z.number()); // string | number

const nameAndAge = z
  .object({ name: z.string() })
  .and(z.object({ age: z.number() })); // { name: string } & { age: number }

parse 를 통해 검증할 때, 에러메세지를 커스텀하고 싶을 수 있다. 검증메서드에 해당 검증을 통과하지 못했을 때 리턴할 메세지를 전달해주면된다.

z.string().min(5, { message: "Must be 5 or more characters long" });
z.string().max(5, { message: "Must be 5 or fewer characters long" });
z.string().length(5, { message: "Must be exactly 5 characters long" });
...

이렇게 Zod 라이브러리를 활용해 입력값 검증을 하는 방법을 간단하게 살펴보았다. 해당 라이브러리를 사용함으로써 귀찮은 작업인 검증을 쉽고 편하게 구현할 수 있으며, 결과적으로 사용자들에게도 더 나은 UX를 제공할 수 있다.

Zod는 홀로 사용될 때도 막강하지만, 사실 react-hook-form과 같은 다른 라이브러리들과 함께 쓰일 때도 그 빛을 발한다고 생각한다. 차후에는 함께 사용하는 방법도 살펴보자.

profile
🐧

2개의 댓글

comment-user-thumbnail
2024년 3월 17일

react-hook-form 포스팅도 기대하겠읍니다.

1개의 답글

관련 채용 정보