zod : 유효성 검사 라이브러리
- 조건을 알려주면 zod가 알아서 검사
- validation 유효성 검사
- transform 데이터 변환
- 사실 input은 기본 속성으로 브라우저 유효성 검사를 지원한다(프론트엔드용 유효성 검사) ⇒ min/max, minLength/maxLength, pattern(정규식)
- zod는 백엔드용 유효성 검사에 적합
기본 사용법
import { z } from "zod"; // 규칙 (여기서는 문자열이어야 하는 규칙) const mySchema = z.string(); // 검사 mySchema.parse("tuna"); // => ok. ("tuna") mySchema.parse(12); // => 에러. (throws ZodError) // safe 검사 (검사가 실패해도 에러x) mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } mySchema.safeParse(12); // => { success: false; error: ZodError }
- parse의 결과값은 데이터, safeParse는 정보가 담긴 객체임을 주의
규칙들
1. 타입 유형 규칙들
- 원시타입 규칙
- 원시타입은 강제 형변환 가능
// primitive values z.string(); z.number(); z.bigint(); z.boolean(); z.date(); z.symbol(); // empty types z.undefined(); z.null(); z.void(); // accepts undefined
z.coerce.string(); // String(input) z.coerce.number(); // Number(input) z.coerce.boolean(); // Boolean(input) z.coerce.bigint(); // BigInt(input) z.coerce.date(); // new Date(input) //ex. const schema = z.coerce.string(); schema.parse("tuna"); // => "tuna" schema.parse(12); // => "12" schema.parse(12); // => "12" schema.parse(true); // => "true" schema.parse(undefined); // => "undefined" schema.parse(null); // => "null"
- 리터럴
const tuna = z.literal("tuna"); const twelve = z.literal(12); const twobig = z.literal(2n); // bigint literal const tru = z.literal(true);
2. 원시형들 규칙
String
//유효성 규칙 z.string().max(5); z.string().min(5); z.string().length(5); z.string().email(); z.string().url(); z.string().emoji(); //변형 (transforms) z.string().trim(); // trim whitespace z.string().toLowerCase(); // toLowerCase z.string().toUpperCase(); // toUpperCase
String 외에도, 무수히 많은 규칙들이 있음 ⇒ https://zod.dev/ 참고
Method
1. parse : 앞서 만든 규칙에 맞는지 데이터 검사
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
1-1. safeParse : 틀려도 에러 던지지 않는 검사
stringSchema.safeParse(12); // => { success: false; error: ZodError } stringSchema.safeParse("billie"); // => { success: true; data: 'billie' }
1-2. parseAsync
: 비동기 parse
refine의 함수가 async 함수일 때 쓰는 것
const stringSchema = z.string().refine(async (val) => val.length <= 8); await stringSchema.parseAsync("hello"); // => returns "hello" await stringSchema.parseAsync("hello world"); // => throws error await stringSchema.safeParseAsync("hello"); await stringSchema.spa("hello");
2. refine
: 커스텀 규칙
const myString = z.string().refine((val) => val.length <= 255, { message: "String can't be more than 255 characters", });
함수 결과값이 true면 성공, false면 에러
- object 전체에 refine하는 경우 에러 메세지는 formError가 됨. 특정 필드의 에러 메세지(fieldError)가 되길 원한다면? ⇒ path 필드 사용!
const passwordForm = z .object({ password: z.string(), confirm: z.string(), }) .refine((data) => data.password === data.confirm, { message: "Passwords don't match", path: ["confirm"], // path of error });
2-1. superRefine
: 스키마 전체 또는 여러 필드 간의 상호 관계를 검증
• 스키마 전체 검증: 스키마의 여러 필드 값을 동시에 참조하여 상호 관계를 기반으로 검증 가능 • 사용자 정의 오류 추가: 검증 실패 시 특정 필드에 대해 사용자 정의 오류를 추가 가능 • 다중 로직 처리: 여러 유효성 검사 로직을 한 번에 처리 가능 • 검증 중단 기능: 특정 조건에서 검증을 중단(Abort Early)하여 이후 검증 로직이 실행되지 않도록 설정가능
const emailSchema = z.string().superRefine((data, ctx) => { if (data.field1 !== data.field2) { ctx.addIssue({ //오류를 추가할 수 있는 컨텍스트 객체, addIssue로 에러 정보 정의 code: "custom", message: "field1 and field2 must match", path: ["field1"], // 오류가 발생한 필드 경로 }); ctx.addIssue({ code: "custom", message: "field1 and field2 must match", path: ["field2"], }); } });
//검증 중단 기능 => `ctx.addIssue()` 메서드에 `fatal: true` 플래그를 추가 const emailSchema = z.string().superRefine(async (email, ctx) => { const emailTest = z.string().email().safeParse(email); if (!emailTest.success) { ctx.addIssue({ code: "invalid_string", validation: "email", message: "Invalid Email", path: ["email"], //없으면 error가 formError로 됨 fatal: true, // 검증 중단 }); return; // 이후 로직(즉 밑에 코드)과 superRefine 이후 추가 검증 실행 x } // 이메일 중복 체크 (형식이 올바른 경우에만 실행) const emailExists = await checkEmailInDatabase(email); // 가상의 동기 데이터베이스 조회 함수 });
3. transform
: 받은 데이터를 변환
const stringToNumber = z.string().transform((val) => val.length); stringToNumber.parse("string"); // => 6
함수의 return 값으로 변환
4. default
: 기본값
const stringWithDefault = z.string().default("tuna"); stringWithDefault.parse(undefined); // => "tuna"
에러 핸들링
parse 결과 값의 success 속성 또는 error으로 규칙에 적합여부를 알 수 있다.
const result = z .object({ name: z.string(), }) .safeParse({ name: 12 }); if (!result.success) { result.error.issues; /* [ { "code": "invalid_type", "expected": "string", "received": "number", "path": [ "name" ], "message": "Expected string, received number" } ] */ }
format
: 에러 결과를 간단하게 알 수 있음
형태 - result.error.format()
if (!result.success) { const formatted = result.error.format(); /* { name: { _errors: [ 'Expected string, received number' ] } } */ formatted.name?._errors; // => ["Expected string, received number"] }
flatten
: object의 에러와 필드에러를 구분해서 확인할 수 있음
형태 - result.error.flatten()
if (!result.success) { console.log(result.error.flatten()); } /* { formErrors: [], fieldErrors: { name: ['Expected string, received null'], contactInfo: ['Invalid email'] }, } */
에러메시지
해당하는 규칙에 맞지 않은 데이터가 오면 에러를 보내는데 이때 메세지를 커스텀할 수 있음
const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", }); z.string().min(5, { message: "Must be 5 or more characters long" }); z.string().min(5, "Must be 5 or more characters long");