Zod로 유효성 검사 구현에 생산성을 더해보아요

date
Aug 27, 2024
slug
zod
author
status
Public
tags
Etc
summary
type
Post
thumbnail
milad-fakurian-0uUzrqDeBNY-unsplash.jpg
category
updatedAt
Nov 11, 2024 01:45 PM
 

TypeScript를 쓰는데 별도의 데이터 유효성 검사 라이브러리가 필요한 이유

TypeScript는 컴파일 타임에 타입을 검사하지만, 런타임에 데이터 유효성을 검사하지 않습니다.
TypeScript는 정적 타입 검사를 합니다. 코드 작성 시 및 컴파일 시점까지 변수, 함수 등의 타입들에 대한 검사를 진행합니다. 다만, 타입 검사와 타입 추론은(타입 자동완성) 우리가 TypeScript로 코드를 작성하고 컴파일하는 시점까지만 누릴 수 있습니다.
⇒ 런타임에는 TypeScript를 JavaScript로 변환한 코드가 실행되기에 TypeScript정보는 사라지기 때문이죠. 실제로 build 후 배포를 위한 dist폴더에는 js파일만 있는 것 기억하시죠…
⇒ 이로 인해 런타임에서 발생하는 데이터들에 대한 유효성 검사가 필요합니다.
*런타임 데이터: API 응답, 사용자 입력, API의 응답값등 외부에서 들어오는 데이터는 런타임에 처리되며 타입스크립트는 이에 대한 검증을 제공하지 않습니다.
 

런타임 데이터에 대한 유효성 검사를 구현하는데 생산성을 높여주는 라이브러리

Yup/ Zod/ joi / Avj
 
라이브러리 왜 써요?
⇒ 유효성 검사 기능 간결하게 구현 + 에러 처리 용이 + 스키마 정의 후 별도의 인터페이스 생성 없이 재활용 가능

joi와 Avj는 우선 건너뜁시다

joi - 정적 타입 추론을 지원하지 않아 제외 (Node.js에서 잘 쓰임.)
Avj - 가장 오래되긴 했습니다. (2015 / 올해 릴리즈 버전은 8까지 있음.) 다만 이 친구는 ts 지원 안되고, 특히 호환성과 의존성 문제들이 있을 수 있다고 합니다. 해당 라이브러리를 사용한 오래된 서비스들이 있다보니 계속 유지되고 있는것으로 보입니다.
 

Yup과 Zod

 
Yup
  • 스키마 정의가 유연하다
  • Formik과 함께 많이 쓰는듯 - Formik공식문서에서 추천중
  • 역사가 깊다 - yup은 Jun 25, 2016 / zod는 Apr 5, 2020
  • repository star 22.7k
 
Zod
notion image
  • TS와 강력한 호환성 제공 - 엄격한 정적 타입 검사 제공
  • 의존성이 없다 ⇒ 경량이다
    • package.lock.json을 보면 dependencies필드가 없는 것을 볼 수 있음.
      //package.lock.json "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "funding": { "url": "https://github.com/sponsors/colinhacks" } }
  • API 스타일이 Yup에 비해 간결한편
  • React-hook-form과 조합해 자주 쓰임
  • release가 잦으며 업데이트 + 관리가 잘 되고있다. 현재 최신 version v3.23.8
  • 구문이 더 간결하다 → 복잡한 validation구현 시 yup에 비해 간결한 구문으로 구현이 가능하다
  • 검사에 더해 데이터 형변환도 지원
  • repository star 33k
 

Zod를 선택한 이유

Yup은 문자형 데이터에 대한 숫자 검증을 엄격하게 하지 않음. 마치 암묵적 타입 변환을 하는 JS 같네요.
const testSchema = yup .string() .validate(333) .then((res) => console.log(res)); // string 자료형을 예상했는데 333을 넣어 확인했을때 엄격하게 자료형을 검사하지 않음. // 반환값은 333 const testSchema = yup .string() .isValid(11) .then((res) => console.log(res)); // true를 반환
이에 대해 엄격히 검사하려면 strict API를 함께 사용해줘야함.
 
const testSchema = yup .string() .strict() .validate(333) .then((res) => console.log(res)); // 333을 반환하지 않고 끝남 const testSchema = yup .string() .strict() .isValid(11) .then((res) => console.log(res)); //false를 반환
이처럼 Zod보다 엄격하지 않고 저 then체이닝도 맘에 안들었습니다.
⇒ 문법 간결함이 Zod에 비해 떨어진다고 느꼈습니다.
 

이 라이브러리를 쓰지 않는다면 유효성 검사 함수를 개별 구현하시면 됩니다

저도 라이브러리 적용 전엔 아래 같은 함수들을 하나하나 구현했습니다.
const emailValidation = (email: string) => { const emailValidation = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*[-_\.]?@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{1,6}$/i; if (email === '' || !emailValidation.test(email)) { return false; } return true; };
비밀번호의 경우라면 비밀번호 정책에 따른 validation에 비밀번호 입력 / 비밀번호 확인 Input에 따라 별도의 함수를 만들고… x100

zod를 사용하면

parse 사용 예시
import { z } from 'zod'; const emailValidationSchema = z.string().email({ message: "Invalid email address" }); const emailValidation = (email: string) => { try { emailValidationSchema.parse(email); return true; } catch (e) { return false; } };
safeParse 사용 예시
const result = emailValidationSchema.safeParse(email); if(!result.success){ // 유효성 검사 실패에 대한 처리 return; }
parse와 safeParse의 차이점
  • parse: 유효하지 않은 입력에 대해 예외를 던집니다.
  • safeParse: 유효하지 않은 입력에 대해 예외를 던지지 않고, 결과를 객체 형태로 반환합니다.
이 두 API는 상황에 따라 다르게 사용할 것 같은데 현재 프로젝트의 경우 api 호출 전 폼에 대한 입력값 검사들을 진행했기에 safeParse를 주로 사용했습니다.