Skip to main content

Request Validation

How validation works depends on the stack, and the difference matters:

  • Express: validation is explicit. You attach express-validator chains to a method via _<verb>Validation. The swaggerModel schemas drive documentation and type generation, not validation, declaring a schema does not validate the request.
  • Bun/Elysia: validation is schema-driven. The TypeBox schemas you put on a route's body/query/params are compiled and enforced by Elysia automatically.
Express: schemas don't validate

A common misconception: on the Express stack, swaggerModel / Magic Types describe the API for OpenAPI and generated types. They do not check incoming requests. If you need to reject bad input, declare a _<verb>Validation chain.

Express

Declaring validation

Add a _<verb>Validation array to the service. Each entry is an express-validator chain. Efesto runs them before your handler:

import { BaseApiService } from "efesto";
import { body, query, param } from "express-validator";

class Users extends BaseApiService {
constructor() {
super(__filename);
}

_getValidation = [
query("page").optional().isInt({ min: 1 }),
query("limit").optional().isInt({ min: 1, max: 100 }),
];
async _get(req, res) {
return res.json(await listUsers(req.query));
}

_postValidation = [
body("name").isString().isLength({ min: 2, max: 50 }),
body("email").isEmail(),
body("age").optional().isInt({ min: 0, max: 150 }),
];
async _post(req, res) {
return res.status(201).json(await createUser(req.body));
}
}
export default Users;

Validation arrays exist for every verb: _getValidation, _postValidation, _putValidation, _patchValidation, _deleteValidation, _headValidation, _optionsValidation, _traceValidation, _connectValidation.

Handling validation errors

Efesto does not impose an error shape. The express-validator results are available through validationResult(req); format them in your errorMiddleware (the second hook passed to efesto()), which runs after the validation chains:

import { validationResult } from "express-validator";

const errorMiddleware = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
};

Custom and cross-field validation

Anything express-validator can do works here, because these are express-validator chains. For example, a custom async rule and a cross-field check:

_postValidation = [
body("email").isEmail().custom(async (email) => {
if (await emailExists(email)) throw new Error("Email already in use");
return true;
}),
body("confirmPassword").custom((value, { req }) => {
if (value !== req.body.password) throw new Error("Passwords do not match");
return true;
}),
];

See the express-validator documentation for the full API (sanitizers, oneOf, schema syntax, and more).

Bun/Elysia

On the Elysia stack, validation is the schema. Attach TypeBox schemas to a route's body, query, or params; Elysia compiles them, validates each request, and returns a 422 with a descriptive error when a request doesn't match. The same schema also documents the endpoint and types the handler.

import { BaseApiService, type Context, RouteValidation, t } from "efesto/elysia";

export default class extends BaseApiService {
_postValidation: RouteValidation = {
body: t.Object({
name: t.String({ minLength: 2, maxLength: 50 }),
email: t.String({ format: "email" }),
age: t.Optional(t.Number({ minimum: 0, maximum: 150 })),
}),
query: t.Object({ ref: t.Optional(t.String()) }),
};
_post({ body }: Context) {
return createUser(body); // validated against the schema
}
}

TypeBox covers the constraints you would reach for, minLength/maxLength, minimum/maximum, format, pattern, enum (via t.Union/t.Literal), arrays, and nested objects. See the Elysia validation guide.

File fields

  • Express: validate the non-file fields with _<verb>Validation as usual; enable the upload with the _<verb>Multer flag (see File Uploads). The file itself (req.file) is checked in your handler.
  • Elysia (native): describe a file field with t.File() in the body schema; Elysia validates type and size from the schema.
  • Elysia (class): enable _<verb>Multer / _<verb>MultipleMulter; the file arrives in ctx.body (see File Uploads).