Documentation Index
Fetch the complete documentation index at: https://mintlify.com/adonisjs/core/llms.txt
Use this file to discover all available pages before exploring further.
Middleware are functions that execute during the HTTP request lifecycle. They can modify requests, responses, or terminate the request early.
Creating Middleware
Middleware Structure
A middleware is a class with a handle method:
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class AuthMiddleware {
async handle(ctx: HttpContext, next: NextFn) {
// Execute before request
console.log('Before request')
// Call next middleware or route handler
await next()
// Execute after response
console.log('After response')
}
}
Middleware Types
Global Middleware
Global middleware execute for every request.
// start/kernel.ts
import server from '#services/server'
server.use([
() => import('#middleware/container_bindings_middleware'),
() => import('@adonisjs/core/bodyparser_middleware')
])
Named Middleware
Named middleware can be selectively applied to routes.
// start/kernel.ts
server.useRouted({
auth: () => import('#middleware/auth_middleware'),
guest: () => import('#middleware/guest_middleware')
})
// start/routes.ts
router.get('/dashboard', [DashboardController, 'index'])
.middleware('auth')
Route Middleware
Middleware applied directly to a specific route.
router.get('/admin', [AdminController, 'index'])
.middleware(async ({ auth, response }, next) => {
if (!auth.user?.isAdmin) {
return response.forbidden('Access denied')
}
await next()
})
Middleware Methods
handle()
The main middleware method that processes requests.
The HTTP context object containing request, response, etc.
Function to call the next middleware or route handler
Optional middleware options/parameters
Returns: Promise<void>
Middleware Patterns
Authentication Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class AuthMiddleware {
async handle(
{ auth, response }: HttpContext,
next: NextFn
) {
// Check authentication
try {
await auth.authenticate()
} catch {
return response.unauthorized({ error: 'Not authenticated' })
}
await next()
}
}
Authorization Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class RoleMiddleware {
async handle(
{ auth, response }: HttpContext,
next: NextFn,
options: { roles: string[] }
) {
const user = auth.user
if (!user || !options.roles.includes(user.role)) {
return response.forbidden({ error: 'Access denied' })
}
await next()
}
}
Usage with options:
router.get('/admin', [AdminController, 'index'])
.middleware([RoleMiddleware, { roles: ['admin'] }])
Logging Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class LoggingMiddleware {
async handle(
{ request, response, logger }: HttpContext,
next: NextFn
) {
const startTime = Date.now()
logger.info(`→ ${request.method()} ${request.url()}`)
await next()
const duration = Date.now() - startTime
logger.info(
`← ${request.method()} ${request.url()} - ${response.getStatus()} (${duration}ms)`
)
}
}
CORS Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class CorsMiddleware {
async handle(
{ request, response }: HttpContext,
next: NextFn
) {
// Set CORS headers
response.header('Access-Control-Allow-Origin', '*')
response.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
response.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
// Handle preflight requests
if (request.method() === 'OPTIONS') {
return response.noContent()
}
await next()
}
}
Rate Limiting Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import redis from '@adonisjs/redis/services/main'
export default class RateLimitMiddleware {
async handle(
{ request, response }: HttpContext,
next: NextFn,
options: { maxRequests: number, windowMs: number } = {
maxRequests: 100,
windowMs: 60000
}
) {
const ip = request.ip()
const key = `rate-limit:${ip}`
const requests = await redis.incr(key)
if (requests === 1) {
await redis.expire(key, Math.floor(options.windowMs / 1000))
}
if (requests > options.maxRequests) {
return response.tooManyRequests({
error: 'Rate limit exceeded'
})
}
response.header('X-RateLimit-Limit', options.maxRequests.toString())
response.header('X-RateLimit-Remaining', (options.maxRequests - requests).toString())
await next()
}
}
Request Validation Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import vine from '@vinejs/vine'
export default class ValidateMiddleware {
async handle(
{ request, response }: HttpContext,
next: NextFn,
options: { validator: any }
) {
try {
await request.validateUsing(options.validator)
} catch (error) {
return response.unprocessableEntity({ errors: error.messages })
}
await next()
}
}
Conditional Middleware
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class MaintenanceModeMiddleware {
async handle(
{ request, response }: HttpContext,
next: NextFn
) {
const isMaintenanceMode = process.env.MAINTENANCE_MODE === 'true'
const isAdminRoute = request.url().startsWith('/admin')
if (isMaintenanceMode && !isAdminRoute) {
return response.serviceUnavailable({
error: 'Service is under maintenance'
})
}
await next()
}
}
Middleware Execution Order
Middleware execute in the order they are registered:
- Global middleware - Execute for all requests
- Named middleware - Execute for specific routes
- Route handler - The actual route controller/handler
- Response middleware - Execute after the route handler
server.use([
() => import('#middleware/initialize_middleware'), // 1
() => import('@adonisjs/core/bodyparser_middleware') // 2
])
router.get('/users', [UsersController, 'index'])
.middleware('auth') // 3
.middleware('throttle') // 4
// Route handler executes here // 5
Terminating Middleware
Middleware can terminate the request by not calling next():
export default class BlockedIpMiddleware {
async handle({ request, response }: HttpContext, next: NextFn) {
const blockedIps = ['192.168.1.1', '10.0.0.1']
if (blockedIps.includes(request.ip())) {
return response.forbidden({ error: 'IP blocked' })
// next() is not called, request is terminated
}
await next()
}
}
Error Handling in Middleware
export default class ErrorHandlingMiddleware {
async handle(ctx: HttpContext, next: NextFn) {
try {
await next()
} catch (error) {
ctx.logger.error(error)
ctx.response.internalServerError({
error: 'Something went wrong'
})
}
}
}
Middleware with Dependencies
import { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import { LoggerService } from '@adonisjs/core/types'
@inject()
export default class LoggingMiddleware {
constructor(protected logger: LoggerService) {}
async handle(ctx: HttpContext, next: NextFn) {
this.logger.info(`Request: ${ctx.request.url()}`)
await next()
}
}
Testing Middleware
import { test } from '@japa/runner'
import AuthMiddleware from '#middleware/auth_middleware'
test.group('Auth Middleware', () => {
test('redirects unauthenticated users', async ({ client }) => {
const response = await client.get('/dashboard')
response.assertStatus(401)
})
test('allows authenticated users', async ({ client }) => {
const response = await client
.get('/dashboard')
.withGuard('web')
.loginAs(user)
response.assertStatus(200)
})
})