잘못된 데이터 유형에 의한 에러는 애플리케이션의 작동에 치명적인 문제를 일으킬 수 있기 때문에, 전송하거나 수신하는 모든 데이터의 유효성을 확인하는 것이 중요하다. 예측하지 못한 데이터 유형이나 값은 애플리케이션의 충돌을 유발하거나 다른 부정적인 결과를 초래할 수 있다.
스키마 유효성 검사는 데이터 검증을 위해 스키마를 만드는 과정을 의미한다. 스키마의 정의는 구현에 따라 달라질 수 있지만, JavaScript에서는 보통 데이터의 유효성을 검증하는 데 사용되는 객체 데이터 유형으로 스키마를 표현한다.
const userSchema = {
name: "string",
age: "number",
};
const userInformation = {
name: "Peter",
age: "15",
};
function checkSchema(schema, data) {
Object.keys(schema).forEach(key => {
if (!data.hasOwnProperty(key)) {
console.error(`${key} is missing.`);
} else if (typeof data[key] !== schema[key]) {
console.error(
`${key} should be type ${schema[key]}, but type ${typeof data[key]} was provided.`
);
} else {
console.log(`${key} passed validation!`);
}
});
}
checkSchema(userSchema, userInformation);
위 코드는 자바스크립트 스키마의 간단한 예시로 userSchema 객체를 통해 userInformation 객체의 name과 age 속성의 유형을 검사한다.
그러나 이런 간단한 유효성 검사 함수만으로는 복잡한 애플리케이션의 데이터 유효성 검사를 충분히 수행하기 어렵다. 복잡한 데이터 구조의 유효성을 검사하고, 예상치 못한 동작을 방지하기 위해서는 보다 강력한 기능이 필요한데, 이때 스키마 유효성 검사 라이브러리가 활용된다.
Zod와 Yup은 둘 다 TypeScript를 사용하여 스키마 기반 검증(Validation)을 수행하기 위한 라이브러리인데 이에대해 알아보자.
Zod는 TypeScript를 기반으로 하는 데이터 유효성 검사 라이브러리로, JavaScript와 TypeScript 개발 환경에서 모두 사용할 수 있다. 이 라이브러리는 데이터의 정의와 유효성 검사를 간편하게 만들어주며, 독립적이고 경량화된 구조로 어떤 TypeScript 혹은 JavaScript 환경에서도 쉽게 적용할 수 있다.
Zod에서 사용하는 원시 타입에는 string, number, BigInt, Boolean, date, symbol 등이 있다. 또한 Zod는 null 값에 대한 스키마를 정의하는 API와 any, unknown과 같은 모든 유형을 포함하는 스키마도 제공한다.
const z = require('zod');
// 또는 import { z } from 'zod'
const schema = z.string()
schema.parse('하이하이');
위 예시는 간단한 문자열 스키마 검증을 보여준다. Zod는 검증을 위한 다양한 메서드를 제공한다.
//ex1
const ageSchema = z.number().min(20);
ageSchema.parse(12); // 에러 발생: 숫자는 20 이상이어야 합니다.
//ex2
const ageSchema = z
.number()
.min(19, { message: "학샏 ㄴㄴ" })
.max(30, { message: "살짝 많음" });
ageSchema.parse(16) // 에러 발생: 성인되고 오세욤.
숫자를 검증하기 위해 스텀 에러 메시지를 사용할 수 있다.
const usernameSchema = z.string().toLowerCase();
const emailSchema = z.string().trim().email();
console.log(usernameSchema.parse("Ho_Seong")); //->ho_seong
emailSchema.parse(" ohs8283@gmail.com ");
Zod는 문자열 값의 형식을 지정하는 데 도움이 되는 변환 방법도 제공하는데 usernameSchema는 파싱된 문자열의 반환 값을 소문자 문자열로 변환한다. emailSchema는 유효한 이메일인지 확인하기 전에 trim 메서드를 사용하여 문자열 주변의 모든 공백을 제거한다.
위의 예에 "user"라는 스키마를 생성하려면 객체가 필요한다.
const userSchema = z.object({
username: z.string().toLowerCase(),
email: z.string().trim().email(),
password: z.string().min(8).max(20),
age: z.number().positive(),
});
userSchema.parse({
username: "Ho_Seong",
email: "ohs8283@gmail.com",
password: "12345678",
age: 26,
});
이 경우 Zod는 발생하는 순서대로 모든 유효성 검사 에러를 발생시키는데 따라서 username과 age가 모두 유효하지 않으면, 두 스키마 모두에 대한 에러 정보가 포함된 배열을 얻게 된다.
기본적으로 객체의 모든 속성은 필수이다. 그러나 선택적인 속성을 갖고 싶다면 optional 메서드를 사용할 수 있다.
Zod는 함수의 유효성을 검사하고, 함수에 전달된 데이터와 함수에서 반환된 데이터가 올바른지 확인할 수 있다. 함수 스키마는 두 개의 API(args 및 returns)를 사용하여 함수의 입력과 출력을 검증한다.
const sumOfNumsSchema = z
.function()
.args(z.number(), z.number())
.returns(z.number());
const sumOfNums = sumOfNumsSchema.validate((val1, val2) => {
return val1 + val2;
});
sumOfNums("1", 10); // TypeError: Expected number, received string
지금까지 본 다른 유효성 검사와 달리 Zod의 함수 유효성 검사에서는 .parse를 사용하지 않는다. 함수 유효성 검사는 Zod에만 있다.
Yup은 JavaScript 및 TypeScript에서 사용할 수 있는 검증 라이브러리로, 주로 React와 함께 사용되는데, Yup의 스키마는 간단하게 작성할 수 있고, 다양한 유효성 검사 메서드를 지원한다.
Yup과 Zod는 모두 데이터 유효성 검사를 위한 JavaScript 라이브러리로, 유사한 기능을 제공하지만 서로 다른 방식으로 작동한다.
const yup = require('yup');
// 또는 import * as yup from 'yup';
const schema = yup.string();
schema.validate(17).then((res) => console.log(res)); // ->333
schema.isValid(10).then((res) => console.log(res)); // ->true
위의 validate 메서드는 Zod의 parse 메서드와 비슷한 역할을 한다. 두 메서드 모두 객체를 검증하는 것이 아니라, 실제로 객체를 파싱한다. 이것은 두 메서드 모두 제공된 데이터를 받아서 반환하려고 시도한다는 것을 의미하며 파서에 에러가 발생하면 실행이 중지되고 에러를 반환한다.
그러나 isValid 메서드는 데이터의 유효성만 검사하고 에러 처리는 사용자에게 맡기게 된다. 따라서 Yup의 validate 메서드와 Zod의 parse 메서드를 사용할 때는 에러 처리를 고려해야 한다.
위의 예에서는 문자열로 지정된 스키마에 숫자 값이 전달되면 왜 에러가 발생하지 않는지 궁금해할 수 있는데 이는 Yup이 기본적으로 엄격하지 않기 때문이다. 즉, 숫자를 문자열로 쉽게 변환할 수 있으므로 에러가 발생하지 않는 것이다. 그러나 strict 메서드를 사용하면 이러한 기본 동작을 변경할 수 있다.
const schema = yup.string().strict();
Yup도 Zod처럼 검증과 변환을 위한 다양한 API를 제공한다.
const userSchema = yup
.object({
username: yup.string().lowercase().required(),
email: yup.string().email().required(),
password: yup.string().min(8).max(20).required(),
age: yup.number().positive().required(),
})
.strict();
userSchema
.validate({
username: "Ho_Seong",
password: "12345678",
age: 26,
})
.then((res) => console.log(res)); // TypeError: email is a required field
이 코드같은 경우 입력된 사용자 데이터의 유효성을 검사하고, 필요한 필드가 누락되거나 유효하지 않은 경우 에러를 반환한다.
Zod와 Yup은 TypeScript를 효과적으로 지원하는 데이터 검증 라이브러리이다. 이 라이브러리들을 이용하면, 데이터 유효성 검사를 위한 TypeScript type 별칭을 생성할 수 있다.
Yup 또는 Zod의 스키마를 활용해 type 별칭을 만들어 변수가 올바른 데이터 유형인지 확인하는 것이 가능하다.
// Yup
import * as yup from "yup";
const yupSchema = yup.string();
type YupType = yup.InferType<typeof yupSchema>;
const yupData: YupType = 2; // TypeError, 작동하진 않음
// Zod
import { z } from "zod";
const zodSchema = z.string();
type ZodType = z.infer<typeof zodSchema>;
const zodData: ZodType = 5; // TypeError
위 스크립트를 TypeScript로 실행하면, 값이 문자열이어야 하는데 숫자임에도 Yup은 아무런 동작을 하지 않는 반면, Zod는 실제로 오류를 발생시킨다.
Zod와 Yup의 성능은 특정 사용 사례, 검증 스키마의 복잡도, 검증되는 데이터의 크기에 따라 달라진다. 간단한 유효성 검사 규칙과 작은 데이터 세트에 대해서는 두 라이브러리 간의 성능 차이가 거의 없을 수 있다. 하지만 복잡한 스키마나 큰 데이터 세트에 대해서는 성능 차이가 발생할 수 있다.
Zod는 TypeScript를 처음부터 고려한 설계를 가지고 있다. 이는 TypeScript 프로젝트와 원활하게 통합되도록 만들어진 Yup과는 조금 다르다. 그래서 이미 TypeScript 프로젝트가 있는 경우 Zod를 사용하는 것이 좋은 것 같다. Zod는 또한 아무런 종속성이 없다는 점도 알려져 있어, 추가적인 종속성을 걱정할 필요 없이 프로젝트에 쉽게 통합할 수 있다.
Yup은 Formik과 같은 인기 있는 form 라이브러리와의 통합이 쉬워서 많이 알려져 있지만, Zod도 React Hook Form과 같은 다른 form 라이브러리와 잘 통합된다. Zod를 사용할 때는 가끔 다른 form 라이브러리와의 원활한 통합을 위해 추가적인 서드 파티 라이브러리가 필요할 수도 있다.
Zod는 함수의 입력과 출력을 검증하여 모든 데이터가 올바른지 확인할 수 있다. 또한 오류가 발생하면 런타임이 중지되는 우수한 TypeScript 지원 기능을 가지고 있는 반면, Yup은 추론된 유형이 잘못되어도 아무런 작업도 수행하지 않는다.
만약 form 유효성 검사를 많이 수행해야 하는 경우, Yup의 다양한 기능이 form에 사용되는 다양한 패턴, 심지어 반올림을 수행해야 하는 상황에 대한 패턴을 포함하므로 Yup을 사용하는 것이 좋다.
하지만 API 데이터 교환을 많이 하고, 클라이언트와 서버 사이에서 전달되는 모든 데이터의 유효성을 검사해야 하는 경우에는 Zod가 더 좋은 선택일 수 있다.
참고문서: https://blog.logrocket.com/comparing-schema-validation-libraries-zod-vs-yup/