Schema Validation
Cuple uses Zod for schema validation. It was choosen because of the good defaults and the great type inference. Within Cuple the ZodIssue is also extended for additional type-safety. You can validate, parse, and convert each part of an HTTP request.
Headers validation
Internally it parses express' req.headers object, so you should use the same naming.
Use z.looseObject: every request carries headers you didn't declare (host, user-agent, …), and a strict object would reject them.
Successfully parsed result will be added to data.headers.
builder
.headersSchema(z.looseObject({ authorization: z.string() }))
.get(async ({ data }) => {
data.headers.authorization // this has a string value
});
Body validation
Currently only json requests are supported.
Successfully parsed result will be added to data.body.
.bodySchema(z.strictObject({
user: z.strictObject({
name: z.string().min(3),
age: z.number()
})
}))
Query validation
Note: Query parameters are always strings!
Luckily zod can convert it to number if you use z.coerce.number().
Successfully parsed result will be added to data.query.
// For URL query parsing (e.g.: ?somevar=12)
.querySchema(z.strictObject({ somevar: z.coerce.number() }))
Path validation
Note: Path parameters are always strings!
Luckily zod can convert it to number if you use z.coerce.number().
Successfully parsed result will be added to data.params.
// For URL params parsing
.path("/post/:id")
.paramsSchema(z.strictObject({ id: z.coerce.number() }))
On error
A failed schema answers with status 422, and the result names the part that failed: invalid-body, invalid-query, invalid-params or invalid-headers.
An issue's path is relative to that part, and typed from its schema: for { email: string } it is [] | ["email"], so a field renamed on the server fails to compile on the client. An example response for a body:
{
result: 'invalid-body',
statusCode: 422,
message: "We found some incorrect field(s) during validating the form.",
issues: [{
path: ["email"]
message: 'This field is required.'
code: 'invalid_type'
}]
}
To report a rule only the server can check (an email already in use) the same way, return it with invalidInput("body", issues). See Request handlers.
Invalid input is a normal result, not a thrown error, and this is why Cuple was started. A thrown error isn't typed. An invalid-body in the response union has typed issues with a path, so the client can type-safely put each message on its field.
Zod was the safest choice: fields are required by default and type inference is good. Only the issues needed a small patch, to narrow each path to strings and numbers. No better option is known today.