From 101898ad214b99379bbe3d3465604004479c9ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=A9=EB=B9=88?= Date: Tue, 27 Feb 2024 18:49:32 +0900 Subject: [PATCH] =?UTF-8?q?Post:=20Zod=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20=EC=84=A0=EC=96=B8=EC=A0=81=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B2=80=EC=A6=9D=20=EA=B8=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/{Develop => TypeScript}/msw-usage.mdx | 10 +- posts/TypeScript/zod-usage.mdx | 265 ++++++++++++++++++ .../msw-usage/from-service-worker.png | Bin .../msw-usage/msw-get-response.png | Bin .../msw-usage/msw-post-response.png | Bin .../{develop => typescript}/msw-usage/msw.png | Bin .../msw-usage/thumbnail.png | Bin .../posts/typescript/zod-usage/thumbnail.png | Bin 0 -> 5181 bytes .../images/posts/typescript/zod-usage/zod.png | Bin 0 -> 13706 bytes 9 files changed, 270 insertions(+), 5 deletions(-) rename posts/{Develop => TypeScript}/msw-usage.mdx (96%) create mode 100644 posts/TypeScript/zod-usage.mdx rename public/images/posts/{develop => typescript}/msw-usage/from-service-worker.png (100%) rename public/images/posts/{develop => typescript}/msw-usage/msw-get-response.png (100%) rename public/images/posts/{develop => typescript}/msw-usage/msw-post-response.png (100%) rename public/images/posts/{develop => typescript}/msw-usage/msw.png (100%) rename public/images/posts/{develop => typescript}/msw-usage/thumbnail.png (100%) create mode 100644 public/images/posts/typescript/zod-usage/thumbnail.png create mode 100644 public/images/posts/typescript/zod-usage/zod.png diff --git a/posts/Develop/msw-usage.mdx b/posts/TypeScript/msw-usage.mdx similarity index 96% rename from posts/Develop/msw-usage.mdx rename to posts/TypeScript/msw-usage.mdx index 1fa6edc..6639025 100644 --- a/posts/Develop/msw-usage.mdx +++ b/posts/TypeScript/msw-usage.mdx @@ -2,7 +2,7 @@ title: MSW를 사용하여 API Mocking 하기 description: 프론트엔드 측에서 백엔드 API를 Mocking하여 더 효율적으로 개발을 해봅시다. createdAt: 2023-12-04 -thumbnail: /images/posts/develop/msw-usage/thumbnail.png +thumbnail: /images/posts/typescript/msw-usage/thumbnail.png --- 안녕하세요! 오늘은 백엔드 API를 Mocking 해주는 라이브러리인 `MSW`란 무엇이며, 사용방법에 대해서 알아보도록 하겠습니다. @@ -17,7 +17,7 @@ thumbnail: /images/posts/develop/msw-usage/thumbnail.png 하지만 이를 보완하기 위해서 서버의 API가 개발 완료될때까지 클라이언트단에서 **모의로 동작**할 수 있게끔 설정을 해두면 어떨까요? 마치 실제 API와 통신이 이루어지는것처럼 코드를 작성할 수 있고, 추후에는 코드를 최소한으로 건드리며 실제 API로 바꿔칠 수 있다면 프론트엔드 개발자들은 시간을 더 효율적으로 사용할 수 있습니다. -![Mocking Service Worker](/images/posts/develop/msw-usage/msw.png) +![Mocking Service Worker](/images/posts/typescript/msw-usage/msw.png) > 공식문서 바로가기: https://mswjs.io @@ -189,11 +189,11 @@ export default App; 과연 API의 응답값이 저희가 임의로 반환한 값으로 응답이 올까요? -![MSW Mocking 실행 결과](/images/posts/develop/msw-usage/msw-get-response.png) +![MSW Mocking 실행 결과](/images/posts/typescript/msw-usage/msw-get-response.png) 원래 JSONPlaceholder API를 호출했을때 임의의 100개 데이터를 전달해주는 반면, API Mocking을 설정해두니 저희가 임의로 반환한 데이터가 올바르게 응답되었네요! 동시에 네트워크 탭을 보시면 `200 OK`가 뜨면서 서비스 워커에서 반환되었음을 알 수 있습니다. -![서비스 워커 반환](/images/posts/develop/msw-usage/from-service-worker.png) +![서비스 워커 반환](/images/posts/typescript/msw-usage/from-service-worker.png) ### 2-3. POST Mocking @@ -279,7 +279,7 @@ POST 요청에 대해서도 API Mocking이 성공했을까요? 네! JSONPlaceholder API로 요청하는 데이터를 Mocking 서버에서 가로챈 후, 응답을 성공적으로 받을 수 있었습니다. -![POST API Mocking](/images/posts/develop/msw-usage/msw-post-response.png) +![POST API Mocking](/images/posts/typescript/msw-usage/msw-post-response.png) API Mocking이 적용되는 과정을 아래처럼 요약할 수 있습니다. diff --git a/posts/TypeScript/zod-usage.mdx b/posts/TypeScript/zod-usage.mdx new file mode 100644 index 0000000..9ef1b2f --- /dev/null +++ b/posts/TypeScript/zod-usage.mdx @@ -0,0 +1,265 @@ +--- +title: zod를 사용하여 선언적으로 데이터 검증하기 +description: 선언적 데이터 검증 라이브러리 zod를 사용해봅시다. +createdAt: 2024-02-27 +thumbnail: /images/posts/typescript/zod-usage/thumbnail.png +--- + +안녕하세요! 오늘은 **선언형 데이터 검증 라이브러리**인 `zod`란 무엇이며, 어떻게 사용하는지를 알아보겠습니다. + +## 1. 기존의 데이터 검증 🤔 + +여러분이 진행하는 프로젝트에서 사용자로부터 **많은 입력값(회원가입, 글 작성 등등)**을 받는경우, 해당 입력값에 대해서 `검증`하는 절차가 작성되는 경우가 많습니다. + +예를들어, 아래의 규칙을 따르는 데이터가 있다고 가정해봅시다. + +```typescript +/* + email: 필수 입력, 이메일 정규식 맞춰야함 + name: 필수 입력 + password: 필수 입력, 비밀번호 정규식 맞춰야함 + interestCategories: 2개 이상, 5개 이하로 선택해야함 +*/ + +interface SignUpDto { + email: string; + name: string; + password: string; + interestCategories: number[]; +} +``` + +위의 데이터를 검증시, 선언형 데이터 검증 라이브러리를 사용하지 않는경우에는 아래처럼 코드를 작성할 수 있습니다. + +```typescript +interface ValidateResult { + succeed: boolean; + message: string; +} + +const validateSignUpDto = (signUpDto: SignUpDto): ValidateResult => { + const { email, name, password, checkPassword, interestCategories } = signUpDto; + + if (email.trim().length <= 0) { + return { + succeed: false, + message: '이메일을 입력해주세요.', + }; + } else if (이메일 정규식에 안맞다면) { + return { + succeed: false, + message: '이메일 정규식에 맞게 입력해주세요.' + }; + } else if (name.trim().length <= 0) { + return { + succeed: false, + message: '이름을 입력해주세요.', + }; + } else if (password.trim().length <= 0) { + return { + succeed: false, + message: '비밀번호를 입력해주세요.', + }; + } else if (비밀번호 정규식에 안맞다면) { + return { + succeed: false, + message: '비밀번호 정규식에 맞게 입력해주세요.', + }; + } else if (interestCategories.length <= 2) { + return { + succeed: false, + message: '관심분야를 2개 이상 선택해주세요.', + }; + } else if (interestCategories.length > 5) { + return { + succeed: false, + message: '관심분야는 5개 이하로 선택가능합니다.', + }; + } + + return { + succeed: true, + message: '성공', + }; +}; +``` + +위처럼 if문을 사용한 검증 함수를 만들고, `React`를 예시로 했을때 **컴포넌트나 커스텀 훅 등에서 함수를 불러와서 데이터를 검증**할 수 있습니다. 기능상으로는 문제가 없습니다. + +
+ +그러나 위의 회원가입 기능에서 필요한 데이터가 추가되고, 그에따른 검증식도 늘어나면 어떻게 될까요? 검증 함수는 **점점 코드량이 증가하여 가독성이 매우 떨어지게 됩니다.** 이는 `유지보수`를 어렵게 만들 수 있는 코드가 되는것이죠. + +이전까지 저도 저렇게 적었을때는 검증에 대한 코드를 작성하는것이 정말 귀찮았던 것 같습니다. 그래서 검증 코드를 잘 작성할 수 있는 방법을 찾아보다가, `zod` 라이브러리를 알게 되었습니다. + +## 2. zod 검증 라이브러리 🔨 + +위처럼 모든 검증 과정을 작성한 코드(`명령형`)의 문제점을 해결하기 위해서 **간결하게 선언적으로 작성** 가능한 `zod` 라이브러리가 등장했습니다. + +![zod 검증 라이브러리](/images/posts/typescript/zod-usage/zod.png) + +> zod 라이브러리 공식 문서 [바로가기](https://zod.dev) + +`zod` 라이브러리의 대표적인 장점을 몇가지 뽑아보자면 + +1. 선언적으로 데이터 검증 (코드 간결) +2. 타입스크립트 지원 +3. 스키마 -> 타입 변환 지원 (중복 코드 최소화) + +위의 장점들을 뽑을 수 있습니다. 그렇다면 `zod`를 코드에서 어떻게 작성할 수 있는지 알아봅시다. + +> 오늘 소개드릴 `zod` 외에도 [yup](https://github.com/jquense/yup), [joi](https://joi.dev) 등의 검증 라이브러리도 훌륭하다고 생각하기에, 한번쯤 보시는걸 추천합니다. 😃 + +## 3. 사용방법 🔍 + +가장 먼저 npm, yarn 등의 패키지 매니저로 `zod`를 설치해줍니다. + +```shell +npm install zod +``` + +또는 + +```shell +yarn add zod +``` + +기본적으로 `zod` 패키지에서 `z` 변수를 `import`하여 기능을 사용할 수 있습니다. + +예를들어, 데이터의 타입이 string일때는 `z.string()`, number일때는 `z.number()` 등으로 작성할 수 있습니다. + +```typescript +import { z } from 'zod'; + +const stringSchema = z.string(); + +const numberSchema = z.number(); +``` + +### 3-1. 데이터 검증 + +`zod`는 선언적 데이터 검증이 주 특징입니다. 그래서 데이터 검증은 핵심기능으로도 볼 수 있습니다. + +특정한 구조의 데이터를 검증하려면 `zod`의 `스키마(Schema)`를 선언해야합니다. 스키마에서는 **필드별 데이터 타입, 규칙을 선언적으로 정의하여 구성**합니다. zod에서 제공하는 규칙용 메소드들은 다양한데요. 이번 글에서는 대표적으로 많이 사용되는 메소드들만 알려드리겠습니다. + +
+ +이번 글에서 설명하지 않은 검증 메소드들에 대해서는 [zod 공식문서](https://zod.dev)에 설명이 매우 잘되어있으니 보시는걸 추천합니다. + +
+ +위에서 예시로 설명드린 회원가입 예제를 `zod`로 변환하여 작성해보겠습니다. 코드에 대한 설명은 주석으로 적어두었습니다. + +> zod는 함수형 프로그래밍 형태로 사용되는점을 미리 알아두시면 좋습니다. + +```typescript +import { z } from 'zod'; // zod 패키지로부터 import + +// 스키마 선언 +// 회원가입과 같은 정보는 일반적으로 객체형태로 관리하기에, 객체임을 나타내는 z.object()로 감싸줍니다. +export const signUpSchema = z.object({ + // 문자열 형태로 필수(1글자 이상), 올바른 이메일 형식 + email: z + .string() + .trim() + .min(1, '이메일을 입력해주세요.') + .email('올바른 이메일을 입력해주세요.'), + + // 문자열 형태로 필수 (1글자 이상) + name: z.string().trim().min(1, '이름을 입력해주세요.'), + + // 문자열 형태로 필수(1글자 이상), 8자 이상의 알파벳으로 구성 + password: z + .string() + .trim() + .min(1, '비밀번호를 입력해주세요.') + .regex(/b[a-zA-Z]{8,}/b, '8자 이상의 알파벳으로 구성되어야 합니다.'), + + // 숫자 배열 형태로 필수, 배열 길이가 2이상, 5이하로 이루어져야함 + interestCategories: z + .array(z.number()) + .min(2, '2개 이상의 카테고리를 선택해주세요.') + .max(5, '5개 이하로 카테고리를 선택해주세요.'), +}); +``` + +`zod`를 사용하면 데이터에 대한 검증 코드를 위처럼 간결하게 작성할 수 있습니다. 이전에 if문을 사용한 검증 코드와 비교했을때 정말 간결함을 알 수 있습니다. + +위 코드에서 `min`, `max`, `email`과 같은 검증 메소드를 사용했음을 알 수 있는데요. 숫자 인자는 **만족하는 데이터 길이**이고, 문자열 인자는 **데이터를 만족하지 않을때 설정할** `오류 메세지`입니다. + +
+ +이제 위의 스키마를 사용하여 데이터를 검증할 수 있는데요. 검증을 하는 방법은 대표적으로 `parse` 또는 `safeParse` 메소드를 사용하여 검증할 수 있습니다. + +> `parse`: 검증 오류시, throw Error (try / catch 처리 필요) +> `safeParse`: 검증 오류시 오류의 정보를 담는 객체 반환 + +해당 메소드에 넘긴 데이터가 검증식에 맞지 않을경우, 실패합니다. + +```typescript +try { + signUpSchema.parse({ + email: 'asdf@gmail.com', + name: '권용빈', + password: 'asdfasdf', + interestCategories: [2], // 카테고리를 한개만 전달했기에, catch문으로 넘어간다. (오류) + }); +} catch (error) { + // ZodError +} + +// 올바른 형태의 데이터 전달, 성공 +const parsed = signUpSchema.safeParse({ + email: 'asdf@gmail.com', + name: '권용빈', + password: 'asdfasdf', + interestCategories: [2, 3, 4], +}); + +console.log(parsed.success); // true + +// 올바르지 않은 형태의 데이터 전달, 실패 +const parsed2 = signUpSchema.safeParse({ + email: 'asdf', // 올바르지 않은 이메일 형식 + name: '권용빈', + password: 'asdfasdf', + interestCategories: [2, 3, 4], +}); + +if (!parsed.success) { + console.log(parsed.error.errors[0].message); // 가장 첫번째 오류의 메세지 출력 +} +``` + +> 🙋‍♂️ `safeParse` 메소드의 `success` 필드가 false로 추론될때만 `error` 필드에 접근이 가능합니다. + +### 3-2. 스키마 -> 타입 변환 + +이번에는 `zod`로 선언한 스키마를 **타입스크립트의 타입으로 변환**하는 방법을 알아보겠습니다. 검증 스키마를 선언한 정보를 타입으로 변환이 가능하다면, 필드 정보를 중복으로 적을일이 없기때문에 개발자 입장에서 더 편합니다. + +
+ +타입으로 변환하는 방법은 `z.infer` 타입을 사용하여 변환할 수 있습니다. + +```typescript +import { z } from 'zod'; + +export type SignUpDto = z.infer; + +/* + email: string; + name: string; + password: string; + interestCategories: number[]; +*/ +``` + +## 4. 마치며 📌 + +오늘은 `zod` 라이브러리란 무엇이며, 어떻게 사용할 수 있는지를 간단하게 알아보았습니다. + +저는 `zod`를 도입해보면서 코드를 더 간결하게 줄일 수 있어서 매우 좋았었는데요. 데이터 검증코드에 대해 고민이 있으시다면 한번 사용해보시는것을 추천합니다. + +
+ +이상으로 글을 마치겠습니다. 읽어주셔서 감사합니다! diff --git a/public/images/posts/develop/msw-usage/from-service-worker.png b/public/images/posts/typescript/msw-usage/from-service-worker.png similarity index 100% rename from public/images/posts/develop/msw-usage/from-service-worker.png rename to public/images/posts/typescript/msw-usage/from-service-worker.png diff --git a/public/images/posts/develop/msw-usage/msw-get-response.png b/public/images/posts/typescript/msw-usage/msw-get-response.png similarity index 100% rename from public/images/posts/develop/msw-usage/msw-get-response.png rename to public/images/posts/typescript/msw-usage/msw-get-response.png diff --git a/public/images/posts/develop/msw-usage/msw-post-response.png b/public/images/posts/typescript/msw-usage/msw-post-response.png similarity index 100% rename from public/images/posts/develop/msw-usage/msw-post-response.png rename to public/images/posts/typescript/msw-usage/msw-post-response.png diff --git a/public/images/posts/develop/msw-usage/msw.png b/public/images/posts/typescript/msw-usage/msw.png similarity index 100% rename from public/images/posts/develop/msw-usage/msw.png rename to public/images/posts/typescript/msw-usage/msw.png diff --git a/public/images/posts/develop/msw-usage/thumbnail.png b/public/images/posts/typescript/msw-usage/thumbnail.png similarity index 100% rename from public/images/posts/develop/msw-usage/thumbnail.png rename to public/images/posts/typescript/msw-usage/thumbnail.png diff --git a/public/images/posts/typescript/zod-usage/thumbnail.png b/public/images/posts/typescript/zod-usage/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..1d8b4501650e5b708e4903b9e1be3edcc13a18d8 GIT binary patch literal 5181 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 literal 0 HcmV?d00001 diff --git a/public/images/posts/typescript/zod-usage/zod.png b/public/images/posts/typescript/zod-usage/zod.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e7d00740ffa33419ad17f453ca0e84ce65a852 GIT binary patch literal 13706 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