Галоўная / Артыкулы / Аднаўленне аднаго шымата Zod між фронтэндам на React і бэкендам на Node.

Аднаўленне аднаго шымата Zod між фронтэндам на React і бэкендам на Node.

Дазвольце даклэ на адзін шымат Zod можна пераверыць формы React, адпаведныя адпаведзіцеў API, тэлы запытак Express і зменныя сераўіса, адночасна ствараючы адпаведныя типы TypeScript.

1536 слоў

Параболіка неабяжна ў кожным прыкладзе практычнага выкарыстоўвання, але команды часта прымушаны ўстановіць яе поступова — адна бібліятэка для фронтэнда, іншая — для бэкэнда, а тыя ж правілы копіююцца і вставляюцца ў разныя месцы. Zod стаў падабаным средства між разработчыкамі JavaScript і TypeScript самэўсёле таму, што ён узбегае такой хаос: вы пішыце адну схему, і гэтая схема як пераканальвае вашы данні, так і стварае адпаведны тип TypeScript, які можна выкарыстоўваць аднойчы і ў браузеры, і на серверы.

1. Чаму ёсць Zod?

Zod — это бібліятэка схем, створаная з самага пачатку з урахоўваннем TypeScript. Вы описваеце формат сваіх дадзеных аднойчы, і Zod выкарыстоўвае гэты опис для пераканальвання значэнняў у час выконання і для автаматычнага стварэння типу TypeScript — няма неабязковай інтэрфейсу, які трэба пісаць, і няма рызыку таго, што ён не будзе супараднаўацца з вашымі правіламі параболіка.

import { z } from 'zod';
const UserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }

Такій адміністратыўны лямбэт можа адразу паводліваць тры функцыі: ён дакументуе структуру дадзеных, прыменяе яе пад час выканання та забезпечвае статычны тип, на які рэгулююцца редактар і кампайляр.

2. Чаму Zod працюе краща за іншыя альтэрнатывы

Галоўная перадчыннае ўмеласць — автаматычная інферэнція типаў. Бібліятекі на кшталт Yup чы Joi зазвычай вымагаюць, каб вы падтрымвалі схему верыфікацыі разам з рукапісным інтерфейсам на TypeScript, пакладаючыся на тое, што гэтыя два элементы не будуць адступаць одны ад другога праз змены ў кодбазе. Zod цэлкам усуняе гэты рызык: тип выводзіцца безпасова з самай схемы, таму няма нічога, што трэба синхронізаваць.

Zod таксама легкі і не маюць зовнішніх залежнасцей, што робіць яго такім жа зручным у пакетах фронтэнду, дзе важліва меры розмяру, як і ў сервісах Node.js. Яго можна складваць у ланцюг, а API дазваляе выплываць нават складную верыфікацыю — вярстакаваныя об’екты, з’еднання, поля, якіе залежаць адзіна ад другога — так, каб усё заставалася чытаемым, а не ператварылася на клубок спецыяльных дапаможных функцый.

3. Испытанне Zod у дапрыемку React

3.1 Верыфікацыя формы з React Hook Form

Zod можа быць прыўязаны безпасова да React Hook Form за дапамогою пакета @hookform/resolvers.

npm install zod react-hook-form @hookform/resolvers
// components/SignupForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const SignupSchema = z.object({
  name: z.string().min(2, 'Name is too short'),
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});
type SignupData = z.infer<typeof SignupSchema>;
export function SignupForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SignupData>({
    resolver: zodResolver(SignupSchema),
  });
  const onSubmit = (data: SignupData) => {
    console.log('Valid data:', data);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      {errors.name && <p>{errors.name.message}</p>}
      <input {...register('email')} placeholder="Email" />
      {errors.email && <p>{errors.email.message}</p>}
      <input type="password" {...register('password')} placeholder="Password" />
      {errors.password && <p>{errors.password.message}</p>}
      <button type="submit">Sign Up</button>
    </form>
  );
}

Няма ручнага стэрагавання статусу адзіны і няма падвойных заяваў пра типы, якія трэба падтрымваць — адна схема адрабатвае перакананне, задае паведамленні пра адзіны, якія паказваюцца ля кожнага поля, і адразу ж вядома типавуюе об’ект data, які быў адправлены, у TypeScript.

3.2 Перакананне адпаведнасці адказаў API

Zod таксама ўжыткавы на вхіднай стороне вашага прыемніка — напрыклад, калі трэба пераканацца, што даныя, якія вярнуліся з API, дэйсна падходзяць таму, чаго вы чакалі, адколі нельга паспакоўвацца толькі типамі на час кампілявання, ўбеджаючыся в этом.

import { z } from 'zod';
const PostSchema = z.object({
  id: z.number(),
  title: z.string(),
  body: z.string(),
});
const PostsResponseSchema = z.array(PostSchema);
async function fetchPosts() {
  const res = await fetch('/api/posts');
  const json = await res.json();
  const result = PostsResponseSchema.safeParse(json);
  if (!result.success) {
    console.error(result.error.flatten());
    throw new Error('Invalid API response shape');
  }
  return result.data; // fully typed Post[]
}

Этый падчынак дазваляе зафіксав некоректныя або неспадзеваныя адпаведзіць прытаму, перш чым яны спрычыняюць тыхія бяды ў вашай інтэрфейсе.

4. Іспылака Zod у сервере Node.js / Express

4.1 Аверыфікацыя тэла запитоў

