Zod
Frourio uses zod for validation; see the documentation at the link above for a definition of the zod schema.
Two Types of Validators
Frourio provides two types of validators: Controller-level Validators and Directory-level Validators. The elements that can be validated in each are different.
- Controller-level Validators: called at the current endpoints.
params
cannot be validated.query
headers
body
- Directory-level Validators: called at the current and subordinate endpoints. Only
params
can be validated.params
Controller-level
See the Routing page for a reference on defineController()
.
{
[(target key)]?: z.object({ ... })
}
The validators
is an object whose keys are the same as RequestParams
(query
, headers
or body
) and whose values are zod object schemas (z.object()
).
Each schemas are checked for consistency with the API type definition using z.ZodType
.
import { DefineMethods } from 'aspida';
import { Fuga, Res } from '$/types';
export type Methods = DefineMethods<{
get: {
resBody: string;
};
post: {
reqBody: {
hogeString: string;
fugaObject: Fuga;
piyoBoolean?: boolean;
};
query: {
length?: number;
};
resBody: Res;
};
}>;
import { defineController } from './$relay';
import { z } from 'zod';
export default defineController(() => ({
get: () => ({ status: 200, body: 'Hello' }),
post: {
validators: {
query: z.object({
length: z.number().optional(),
}),
body: z.object({
hogeString: z.string(),
fugaObject: z.object({ ... }),
piyoBoolean: z.boolean().optional(),
}),
},
handler: async ({ params: { id }, body, query: { length } }) => {
const res = await createBaz(id, body, length);
if (!res) return { status: 400 };
return { status: 201, body: res };
},
},
}));
Directory-level
To define directory-level validators, create validators.ts
at the top of the target directory, and use the function defineValidators
exported by ./$relay.ts
.
If there are multiple directory-level validators affecting to the endpoint, they are converted to an intersection type using .and
.
Frourio automatically creates validators.ts
when a directory with a path parameter in its name is created.
import { defineValidators } from './$relay';
import { z } from 'zod';
export default defineValidators(() => ({
params: z.object({ id: z.string() }),
}));
Function defineValidators
- Fastify
- Express
Argument Type
- function
(fastify: FastifyInstance) => { params: (zod object schema) }
The actual type definition of (zod object schema)
is z.ZodType<{ (params type) }>
, but there is no need to be aware of this.
This ensures consistency with the API type definitions.
{ (params type) }
The type definition of the URL parameter that the directory has.
It does not include that of the upper-level directory.
If unspecified, it will be string
, but as noted on Automatic Validation page, it is recommended that it be specified.
Argument Type
- function
(app: Express) => { params: (zod object schema) }
The actual type definition of (zod object schema)
is z.ZodType<{ (params type) }>
, but there is no need to be aware of this.
This ensures consistency with the API type definitions.
{ (params type) }
The type definition of the URL parameter that the directory has.
It does not include that of the upper-level directory.
If unspecified, it will be string
, but as noted on Automatic Validation page, it is recommended that it be specified.
Operation with z.infer<>
z.infer<>
generates types for TypeScript from zod schema.
This can be used in the API definition to avoid forgetting to define some parts in the zod schema.
For this use case, it is recommended to export types from validator.ts
.
export const hoge = z.object({
hoge: z.string(),
});
export type Hoge = z.infer<typeof hoge>;
In addition, the satisfies
operator in TypeScript 4.9 and later can be used to improve correctness for complex types.
type HogeBase = {
hoge: string;
};
export const hoge = z.object({
hoge: z.string(),
}) satisfies z.ZodType<HogeBase>;
export type Hoge = z.infer<typeof hoge>;
If you define and export the zod schema and the type in controller.ts
and reference them in index.ts
, typecheck may fail.
Because some Fastify plugin types being any
due to type evaluation order issues.
Ref. (⚠️ Japanese): frouriojs/frourio#276