Serverless Express + API Gateway + Lambda の構成で、クエリパラメーターが正の整数であるか zod でバリデーションしてみた(AWS CDK)
こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。
zod とは TypeScript 向けに作られたスキーマバリデーションライブラリです。
今回は、Serverless Express + API Gateway + Lambda + AWS CDK で実装した REST API で、クエリパラメーターが正の整数であるか zod でバリデーションしてみました。
試してみた
パッケージのインストール
zod を含めた必要なパッケージを npm でインストールします。
npm i @codegenie/serverless-express express cors zod npm i -D @types/express
Lambda ハンドラー(バリデーション)
下記ではクエリパラメータのスキーマを zod で定義し、バリデーションする実装をしています。
import { Request } from 'express'; import * as zod from 'zod'; const headers = { 'Access-Control-Allow-Headers': 'Content-Type,Authorization', 'Access-Control-Allow-Methods': 'OPTIONS,POST,PUT,GET,DELETE', 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', }; // クエリパラメーターのバリデーション const queryScheme = zod.object({ max_items: zod .string() .optional() .refine((val) => val === undefined || /^(?!0)\d+$/.test(val), { message: '正の整数の文字列を指定してください', }) .transform((val) => (val === undefined ? undefined : Number(val))) .refine((num) => num === undefined || num <= 10, { message: '1以上10以下の数値を指定してください', }), }); export const companiesGetHandler = (event: Request) => { const query = queryScheme.safeParse(event.query); if (!query.success) { return { statusCode: 400, body: JSON.stringify({ message: query.error.message }), headers, }; } return { statusCode: 200, body: JSON.stringify([{ value: query.data.max_items }]), headers, }; };
上記実装では max_items
クエリパラメータに対して下記のバリデーションを行っています。
string()
では文字列であることをバリデーションします。Serverless Express で受け取るクエリパラメーターは必ず文字列型となります。optional()
ではクエリパラメータが省略可能であることをバリデーションします。- 1 つ目の
refine()
では正の整数であることをバリデーションします。^(?!0)\d+$
は正の整数の正規表現です。 transform()
ではクエリパラメータが省略された場合にundefined
に変換し、それ以外の場合にはNumber
に変換します。- 2 つ目の
refine()
では 1 以上 10 以下であることをバリデーションします。数値の上限を設けない場合はこの行は不要です。
これにより、取得するアイテム数の上限の指定を 1 以上 10 以下の数値に制限しています。
ちなみに string()
と optional()
は下記のように union()
で記述を置き換えることができます。
const queryScheme = zod.object({ max_items: zod .union([zod.string(), zod.undefined()]) .refine((val) => val === undefined || /^(?!0)\d+$/.test(val), { message: '正の整数の文字列を指定してください', }) .transform((val) => (val === undefined ? undefined : Number(val))) .refine((num) => num === undefined || num <= 10, { message: '1以上10以下の数値を指定してください', }), });
Lambda ハンドラー(ルーティング)
Express によるルーティングの実装です。GET /companies
へのリクエストを上記の companiesGetHandler
で処理します。
import serverlessExpress from '@codegenie/serverless-express'; import cors from 'cors'; import express, { Request, Response } from 'express'; import { companiesGetHandler } from './companies-get-handler'; const app = express(); app.use(cors()); app.use(express.json()); app.get('/companies', (req: Request, res: Response): void => { const response = companiesGetHandler(req); res.status(response.statusCode).header(response.headers).send(response.body); }); export const handler = serverlessExpress({ app });
動作確認
クエリパラメータを指定しない場合はバリデーションを通過し、正常にレスポンスが返されます。
$ curl -X GET -H "Content-Type: application/json" \ https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies [{}]
クエリパラメータ max_items
に 1 以上 10 以下の数値を指定した場合もバリデーションを通過し、正常にレスポンスが返されます。
$ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=1" [{"value":1}] $ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=10" [{"value":10}]
クエリパラメータ max_items
に 1 未満または 0 の数値を指定した場合はバリデーションエラーとなり、1 つ目の refine() のエラーメッセージが返されます。
$ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=-1" {"message":"[\n {\n \"code\": \"custom\",\n \"message\": \"正の整数の文字列を指定してください\",\n \"path\": [\n \"max_items\"\n ]\n }\n]"} $ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=0" {"message":"[\n {\n \"code\": \"custom\",\n \"message\": \"正の整数の文字列を指定してください\",\n \"path\": [\n \"max_items\"\n ]\n }\n]"}
クエリパラメータ max_items
に 11 以上の数値を指定した場合もバリデーションエラーとなり、2 つ目の refine() のエラーメッセージが返されます。
$ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=11" {"message":"[\n {\n \"code\": \"custom\",\n \"message\": \"1以上10以下の数値を指定してください\",\n \"path\": [\n \"max_items\"\n ]\n }\n]"}
クエリパラメータ max_items
に数値以外の文字列を指定した場合もバリデーションエラーとなり、1 つ目の refine() と 2 つ目の refine() のエラーメッセージが返されます。どちらかに制限することも可能ですが、記述が複雑となるため、今回は両方のエラーメッセージを返すようにしています。
$ curl -X GET -H "Content-Type: application/json" \ "https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies?max_items=aaa" {"message":"[\n {\n \"code\": \"custom\",\n \"message\": \"正の整数の文字列を指定してください\",\n \"path\": [\n \"max_items\"\n ]\n },\n {\n \"code\": \"custom\",\n \"message\": \"1以上10以下の数値を指定してください\",\n \"path\": [\n \"max_items\"\n ]\n }\n]"}
デフォルト値を返すようにする
次のように default()
でデフォルト値を返すようにすることもできます。
const queryScheme = zod.object({ max_items: zod .union([zod.string(), zod.undefined()]) .default('5') .refine((val) => val === undefined || /^(?!0)\d+$/.test(val), { message: '正の整数の文字列を指定してください', }) .transform((val) => (val === undefined ? undefined : Number(val))) .refine((num) => num === undefined || num <= 10, { message: '1以上10以下の数値を指定してください', }), });
クエリパラメータ max_items
を省略してリクエストすると、デフォルト値が返されました。
$ curl -X GET -H "Content-Type: application/json" \ https://arf1rcsln7.execute-api.ap-northeast-1.amazonaws.com/v1/companies [{"value":5}]
おわりに
Serverless Express + API Gateway + Lambda + AWS CDK で実装した REST API で、クエリパラメーターが正の整数であるか zod でバリデーションしてみました。
Serverless Express で実装した場合は、クエリパラメータの値が必ず文字列型になる挙動で少しハマりました。どなたかの参考になれば幸いです。
参考
以上