npm install zod express
// schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export function validate(schema: ZodSchema) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({ errors: result.error.flatten() });
    }
    req.body = result.data;
    next();
  };
}
// routes/users.ts
import { Router } from 'express';
import { validate } from '../middleware/validate';
import { CreateUserSchema } from '../schemas/user-schema';
const router = Router();
router.post('/users', validate(CreateUserSchema), (req, res) => {
  // req.body is now guaranteed to match CreateUserInput
  const { name, email, age } = req.body;
  res.status(201).json({ name, email, age });
});
export default router;

Гэта налаштаванне дае кожнаму маршруту аднообразны, декларатыўны крок аверыфікацыі, пры чым обработка бядаў централізавана, а не павтараецца як лінейныя if-пераказы ў кожным хэндлеры.

4.2 Аверыфікацыя зменных сэраўису

Адзін недаабраны, але эфектыўны спосаб выкарыстоўвання Zod — гэта перагляд process.env пад час запуску аплікацыі, таким чынам паказваючы некоректную настройку і спрычыняючы негайную паўстаноўку, замест таго каб пазней з’явіўся незрозумелы баг.

// config/env.ts
import { z } from 'zod';
const EnvSchema = z.object({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = EnvSchema.parse(process.env);

Якщо якая-небудзь неабходная зменна відсутня або мае некоректны формат, процес негайна падупае з чытаемым поведамленням пра адказ — гэта набагато лёгкае для діагназавання, чым таўарныя паўстаноўкі, якія выкаліваюцца пад час вызову базы дадзенаў.

5. Асалоджэнне: адна схема, якая выкорыстоўваецца ў всім стэйку

Паколькі схемы Zod — гэта проста значэнні TypeScript, ніч не заважае разместіць іх у спяльнай пакетазе — або ў спяльной папцы ўнутрь монорепо — і пераўтарна выкарыстоўваць точна такую ж схему як у кліенте, так і на серверы.

/packages
  /shared
    /schemas
      user-schema.ts   <-- used by both React app and Express API
  /web (React/Next.js)
  /api (Node/Express)
// packages/shared/schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;

React-аплікацыя выкорыстоўвае гэтую схему для пераканання формы рэўізіі пры яе адправцы. API Express таксама выкорыстоўвае тую ж самую схему для пераверкі прыходзячых дадзенняў. Калі схема зміняецца — напрыклад, падыходзіць новая обавязкова ячэйка — абе слоі адразу ж реагуюць на змяну, а TypeScript негайна выяўляе ўсі код, які ўжо не прыстосаваўся да новага формата. Чыгунам гэта закрывае цэлую клясу бягоў, калі пераверкі з боку кліента і сервера па часе таямна адступаюць адзін ад другога.

6. Найкращыя практыкі

  • Выбірайце safeParse, калі неудача є нормальным, прыемлівым рэзультатам (вхідныя даны формы, адпаведзі трэціх сторон), а parse, який выклекае адказ, застаўляйце для ситуацыяў, якія практычна ніколи не можуць быць некоректнымі, напрыклад, для зменных сяродовышча, якія пераканваюцца пад час запуску.
  • Калі вы володзеце як фронтэндам, так і бэкэндам, трэба зберагаць спяльваныя схемы ў аднам загальным пакете, каб не трэбавалася падтрымка двух копій тых сабе правілаў.
  • Ўжывайце .transform() для чысткі падаўленых дадзенняя як частку самай пераканальні — адсечванне прасоў, прымусовая канвертазія типаў — замест таго, каб пасля гэтага запускаць адзінокы процес нормалізацыі.
  • Для всьога, што вялікі час падтрымваецца схемай, валічыце z.infer працоўней, чым ручна напісаныя інтэрфейсы, каб вашы типы і логіка пераканальні ніколі не выйшлі з сінхрону.
  • У адпаведзях API на паказанні каштоўкаў адправляйце error.flatten() або error.format(), чым код фронтэнду будзе лёгка асоціяваць кожны паказанні з правым полем формы.
  • 7. Заключэнне

    Zod — гэта не проста стандартная бібліятэка для верыфікацыі; яна цалкам зменяе адносы между верыфікацыёй і заданнем типаў. Генеруючы типы TypeScript безпасцельна з схем, якія выкарыстоўваюцца пад час адработкі данных, яна усуняе проблему рознаковасці падзэльных заданняў типаў і правілаў верыфікацыі. Да гэтага яе мінімальны розмах, можлівасць складання з іншымі компонентамі, а таксама стабільнае працаванне як у браузеры, так і ў Node робяць Zod ідеальным выборам для проектаў на TypeScript з архітэктурай full-stack, пабудаваных на React і Node.js.

    Наступныя крокі:

    • Якщо вам трэба генеравацыя дакументацыі OpenAPI безпасцельна з вашых схем, паглядзіце на zod-to-openapi
    • Для стварэння спецыяльной логікі верыфікацыі, якая распрастараецца на калькі полей, выкорыстоўваюце .refine() і .superRefine()
    • Паглядзіце на tRPC — ён апытна выкарыстоўвае схемы Zod для забезпечэння безпекі типаў на всіх етапах працы вашай API

    Спадні матэрыялы

  • Тыхія успехі TypeScript 6 і прычыны, за якімі старшыя разрабоўцы вжываюць JavaScript — Дазвольце пазнакоміцца з функцыямі TypeScript 6, якія застаюцься незаўважанымі, такімі як частаковая калектываванне рэсурсаў і параметры типу const, а таксама з ідіомамі JavaScript, на якія пасляўжаюць старшыя інжынеры ў сваей ўздоўжнай працы.
  • req-guard-lite: Мінімалістычны лімітэр для Express, створаны на базе TypeScript — Дазвольце дакладна разабраць, як працюе лёгкі лімітэр для Express без жадных залежнасцей, ад стандартных настройкаў у памяці да масштабавання за дапамогою Redis і стварэння спецыяльных ключоў.