Skip to main content

Middlewares

Understand how you can set up middlewares in your REST API application.

Introduction

Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, Athenna includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will throw an unauthorized error. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

Additional middleware can be written to perform a variety of tasks besides authentication. For example, a logging middleware might log all incoming requests to your application.

Defining middleware

To create a new middleware, use the make:middleware command:

node artisan make:middleware EnsureApiKeyIsValid

This command will place a new EnsureApiKeyIsValid class within your Path.middlewares() directory. In this middleware, we will only allow access to the route if the supplied api key input matches a specified value. Otherwise, we will throw an unauthorized exception:

import { Context, Middleware, MiddlewareContract } from '@athenna/http'

@Middleware()
export class EnsureApiKeyIsValid implements MiddlewareContract {
public async handle(({ request }): Context): Promise<void> {
if (request.query('apiKey') !== 'my-api-key') {
throw new UnauthorizedException('Your api key does not match with application api key.')
}
}
}

As you can see, if the given apiKey does not match our application api key, the middleware will return an unauthorized exception to the client; otherwise, the request will be passed further into the application.

It's best to envision middleware as a series of "layers" HTTP requests must pass through before they hit your application. Each layer can examine the request and even reject it entirely.

tip

All middleware is resolved via the service container, so you can use any dependencies you need within a middlewares constructor.

Registering middleware

If you are using make:middleware command to register your middlewares, Athenna will auto register your middlewares inside the middlewares property of your .athennarc.json file:

{
"middlewares": [
"#app/http/middlewares/EnsureApiKeyIsValid"
]
}
warning

Remember that if you have not created your middleware using make:middleware command, you will need to manually add it to middlewares array. If you don't do that, Athenna will not register your middleware in the service container and you will not be able to use it.

Global middleware

If you want a middleware to run during every HTTP request to your application, you can set the isGlobal value to true in the @Middleware() annotation:

import { Middleware } from '@athenna/http'
import type { Context, MiddlewareContract } from '@athenna/http'

@Middleware({ isGlobal: true }) 👈
export class EnsureApiKeyIsValid implements MiddlewareContract {
public async handle(({ request }): Context): Promise<void> {
if (request.query('apiKey') !== 'my-api-key') {
throw new UnauthorizedException('Your api key does not match with application api key.')
}
}
}

If you are not using TypeScript, you can use the globalMiddlewares array in your .athennarc.json file:

{
"globalMiddlewares": [
"#app/http/middlewares/EnsureApiKeyIsValid"
]
}

Assigning middleware to routes

If you would like to assign middleware to specific routes, you can use the name of your middleware class in camelCase (ensureApiKeyIsValid). But we recommend you to assign a custom name to your middleware using the @Middleware() annotation:

import { Middleware } from '@athenna/http'
import type { Context, MiddlewareContract } from '@athenna/http'

@Middleware({ name: 'auth' }) 👈
export class EnsureApiKeyIsValid implements MiddlewareContract {
public async handle(({ request }): Context): Promise<void> {
if (request.query('apiKey') !== 'my-api-key') {
throw new UnauthorizedException('Your api key does not match with application api key.')
}
}
}

If you are not using TypeScript, you can use the namedMiddlewares object in your .athennarc.json file:

{
"namedMiddlewares": {
"auth": "#app/http/middlewares/EnsureApiKeyIsValid"
}
}

Once the middleware name has been defined, you may use the Route.middleware() method to assign middleware to a route:

Route.get('/welcome', () => {
//
}).middleware('auth') 👈
tip

To learn more about the @Middleware() annotation, visit the REST API annotations documentation page.

Intercept middleware

Sometimes a middleware may need to do some work before the HTTP response has been sent to the client. For this purpose, you can use the Athenna interceptor middleware. To create one, you can use the following command:

node artisan make:interceptor InterceptMiddleware

Just like make:middleware, Athenna will auto register the middleware if using make:interceptor command. The above command will produce the following content at app/http/interceptors/InterceptMiddleware.ts file:

import { Interceptor } from '@athenna/http'
import type { InterceptContext, InterceptorContract } from '@athenna/http'

@Interceptor()
export class InterceptMiddleware implements InterceptorContract {
public async intercept(({ response }): InterceptContext): Promise<unknown> {
response.body.intercepted = true

return response.body
}
}
warning

As you can see in the example above, you should always return the intercepted response body in interceptors.

The registration process of Athenna interceptors is the same of middlewares.

To assign your interceptor to a route, you can use the Route.interceptor() method:

Route.get('/welcome', () => {
//
}).interceptor('interceptMiddleware') 👈
tip

To learn more about the @Interceptor() annotation, visit the REST API annotations documentation page.

Terminate middleware

Sometimes a middleware may need to do some work after the HTTP response has been sent to the client. For this purpose, you can use the Athenna interceptor middleware. To create one, you can use the following command:

node artisan make:terminator TerminateMiddleware

Just like make:middleware, Athenna will auto register the middleware if using make:terminator command. The above command will produce the following content at app/http/terminators/TerminateMiddleware.ts file:

import { Terminator } from '@athenna/http'
import type { TerminateContext, TerminatorContract } from '@athenna/http'

@Terminator()
export class TerminateMiddleware implements TerminatorContract {
public async terminate(ctx: TerminateContext): Promise<void> {}
}

The registration process of Athenna terminators is the same of middlewares.

To assign your terminator to a route, you can use the Route.terminator() method:

Route.get('/welcome', () => {
//
}).terminator('terminateMiddleware') 👈
info

The requests log created by Athenna when the logger property is true in your Path.config('http.ts') file are handled by a terminator middleware! You can see the code here.

tip

To learn more about the @Terminator() annotation, visit the REST API annotations documentation page.