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).
Custom messages (optional second argument):
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().
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?).
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 ofreq.bodyfor 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 fromreq.hasFile(field)(andreq.filefor image when needed) instead of raw body text.
Use these in the array form, for example:
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 JCCnormalizeBody, 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 whenotherFieldequalsvalue.required_unless:otherField,value— Required unlessotherFieldequalsvalue.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 exactlyndigits.digits_between:min,max— Digit length betweenminandmax(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 rawvalidatorjsAPIs).not_in:foo,bar,...— Value must not be in the list.same:otherField— Must equalotherField.different:otherField— Must differ fromotherField.confirmed— Expects{field}_confirmationmatchingfield(e.g.password/password_confirmation).
Other scalars
boolean—true/false/0/1and common string forms.array— Value is an array.accepted—yes,on,1, ortrue(typical “accept terms” check).
Dates (parsed with JavaScript Date; library includes extra date sanity checks)
dateafter:date,after_or_equal:datebefore: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 callspasses(); meaningful use is together withnormalizeBody, which injects""when the field is missing so other rules can treat “empty” consistently.unique:modelorunique:model,column— No row may already have this value.modelis resolved viagetModelFile;columndefaults to the attribute name. Implemented withexistsHelperagainst your ORM (Lucid / Sequelize / Mongoose).exists:modelorexists: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
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?)orawait validate(...). - Prefer arrays of rules when using
sometimes,nullable,file, andimage. unique/existstie into your models;ValidationException+AppErrorHandlerproduce 422 JSON orredirectBackwith flashedold/ errors.FormRequest: implementasync rules()withawait this.validate({ ... }); the container runsrules()before your controller method when the request type is injected (Controllers.md). After success, usereq.validated()or thevalidatereturn value (Request.md).
