I use Zod in a middleware to pre-validate the submitted bodies. However I found no consistent way to handle pre-parsed bodies with just the request.body property. To illustrate, here's how I've solved it.
The Zod middleware:
import { Context, Next } from 'oak';
import { AppState } from '../AppState.ts';
import z from 'zod';
export const zodMw = <TBody>(schema: z.ZodObject<any>) => {
return async (ctx: Context<AppState<TBody>>, next: Next) => {
// First we also checked if the request body was actually json,
// But ctx.request.body.json() will just throw a bad request
// if it isn't.
const body = await ctx.request.body.json();
const result = await schema.safeParseAsync(body);
if (!result.success) {
ctx.response.body = result.error.issues;
ctx.throw(400, 'Not a valid request body');
} else {
ctx.state.validatedBody = result.data as TBody;
}
await next();
};
};
Let's say I have this Zod schema:
import { z } from 'zod';
export const v1ExplainPostRequestBodySchema = z.object({
question: z.string(),
});
export type V1ExplainPostRequestBodySchema = z.infer<typeof v1ExplainPostRequestBodySchema>;
I can add the Zod middleware before the route:
return r
.post(
'/v1/explain',
zodMw<V1ExplainPostRequestBodySchema>(v1ExplainPostRequestBodySchema),
v1ExplainPost,
);`
And then the route looks like this:
import { Context } from 'oak';
import { AppState } from '../../../AppState.ts';
import { V1ExplainPostRequestBodySchema } from './RequestBody.ts';
import { explain } from '../../../../run/gh.ts';
export const v1ExplainPost = async (ctx: Context<AppState<V1ExplainPostRequestBodySchema>>) => {
const result = await explain(ctx.state.validatedBody.question);
ctx.response.body = result;
};
This works, the body is nicely typed AND validated, and the validation, schema and route handling are nicely separated.
The only thing that bothers me a bit obviously is the line
ctx.state.validatedBody
I don't want to put the validated body on the state object. I want it to be on the request object.
Would it be possible to have maybe a nullable property on the request where the user can store a validated or resolved body?
Currently the place we await the body resolving must also be the place you use it.
Or am I missing a natural way this can already be done?
I use Zod in a middleware to pre-validate the submitted bodies. However I found no consistent way to handle pre-parsed bodies with just the request.body property. To illustrate, here's how I've solved it.
The Zod middleware:
Let's say I have this Zod schema:
I can add the Zod middleware before the route:
And then the route looks like this:
This works, the body is nicely typed AND validated, and the validation, schema and route handling are nicely separated.
The only thing that bothers me a bit obviously is the line
ctx.state.validatedBodyI don't want to put the validated body on the state object. I want it to be on the request object.
Would it be possible to have maybe a nullable property on the request where the user can store a validated or resolved body?
Currently the place we await the body resolving must also be the place you use it.
Or am I missing a natural way this can already be done?