안녕하세요 1년차 개발자로 에너지 모니터링 솔루션 웹앱을 만들며 zod를 도입하게 된 배경에 대한 글입니다.
어떤 문제가 있었고, 왜 zod를 선택했으며, zod의 어떤 기능을 활용해서 문제를 해결 했는지 위주로 작성했습니다.
이하 서술은 반말로 작성한점 양해 부탁드립니다.
목차)
1. zod 도입 배경
2. zod 활용법
3. zod 도입 시 고민한 부분 (zod + prisma, meta)
1. zod 도입 배경
운영 환경에서 API 응답(DB 데이터)이 가끔 신뢰할 수 없는 상태가 되는 이슈가 있었다.
이로 인해 해당 문제에 대한 대응이 운영 중 반복적으로 논의되었고,
개발자가 직접 일일이 데이터를 확인해야 하는 상황이 발생했다.
시간이 흘러 다른 개발자분과 커피챗을 하다가 타입 안정성을 위해 zod를 사용하신다는 이야기를 들었다.
진행중인 프로젝트에 zod를 도입하면, DB에서 가져온 데이터의 구조를 런타임에서 검증하면서 데이터 신뢰성을 확보할 수 있고,
`Cannot read property 'value' of undefined`에러나 누락이 된지도 모르게 조용하게 UI가 깨지는 현상을 해결할 수 있을거라고 판단했다.
구현이 바쁜 상황이라 바로 팀에 건의하지 못했는데,
당시 백엔드 개발자분이 API응답값의 타입(DTO 레이어의 타입)이 추가로 필요해서 새로 타입을 선언할지 고민하는 상황이 생겼다.
이번이 zod를 적용해보자고 설득할 기회라고 생각했고 일과 후 zod 테스트 브랜치를 만들었다.
zod 스키마로 데이터를 런타임에서 검증하고 해당 스키마를 확장해서 API 응답 타입으로 쓰는 샘플 코드를 작성하여 팀에 zod를 도입해보자고 건의했다.
그 결과 "좋지만 장점이 충분한지 모르겠다, 장치에 대한 한글 레이블맵도 혹시 스키마와 한번에 관리 가능하다면 도입해보라."
고 하셔서 해당 부분을 조사 후 zod v4의 meta 태그를 활용해 한글 레이블맵도 같은 코드로 일원화 하며 zod를 도입했다.
팀에서 이미 react-hook-form을 써서 zod의존성이 있었기 때문에 더 부담 없이 도입할 수 있었다.
정리하면, zod를 선택한 이유는 다음과 같다.
- TypeScript 타입만으로는 런타임 데이터 오류를 막을 수 없었고
- API 응답을 완전히 신뢰할 수 없는 환경이었으며
- 런타임 검증과 타입 추론을 동시에 가져가고 싶었다.
2. zod 활용법
zod 사용법은 간단하다.
zod 스키마를 선언하고 schema.parse로 타입 구조가 일치하는지 확인한다.
zod는 주로 데이터의 입/출력 경계에서 값을 검증하는데 활용한다.
유저의 입력값 검증 (FE, BE)
FE와 BE의 경계 검증 (FE, BE)
DB에서 BE의 경계 검증 (BE)
등에 주로 활용하고 나는 특히 DB에서 들어오는 값을 검증하는게 핵심이었다.
2-1 zod 스키마 선언
import { z } from "zod";
// 스키마 정의
export const SampleSchema = z.object({
id: z.string(),
name: z.string(),
energyUsage: z
.number()
.transform((value) => Number(value.toFixed(2)))
.meta({ label: '에너지 사용량', unit: 'kWh' }),
});
// 스키마에서 타입 추출
export type Sample = z.infer<typeof SampleSchema>;
z.infer로 타입을 선언하고 활용할 수 있다.
2-2 API route에서 DB 데이터 검증
import { z } from "zod";
import { prisma } from "@/lib/prisma";
import { sampleSchema } from "@/schemas/sample";
// Next.js API Route
export async function GET() {
const raw = await prisma.sample.findMany();
const devices = z.array(sampleSchema).parse(raw);
// 이하 생략
}
2-3 API 응답값 사용
const res = await fetch("/api/sample");
const samples: Sample[] = await res.json();
3. zod 도입 시 고민한 부분 (zod + prisma)
3-1 zod와 prisma 공존
기존에 Prisma를 사용하고 있었기 때문에,
DB 조회 결과에 대해서는 `prisma/client`에서 제공하는 모델 타입을 활용하고 있었다.
장치 데이터에 대해 zod 스키마와 prisma 모델 타입이
일부 중복되는 부분이 있어서 zod를 도입하는게 맞는지 고민했다.
결론적으로는 둘의 역할이 다르고 zod도입의 장점이 더 크다고 판단해서 중복을 감수했다.
- Prisma 모델 타입: DB 조회 결과의 타입 표현
- Zod 스키마: 경계에서의 런타임 검증 및 정규화
만약 drizzle을 사용했다면
DB schema를 기반으로 zod 스키마를 바로 활용할 수 있어
중복을 더 줄일 수 있었을 것이라 생각한다.
3-2 zod의 meta 활용법
zod v4에서 스키마에 추가 정보를 부여할 수 있는 `meta()` 기능이 새로 추가됐다.
이전에도 label이나 metadata를 스키마와 함께 관리하고 싶다는 요구와 이슈들이 꾸준히 있어서 이번 버전에서 추가된 듯 하다.
단, meta 정보는 스키마 인스턴스에 결합되며,
인스턴스별 저장소를 가지기 때문에
`extend` 등을 통해 스키마를 확장할 경우 meta가 자동으로 상속되지 않음을 주의해야 한다.
`zod-to-json-schema`와 함께 주로 활용하는 듯 하지만 나는 레이블 관리에 활용했다.
meta에 한글 레이블을 저장한 뒤 유틸 함수로 추출해서 캐싱하여 사용했다.
이 과정에서 스키마와 UI 정보가 강하게 결합되는 것 같아 이렇게 활용하는게 맞는지 많이 고민했다.
그럼에도 meta 방식을 선택한 이유는
기존 레이블맵의 관리·수정 비용이 매우 컸고,
추후 여러 앱에서 장치 종류가 늘어날 경우
유지보수 비용에 대한 팀 내부 우려가 컸기 때문이다.
이 경험을 통해,
Common Practice 관점에서의 정답 여부도 중요하지만,
실제 개발·운영 중인 웹앱의 특성과 팀의 상황에 맞는 선택을 하는 것 또한
매우 중요하다는 점을 배웠다.
관련이슈 링크:
https://github.com/colinhacks/zod/issues/273
zod meta 기능:
https://zod.dev/metadata?id=meta
Metadata and registries | Zod
Attaching and manipulatinvg metadata on Zod schemas
zod.dev
'next.js' 카테고리의 다른 글
| Nextjs hydration mismatch error(하이드레이션) (0) | 2024.09.29 |
|---|---|
| Nextjs 미들웨어 토큰 유효성 검증, 미들웨어 런타임 (0) | 2024.09.22 |
| API Route에서 jwt 토큰 유효성검증, next/headers cookies 함수 (0) | 2024.09.10 |
| NextAuth.js, Lucia auth, 토큰과 세션의 특징, 라이브러리 선택 시 유의할 점 (0) | 2024.09.05 |
| vercel 배포 느림 해결 (vercel region 지역 설정) (0) | 2024.08.27 |