express-request-validation
SkillDev toolsConventions for validating PrairieLearn Express request parameters, query strings, bodies, and `__action` forms with Zod.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the express-request-validation skill
What this skill tells your AI
The instructions your AI receives, as published by prairielearn/prairielearn in .agents/skills/express-request-validation/SKILL.md and read by ahel’s review.
Request boundaries
- Define request schemas at module scope unless they genuinely depend on request-time values. Prefer a static structural schema followed by a semantic check over constructing a schema per request.
- Parse every request source a handler consumes before database queries or other work. After parsing, use only the parsed values; do not read the corresponding
req.params,req.query, orreq.bodyvalues again. - Use
IdSchemafrom@prairielearn/zodfor PrairieLearn database IDs in params, query strings, and bodies. Do not validate them as arbitrary strings or numbers. External-system identifiers that are not PrairieLearn database IDs should use schemas appropriate to those systems. - Treat parsed request objects as boundary data, not as parameter bags. Pass explicit fields to database queries, model functions, and other downstream APIs instead of passing
params,query, orbodywholesale; this keeps each callsite's contract visible and avoids unused-parameter failures when request schemas grow. - Use
parseRequestwhen validating multiple sources together. UseparseRequestParams,parseRequestQuery, orparseRequestBodywhen validating only one source. - Express cannot verify that a params schema matches the router path. Check the parent mount path as well as the local route and ensure every
ParamsSchemakey matches an actual:parameter.
Schema naming
- Name schemas at the boundary where they are consumed:
PostBodySchemaforparseRequestBodyandPostRequestSchemasforparseRequestin a POST handler. - Inline a source schema inside
PostRequestSchemaswhen it is used only there. ExtractParamsSchema,QuerySchema, orPostBodySchemawhen the same schema is reused by another handler, parser, inferred type, or composed schema. - The combined request schema object is plural because it contains schemas keyed by source.
- If a file has multiple handlers that would make these names ambiguous, add a concise operation prefix, such as
CreatePostBodySchemaorArchivePostRequestSchemas. - For a discriminated union, action-specific branch schemas may use names such as
InviteUidsBodySchema, while the complete union remainsPostBodySchema.
__action forms
- Use
__actiononly when one POST endpoint genuinely multiplexes multiple operations. A single-operation endpoint should use a plain body schema and a submit button withoutname="__action". - Model a multi-operation body as a top-level
z.discriminatedUnion('__action', ...), parse it before dispatch, and switch on the parsedbody.__action. - An unrecognized top-level
__actiondiscriminator is automatically reported asunknown __action: <value>by the request-validation helpers. Other body validation failures remainInvalid request body.
Example
const ParamsSchema = z.object({ course_id: IdSchema });
const PostRequestSchemas = {
params: ParamsSchema,
body: z.object({ name: z.string().min(1) }),
};
router.post(
'/',
typedAsyncHandler<'plain'>(async (req, res) => {
const { params, body } = parseRequest(req, PostRequestSchemas);
await updateCourse(params.course_id, body.name);
res.redirect(req.originalUrl);
}),
);
Signals
- GitHub stars
- 500
- Forks
- 393
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
express-request-validation- Source
- github.com/prairielearn/prairielearn