I'm rewriting the Local Library project from MDN in TypeScript and struggling to define the type of a controller that has validation checks.
const createAuthor: ValidatedHandler = [
...validate.authorData,
async (req, res) => { /* controller */ },
];
const createGenre: ValidatedHandler = [
validate.genre,
async (req, res) => { /* another controller */ }
];
ValidatedHandler
is a type I created and defined as follows:
import type { RequestHandler } from "express";
import type { ValidationChain } from "express-validator";
type ValidatedHandler = [...ValidationChain[], RequestHandler];
validate.authorData
is an array of validators, and validate.genre
is a single validator:
import { body } from "express-validator";
const authorData = [
body("first-name")
.trim()
.notEmpty()
.withMessage("First name must be specified."),
body("family-name")
.trim()
.notEmpty()
.withMessage("Family name must be specified."),
// etc.
];
const genre = body("name")
.trim()
.notEmpty()
.withMessage("Genre name must be specified.");
The spread operator in the type definition makes ValidationChain
optional. If I remove ...validate.authorData
or validate.genre
, TypeScript has no problem with that. How can I fix that and make it so that at least one validator is required?
I've seen posts that suggest putting one type without the spread operator (i.e., [ValidationChain, ...ValidationChain[], RequestHandler]
), but when I do that, VS Code has no problem with validate.genre
but shows this error for ...validate.authorData
:
Type 'ValidationChain | ((req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<...>, number>) => Promise<...>)' is not assignable to type 'ValidationChain'.
Type '(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>, number>) => Promise<...>' is missing the following properties from type 'ValidationChain': builder, not, withMessage, custom, and 117 more.
I've seen posts that suggest putting one type without the spread operator (i.e.,
[ValidationChain, ...ValidationChain[], RequestHandler]
)
This is a good suggestion! It says the array must have an element at index 0
which is a ValidationChain
. That's what you want.
but when I do that, VS Code [shows an error] for
...validate.authorData
...validate.authorData
must be known to have at least one element to satisfy the new ValidatedHandler
type, but authorData
's type is inferred as ValidationChain[]
, which is an array type with an unknown number of elements (as far as the type is concerned, its value could be []
).
You could add a const
assertion to the initial value of authorData
to get its type to be inferred as a tuple known to have two elements, which allows validate.authorData
to be spread into ValidatedHandler
:
import type { RequestHandler } from "express";
import type { ValidationChain } from "express-validator";
import { body } from "express-validator";
type ValidatedHandler = [
ValidationChain, // <-- this was added
...ValidationChain[],
RequestHandler,
];
const authorData = [
// ^? - const authorData: readonly [ValidationChain, ValidationChain]
body("first-name")
.trim()
.notEmpty()
.withMessage("First name must be specified."),
body("family-name")
.trim()
.notEmpty()
.withMessage("Family name must be specified."),
// etc.
] as const; // <-- this was added
const genre = body("name")
.trim()
.notEmpty()
.withMessage("Genre name must be specified.");
const validate = { authorData, genre };
const createAuthor: ValidatedHandler = [
...validate.authorData,
async (req: unknown, res: unknown) => { /* controller */ },
];
const createGenre: ValidatedHandler = [
validate.genre,
async (req: unknown, res: unknown) => { /* another controller */ }
];
This makes it so at least one validator is required. Code like the following now results in a type error:
const oops: ValidatedHandler = [
async (req: unknown, res: unknown) => {} // error
];