💤

zodとgenericでexpressのrequest.bodyにタイプをつける

2023/04/08に公開

はじめに

こんにちは。
full-stack developerを目指しているShenです。
最近zodがすこく流行っていて、zodでexpressのrequestにタイプをつけるをやってみました。

問題

例えば、以下のようなエンドポイントがあって、idが提供されていないでしたら、400でエラーを返したいとしています。

app.post('/user', (req: express.Request, res: express.Response) => {
  const id = req.body.id;
  if (!id) {
    res.status(400).send('no id provided error');
  }
  res.status(200).send('done');
});

req.body.idにhoverすると、anyタイプになっていることがわかります。

req.body.idが文字列で、長さは1~10なんかしらの入力制限をしたいとき、class-validatorを使っていると思うが、zodでかなり実装時間を省けると思う。

zodで解決

まずはgenericでエラーをハンドルできるようなcallbackメソッドを作成する。

const requestBodyLayer =
  <Tbody>(
    schema: z.Schema<Tbody>,
    callback: (
      req: express.Request<any, any, Tbody, any>,
      res: express.Response
    ) => void
  ) =>
  (req: express.Request, res: express.Response) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      return res.status(400).send(result.error.issues[0].message);
    }
    
    return callback(req, res);
  };

req: express.Request<any, any, Tbody, any>はなぜ3番目にgenericタイプを付与したか?expressのタイプファイルを確認すると、ReqBodyは元々anyだったので、bodyのタイプをzodで定義したタイプに変更する

そして、/user専用のユーザーリクエストハンドラを生やす。

const userHandler = requestBodyLayer(
  z.object({ id: z.string().min(1).max(10) }),
  (req, res) => {
    const id = req.body.id;
    res.status(200).send(`id : ${id}`);
  }
);

app.post('/user', userHandler);

上記のハンドラが追加されたことにより、req.bodyがタイプでガードできるようになりました。しかも、runtimeタイプチェックになります。

bodyに項目を追加したい場合、z.object内でプロパティを追加するだけで良い。

 z.object({
    id: z.string().min(1).max(10),
    name: z.string().min(1).max(255),
    age: z.number(),
  }),

以下のように、自動補完機能も効くようになって、開発速度が早くなるわけだ。

終わりに

以上はzodとgenericを使って、expressのrequest.bodyにタイプをつけるようにするサンプルになります。様々な方法があると存じますが、ぜひこれも一度試していただけたらと・・・

Discussion