Enforcing Request Integrity in AdonisJS: A Practical Guide to Built‑In Validation
AdonisJS’s built‑in Validator turns request payloads into guaranteed, type‑casted data. Learn how to define schemas, inject them in controllers, customize error messages, and avoid common pitfalls with a practical example.
17 Jun 2026, 11:45 UTC

Concrete Problem: The API that Accepts Anything
When an API endpoint blindly trusts incoming JSON, it becomes a moving target for bugs, security gaps, and data corruption. A simple “create user” route that accepts any shape of data may store incomplete profiles, break downstream services, or expose sensitive fields. The solution in AdonisJS is the Validator service, a first‑class feature that validates request payloads against a declarative schema before the controller logic runs.
Thesis: Why Use the Built‑In Validator?
AdonisJS’s Validator is more than a form helper—it’s a contract between the client and the server. By declaring rules, custom messages, and type casting in a single place, you:
- Guarantee consistent error responses (always a 400 with a predictable JSON shape).
- Centralize validation logic, making it easier to maintain and audit.
- Leverage automatic type casting (e.g., strings to dates) to keep business logic clean.
Section 1: Defining a Validation Schema
AdonisJS 5.x ships with a Schema builder that lets you describe the shape of expected data. Each field maps to a set of rules that the validator will check.
// app/Validators/UserValidator.ts
import { schema, rules } from '@ioc:Adonis/Core/Validator'
export default class UserValidator {
public schema = schema.create({
name: schema.string({ trim: true }, [rules.minLength(2)]),
email: schema.string({ trim: true }, [rules.email(), rules.unique({ table: 'users', column: 'email' })]),
password: schema.string({}, [rules.minLength(8)]),
birthDate: schema.date.optional({ format: 'YYYY-MM-DD' })
})
public messages = {
'name.required': 'Name is required',
'email.required': 'We need your email address',
'password.minLength': 'Password must be at least 8 characters'
}
}
Key points:
schema.stringdeclares a string field;schema.date.optionalmakesbirthDatenullable.- Rules are chained:
rules.email()ensures a valid address;rules.uniquechecks the database. - Custom messages are scoped per field and rule, enabling localized feedback.
Section 2: Injecting the Validator into a Controller
Once the schema is ready, you can use it in a controller action. The validateWith helper runs the validation and throws a ValidationException on failure, which AdonisJS automatically turns into a 400 response.
// app/Controllers/Http/UserController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import UserValidator from 'App/Validators/UserValidator'
export default class UserController {
public async store({ request, response }: HttpContextContract) {
const payload = await request.validate(UserValidator)
// payload is now type‑casted and guaranteed to match the schema
const user = await User.create(payload)
return response.created(user)
}
}
Notice the brevity: the controller no longer needs to manually check each field. If validation fails, the framework returns:
{
"message": "Validation Failed",
"errors": {
"email": ["We need your email address"]
}
}
Section 3: Handling Validation Errors Gracefully
While AdonisJS automatically returns a 400, you may want to customize the error payload or log details. Wrap the validate call in a try/catch and inspect the ValidationException:
try {
const payload = await request.validate(UserValidator)
} catch (error) {
if (error instanceof ValidationException) {
// Log or transform the error
console.warn('Validation error:', error.messages)
return response.badRequest(error.messages)
}
throw error
}
This pattern keeps the default behavior while giving you a hook for analytics or custom formatting.
Section 4: Trade‑Offs and Practical Checklist
Using the built‑in validator introduces a few considerations:
- Version mismatch: AdonisJS 4.x and 5.x use different syntax. Verify your project’s version before copying code.
- Over‑strict schemas: Declaring every field as
requiredcan break clients that send empty objects. Prefer optional fields with defaults where possible. - Database uniqueness: The
rules.uniquerule hits the database on every request. For high‑traffic endpoints, consider debouncing or caching. - Performance impact: Complex regex or many custom rules add CPU overhead. Profile critical paths if latency is a concern.
Checklist before production:
- Run
node ace test:runto cover validation paths. - Verify the 400 JSON shape matches your API spec.
- Ensure custom messages are present in the
messagesobject. - Test type casting by sending string dates and inspecting the resulting
Dateobjects.
Actionable Closing: Integrate Validation Early
Start every new route with a dedicated validator. Keep the schema in its own file, reuse it across controllers, and document the expected payload in your API spec. When you need to add a new field, update the schema and the tests, not the controller logic. This pattern keeps your codebase maintainable, your data consistent, and your clients happy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.