Skip to main content

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.

server/api/_id@string/index.ts
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;
};
}>;
server/api/_id@string/controller.ts
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.

note

Frourio automatically creates validators.ts when a directory with a path parameter in its name is created.

server/api/_id@string/validators.ts
import { defineValidators } from './$relay';
import { z } from 'zod';

export default defineValidators(() => ({
params: z.object({ id: z.string() }),
}));

Function defineValidators

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.

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.

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.

validator.ts
type HogeBase = {
hoge: string;
};

export const hoge = z.object({
hoge: z.string(),
}) satisfies z.ZodType<HogeBase>;

export type Hoge = z.infer<typeof hoge>;
caution

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