JCC Express

Validation

Introduction

Input validation uses validatorjs under the hood, with JCC-specific async rules registered on the shared Validator class (jcc-express-mvc/lib/Validation/Validator/index.ts). The request exposes req.validate(rules, customMessages?), which delegates to Validator.validate. The global validate(rules, customMessages?) helper resolves the current request and calls the same method.

When a controller action is injected with a FormRequest subclass, the container constructs it with the current req, then await requestInstance.rules() before your method runs. Implement async rules() and call await this.validate({ ... }) there so validation failures never reach the action (see Controllers.md and Request.md).

On failure, the framework flashes old input (the request body) and validation errors, then throws ValidationException. The error handler turns that into 422 responses: JSON for API-style requests, redirectBack() for typical browser forms (AppErrorHandler).


Basic usage

Rules are an object: each key is a field name (matching req.body keys unless you use file rules), each value is a string or array of rule tokens (arrays work best with JCC’s normalizeBody for sometimes, nullable, file, and image).

TypeScript

Custom messages (optional second argument):

TypeScript

After a successful run, req.validated is set to a function that returns the validated body (currently req.body). Validator.validate also resolves with that same object, so you can use either the return value or req.validated().

TypeScript

Use await on validate because the engine runs checkAsync. req.validated() itself is synchronous.


Global helper

Defined in jcc-express-mvc/lib/Global/helpers.ts as validate(rules, customMessages?).

TypeScript

Same behavior as req.validate, but resolves the current request from the container (only valid during an HTTP request after bindings are set).


Failed validation and the UI

When validation fails, Validator.validate flashes:

  • old — copy of req.body for repopulating forms.
  • VALIDATION_ERROR — error payload for templates.

HttpRequest maps the latest flash into res.locals.errors and res.locals.old on the next render so Blade can show field errors (see Request.md and JCC-Blade.md). For JSON clients (expectsJson() and not Inertia), AppErrorHandler responds with 422 and an errors payload derived from the exception.


Rule modifiers in JCC

normalizeBody (in Validator) adjusts the data passed into validatorjs for these rule names:

  • sometimes — If the field is missing from the body, that field can be skipped for normalization (optional presence).
  • nullable — Missing field becomes "" so nullable-style rules can run consistently.
  • file / image — Value is derived from req.hasFile(field) (and req.file for image when needed) instead of raw body text.

Use these in the array form, for example:

TypeScript

Supported validation rules

The project depends on validatorjs (see package.json; rules below match the library’s built-ins in node_modules/validatorjs/src/rules.js). JCC runs validation through checkAsync, so every rule name is resolved in an async pipeline; the stock rules are still ordinary (fast) checks, while the rules in CustomValidation.ts are true async callbacks registered with Validatorjs.registerAsync.

Built-in rules from validatorjs

Presence and conditionals

  • required — Value is present and not empty after trimming whitespace.
  • sometimes — Always passes; combined with JCC normalizeBody, missing fields can be omitted from the normalized input so other rules are skipped for that key (see Rule modifiers in JCC).
  • present — Key must exist in input; value may be empty.
  • required_if:otherField,value — Required when otherField equals value.
  • required_unless:otherField,value — Required unless otherField equals value.
  • required_with:foo,bar,... — Required if any of the listed fields are present.
  • required_with_all:foo,bar,... — Required if all listed fields are present.
  • required_without:foo,bar,... — Required if any of the listed fields are absent.
  • required_without_all:foo,bar,... — Required if all listed fields are absent.

Strings and text shape

  • string — Value is a string.
  • email — Email-shaped string.
  • url — Valid URL format.
  • alpha — Letters only.
  • alpha_num — Letters and numbers only.
  • alpha_dash — Letters, numbers, dashes, underscores.
  • hex — Hexadecimal characters.
  • regex:pattern — Matches regex; prefer an array of rules if the pattern contains |. Escape backslashes for regex strings.

Numbers and digits

  • numeric — Numeric (string forms allowed).
  • integer — Integer value.
  • digits:n — Numeric string with exactly n digits.
  • digits_between:min,max — Digit length between min and max (see library behavior for empty values).

Size and bounds (length for strings, value for numbers; see library getSize)

  • min:n, max:n, size:n, between:min,max.

Sets and equality

  • in:foo,bar,... — Value must be one of the comma-separated options (also supports object/array rule form in raw validatorjs APIs).
  • not_in:foo,bar,... — Value must not be in the list.
  • same:otherField — Must equal otherField.
  • different:otherField — Must differ from otherField.
  • confirmed — Expects {field}_confirmation matching field (e.g. password / password_confirmation).

Other scalars

  • booleantrue / false / 0 / 1 and common string forms.
  • array — Value is an array.
  • acceptedyes, on, 1, or true (typical “accept terms” check).

Dates (parsed with JavaScript Date; library includes extra date sanity checks)

  • date
  • after:date, after_or_equal:date
  • before:date, before_or_equal:date

IP addresses

  • ip — IPv4 or IPv6.
  • ipv4, ipv6

Official behavior, edge cases, nested keys, and wildcards (e.g. users.*.email) are described in the validatorjs README on GitHub.

JCC custom rules (CustomValidation.ts)

These are registered on every Validator.validate call alongside validatorjs:

  • nullable — Async rule that always calls passes(); meaningful use is together with normalizeBody, which injects "" when the field is missing so other rules can treat “empty” consistently.
  • unique:model or unique:model,column — No row may already have this value. model is resolved via getModelFile; column defaults to the attribute name. Implemented with existsHelper against your ORM (Lucid / Sequelize / Mongoose).
  • exists:model or exists:model,column — A row must exist for this value. Fails with “required” if the value is empty.
  • file — After normalization, the value reflects upload presence; fails if falsy (“The file field is required.”).
  • image — Same idea for image uploads (“The image field is required.”).

Use array rule lists for sometimes, nullable, file, and image so JCC’s normalizeBody can detect them (see Rule modifiers in JCC).


ValidationException

Thrown when checkAsync fails. The exception carries a flattened errors map (field → first message) for the handler. Validator.validate already flashes old and VALIDATION_ERROR before rejecting; AppErrorHandler may flash again when handling ValidationException for web flows. Do not catch and swallow the exception unless you intend to replace the default 422 / redirect behavior. For how these errors become HTTP responses (JSON vs redirectBack, status codes), see Error-handling.md.


FormRequest example

TypeScript

Use this.req.validated() only after this.validate(...) has completed successfully (for example inside the same class method). There is no this.validated() on FormRequest itself.

FormRequest messages

Override protected messages(): Record<string, string> on your request class. FormRequest.validate passes this.messages() as the second argument to req.validate, so custom keys follow validatorjs message conventions.


Summary

  • Validate with await req.validate({ field: ["rule", ...] }, messages?) or await validate(...).
  • Prefer arrays of rules when using sometimes, nullable, file, and image.
  • unique / exists tie into your models; ValidationException + AppErrorHandler produce 422 JSON or redirectBack with flashed old / errors.
  • FormRequest: implement async rules() with await this.validate({ ... }); the container runs rules() before your controller method when the request type is injected (Controllers.md). After success, use req.validated() or the validate return value (Request.md).