こんにちは、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 で定義し、バリデーションする実装をしています。
src/companies-get-handler.ts
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
で処理します。
src/rest-api-router.ts
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 で実装した場合は、クエリパラメータの値が必ず文字列型になる挙動で少しハマりました。どなたかの参考になれば幸いです。
参考
以上