🧐

フロント側とAPI側でzodを使用してバリデーションチェックをする。

2022/10/11に公開約3,400字2件のコメント

はじめに

業務の中でバリデーションの定義をまとめたいと思い調べていた所
zodというライブラリがあったので使ってみました。

バリデーションスキーマの定義

import { z } from 'zod';

export const userSchema = z.object({
  email: z
    .string()
    .min(1, { message: 'Email is required' })
    .email({ message: 'Invalid email address' }),
  password: z.string().min(1, { message: 'Password is required' }),
});

export type UserSchema = z.infer<typeof userSchema>;

フロント側

フロントではReact Hook Formとzodを組み合わせてバリデーションを行います。

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import axios from 'axios';

import { userSchema, UserSchema } from '../../schema/';

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<UserSchema>({
    resolver: zodResolver(userSchema),
  });

  const onSubmit: SubmitHandler<UserSchema> = (input) => {
    axios
      .post('http://localhost:4000/v1/user', input)
      .then((response) => {
        console.log(response);
      })
      .catch((error) => {
        switch (error.response.status) {
          case 400:
            console.log(error.response.data);
            break;

          default:
            console.log(error);
            break;
        }
      });
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label>EMAIL</label>
          <input {...register('email')} />
          {errors.email?.message && (
            <p className="error_text">{errors.email?.message}</p>
          )}
        </div>

        <div>
          <label>PASSWORD</label>
          <input {...register('password')} />
          {errors.password?.message && (
            <p className="error_text">{errors.password?.message}</p>
          )}
        </div>

        <input type="submit" />
      </form>
    </>
  );
};

export default App;

API側

ルーティングの設定などを行う。

app.ts
import express from 'express';
import cors from 'cors';
import router from './routes/v1/index';

const app = express();

const corsOptions = {
  origin: 'http://localhost:3000',
  credentials: true,
  optionSuccessStatus: 200,
};
app.use(cors(corsOptions));

app.use(express.json());

// ルーティング
app.use('/v1', router);

// 4000ポートで受信
const port = process.env.PORT || 4000;

// APIサーバ起動
app.listen(port);
routes/v1/index.ts
import express from 'express';
import userRouter from './user';

const router = express.Router();

// v1以下のルーティング
router.use('/user', userRouter);

export default router;

zodを使用してバリデーションを行う。

routes/v1/user.ts
import express, { Request, Response, NextFunction } from 'express';
import { ZodError, AnyZodObject } from 'zod';

import { userSchema } from '../../schema';

const router = express.Router();

const validate =
  (schema: AnyZodObject) =>
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      await schema.parseAsync({ ...req.body });
      return next();
    } catch (error) {
      if (error instanceof ZodError) {
        return res.status(400).send({
          error: error.flatten(),
        });
      }
    }
  };

router.post(
  '/',
  // バリデーションチェックが問題なければ次の処理に進む。
  validate(userSchema),
  (req: Request, res: Response): Response => {
    return res.send('done');
  },
);

export default router;

参考

https://github.com/colinhacks/zod

https://note.com/tabelog_frontend/n/n0dc1a2089e58

https://dev.to/franciscomendes10866/schema-validation-with-zod-and-expressjs-111p

Discussion

ご存知かもしれませんが言及されてなかったのでご参考までに。
バックエンドとフロントエンドで連携したい場合はtRPCという選択肢があります。
https://github.com/trpc/trpc

なるほどtRPCというものがあるんですね。
ありがとうございます。勉強になりました。

ログインするとコメントできます