Application Flow
The lifecycle of a request in the backend application.
Understanding the flow of a request from the moment it hits the server to when a response is sent back is key to working with the backend. This project follows the standard AdonisJS request lifecycle but has some specific conventions.
1. Routing (start/routes.ts
)
All incoming requests are first matched against the routes defined in start/routes.ts
. This file acts as the main entry point for routing.
- Route Groups: Routes are heavily grouped by prefix (
/api/v1
) and middleware. - Modular Routes: Instead of defining all routes in one file, the main file imports and calls route-defining functions from the
app/controllers
directory (e.g.,customerRoutes()
fromapp/controllers/customer/routes.ts
). This keeps the routing organized by feature.
2. Middleware (start/kernel.ts
)
Once a route is matched, the request passes through the registered middleware.
- Global Middleware: These run on every request (e.g.,
cors_middleware
). - Named Middleware: These are applied to specific routes or groups. The key custom middleware in this project are:
auth
: Protects routes, checks for a valid access token, and can verify user roles.captcha
: Requires a valid CAPTCHA token for sensitive, unauthenticated endpoints like sign-in and sign-up.
3. Validation (@vinejs/vine
)
Before the controller logic runs, the request body, params, and headers are typically validated. This project uses VineJS for validation.
- Schema Definition: Validation schemas are usually defined at the top of the controller file using
vine.compile()
. - Execution: The controller calls
ctx.request.validateUsing(schema)
as its first step. If validation fails, aValidationException
is thrown automatically, which the global exception handler formats into a 422 response.
// Example from signin_controller.ts
export const input = vine.compile(
vine.object({
email: CustomerEMailS(),
password: CustomerPasswordS(),
}),
);
export default class Controller {
async handle(ctx: HttpContext) {
// This will throw if validation fails
const payload = await ctx.request.validateUsing(input);
// ...
}
}
4. Controller Logic (app/controllers
)
If validation passes, the controller's handle
method executes the core logic. This typically involves:
- Authorization: Calling
ctx.bouncer
to authorize the action (see Authorization docs). - Database Interaction: Using Lucid models (
User
,Workspace
, etc.) to fetch or persist data. - Business Logic: Calling services or modules (like
tokenService
) for complex operations. - Response: Returning a JSON object, which AdonisJS automatically sends as the HTTP response.
5. Error Handling (app/exceptions/handler.ts
)
If any exception is thrown during the process (e.g., a ValidationException
, ForbiddenException
from Bouncer, or a database error), it is caught by the global HttpExceptionHandler
. This handler uses the parseError
utility from @localspace/node-lib
to ensure that all errors are converted into a consistent, predictable JSON response format for the frontend.