Implementing Request Validation in AdonisJS 5 with Schema Files
Learn how to define request schemas in AdonisJS 5, invoke the built‑in validator, and handle errors to return proper HTTP status codes.
18 Aug 2025, 01:03 UTC

Implementing Request Validation in AdonisJS 5 with Schema Files
The common pain point when building APIs is receiving malformed JSON payloads that cause runtime errors or security issues. The useful takeaway is that AdonisJS 5 provides a built‑in, type‑safe validation layer that lets you describe request shapes in separate schema files and automatically enforce them in controllers.
Prerequisites
- Node.js version 18 or newer – required for the TypeScript compiler used by AdonisJS 5.
- AdonisJS 5 installed via npm:
npm i adonisjs@5(run in the project root). - A
.envfile containingAPP_KEY– this secret key encrypts password hashes and signs tokens. - Access to the source directory with write permission (typically a developer account).
Step‑by‑step Procedure
- Create a validator file. In the project root run:
Open the file and extend thetouch app/Validators/userValidator.tsSchemaclass:
Theimport { Schema, rules } from '@ioc:Adonis/Core/Validator'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; export default class UserValidator extends Schema { public async validate(params: HttpContextContract['params'], rules: SchemaParams) { return this.object(params, { username: rules.string({ minLength: 3, maxLength: 30 }).required(), email: rules.email({ email: true }).required(), age: rules.number({ minValue: 18, maxValue: 99 }).optional(), }); } }objectmethod defines rules for the request body (or query parameters if you usequeryinstead ofparams). - Invoke the validator in a controller method. Example controller (
app/Controllers/Http/UserController.ts):
Theimport { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; import UserValidator from 'App/Validators/UserValidator'; import User from 'App/Models/User'; export default class UserController { public async create({ request, response }: HttpContextContract) { const payload = await validate(request, new UserValidator()); const user = await User.create(payload); return response.created(user); } }await validatecall throws aValidationExceptionwhen any rule fails. - Handle the exception and return a structured error response. Add a
try/catchblock:
Thetry { const payload = await validate(request, new UserValidator()); const user = await User.create(payload); return response.created(user); } catch (error) { if (error instanceof HttpException && error.status === 422) { return response.response(422, { message: 'The submitted data did not pass validation.', errors: error.messages, }); } // unexpected errors console.error(error); return response.response(500, { message: 'Internal server error' }); }error.messagesobject contains field‑specific messages generated from the schema rules.
Expected Checks
- Successful creation returns HTTP 201 with the newly created user JSON.
- When validation fails, the endpoint returns HTTP 422 and a JSON payload containing
errorsmapped to each field. - Inspect the
requestobject after validation – the validated data is automatically merged intorequest.all()for downstream use.
Recovery Options
- Wrap the
validatecall in atry/catchblock as shown above; this prevents the request from crashing and lets you log detailed error information. - Log the
errorobject (includingerror.messages) to your application logger; this aids troubleshooting without exposing sensitive data to the client. - Return a consistent JSON error shape, e.g.,
{ message: '...', errors: { email: ['The email must be valid.' }] }, so API consumers can programmatically handle failures.
Verification & Testing
- Unit test: create a test that sends an invalid payload (e.g., missing
username) and asserts that the response status is 422 and thatresponse.json().errors.usernamecontains the expected message. Example command:
The test should pass if the validation layer behaves as documented.npm run test --test=user.validation.test.ts - Manual test: use Postman or curl to call
POST /userswith a valid payload:
Check that the response code is 201 and that the returned JSON contains the created user fields.curl -X POST http://localhost:3333/users \ -H \"Content-Type: application/json\" \ -d '{\"username\":\"john\",\"email\":\"[contact removed]\",\"age\":25}'
Limitations & Risks
- Node.js < 18 may break TypeScript compilation and cause runtime mismatches with AdonisJS 5 features.
- Missing
APP_KEYprevents encryption of password hashes and token signing, leading to authentication failures. - Incorrect schema rules (e.g., using
rules.string()without length constraints) can allow unexpected data shapes, potentially causing silent corruption downstream.
Practical Way to Check the Result
After implementing the validator, run the unit test suite and manually invoke the endpoint with a tool like Postman. Verify that a 422 response includes the correct error structure and that a 201 response returns the expected JSON body. If any discrepancy appears, review the schema definition for typo‑related rule names and ensure the APP_KEY is set.
Conclusion
By defining request shapes in dedicated schema files and using the validate helper, AdonisJS 5 eliminates the need for manual checks scattered across controllers. The approach is declarative, type‑safe, and integrates cleanly with the framework’s middleware pipeline, making API development more reliable and maintainable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.