Skip to main content

Request Context

Understand the purpose of the request context object.

Introduction

Athenna provides an object inside all Http handlers called ctx. This property is implemented by the Context interface imported from @athenna/http package.

The context object

In Athenna as you can see in the previous documentation page of Middlewares and Controllers we are always destructuring the ctx property and using like this:

Route.get('/welcome', ({ response }) => {
response.status(200).send({ hello: 'world' })
})

But is the same of doing this:

Route.get('/welcome', (ctx) => {
ctx.response.status(200).send({ hello: 'world' })
})

The ctx object is little different for each type of handlers, and we will see all the differences previous in this documentation page.

The request object

Athenna Request class provides an object-oriented way to interact with the current HTTP request being handled by your application as well as retrieve the ip, headers, body, and files that were submitted with the request.

The id getter

Get the id from the request:

Route.get('/welcome', ({ request }) => {
console.log(request.id) // 123e4567-e89b-12d3-a456-426614174000

/*....*/
})

This is very useful to trace the requests of your server. Check the tracing requests documentation page for more information.

The ip getter

Get the ip from where the request were executed:

Route.get('/welcome', ({ request }) => {
console.log(request.ip) // 192.168.0.1

/*....*/
})

The method getter

Get the REST method of your request:

Route.get('/welcome', ({ request }) => {
console.log(request.method) // GET

/*....*/
})

The hostUrl getter

Get the host url of the request concatenating the host:port of your application and the originalUrl of the request:

Route.get('/welcome', ({ request }) => {
console.log(request.hostUrl) // http://localhost:1335/welcome

/*....*/
})

The baseUrl getter

Get the url of the route without the query params:

Route.get('/welcome', ({ request }) => {
console.log(request.baseUrl) // /welcome

/*....*/
})

The originalUrl getter

Get the original url with the query params:

Route.get('/welcome', ({ request }) => {
console.log(request.originalUrl) // /welcome?hello=world

/*....*/
})

The body, params, queries and headers getters

Retrieve all the data inside each one of then:

Route.post('/welcome/:id', ({ request }) => {
console.log(request.body) // { hello: 'world' }
console.log(request.params) // { id: '1' }
console.log(request.queries) // { world: 'hello' }
console.log(request.headers) // { 'content-type': 'application/json' }

/*....*/
})

The input() and payload() methods

Retrieve only one value per call from the request body:

Route.post('/welcome/:id', ({ request }) => {
const defaultValue = 'defaultValue'

console.log(request.input('hello', 'found')) // 'world'
console.log(request.input('not-found', defaultValue)) // 'defaultValue'

console.log(request.payload('hello', defaultValue)) // 'world'
console.log(request.payload('not-found', defaultValue)) // 'defaultValue'

/*....*/
})
tip

As you can see, you can use the second argument of this type of methods to set the default value if the key has not been found in your request.

You may even use "dot" syntax to retrieve values that are nested within JSON arrays / objects:

const name = request.input('user.name')

The only() and except() methods

If you need to retrieve a subset of the input data, you may use the only() and except() methods. Both of these methods accept a single array or a dynamic list of arguments:

const input = request.only('username', 'password')
const input = request.only(['username', 'password'])

const input = request.except('credit_card')
const input = request.except(['credit_card'])
warning

The only() method returns all the key / value pairs that you request; however, it will not return key / value pairs that are not present on the request body.

The param(), query() and header() methods

Retrieve only one value of params, queries or headers. You can also set a second parameter that will set the default value if the first argument key doesn't exist:

Route.post('/welcome/:id', ({ request }) => {
const defaultValue = 'defaultValue'

console.log(request.param('id', defaultValue)) // '1'
console.log(request.param('not-found', defaultValue)) // 'defaultValue'

console.log(request.query('world', defaultValue)) // 'hello'
console.log(request.query('not-found', defaultValue)) // 'defaultValue'

console.log(request.header('content-type', defaultValue)) // 'application/json'
console.log(request.header('not-found', defaultValue)) // 'defaultValue'

/*....*/
})

The getFastifyRequest() method

Retrieve the vanilla Fastify request object to use more advanced getters and methods from Fastify:

Route.get('/welcome', ({ request }) => {
const fastifyRequest = request.getFastifyRequest()

/*....*/
})

The response object

Athenna Response class provides an object-oriented way to interact with the current HTTP response being handled by your application as well set a status code and return the response to the client.

The send() method

Terminate the request sending a response body to the client:

Route.get('/welcome', ({ response }) => {
response.send({ hello: 'world' })
})

The view() method

Terminate the request rendering a view in the response body to the client:

Route.get('/welcome', ({ response }) => {
response.view('welcome', { hello: 'world' })
})

The helmet() method

Apply all the Helmet response headers in your response:

Route.get('/welcome', async ({ response }) => {
if (condition) {
// we apply the default options
await response.helmet()
} else {
// we apply customized options
await response.helmet({ frameguard: false })
}
})

The status() method

Apply the status code of your response:

Route.get('/welcome', async ({ response }) => {
response.status(200).send({ hello: 'World' })
})

The sendFile() method

Serve files if the static plugin is enabled in your application:

Route.get('/welcome', async ({ response }) => {
response.status(200).sendFile('img.png')
})

The download() method

Serve files with a custom name if the static plugin is enabled in your application:

response.status(200).download('img.png', 'custom-img.png')

The header(), safeHeader() and removeHeader() methods

Set custom header for your response, the header() method will overwrite the already set headers, the safeHeader() will only register the header if the header is not yet registered and the removeHeader() will remove a header from the response:

Route.get('/welcome', async ({ response }) => {
response.header('content-type', 'application/json')
response.safeHeader('content-type', 'application/json')
response.removeHeader('content-type')
})

The redirectTo() method

Redirect your response to another url and with a different status code:

Route.get('/hello', ctx => ctx.response.status(200))

Route.get('/welcome', async ({ response }) => {
response.redirectTo('/hello', 200)
})

The sent getter

Verify if your response has already been sent to client, useful to be used in interceptors:

Route.get('/welcome', async ({ response }) => {
response.send({ status: 'ok' })
}).interceptor(({ response }) => {
if (response.sent) {
// do something
}
})

The body, statusCode and headers getters

Get the content of the response body, status code and headers if it exists. These values will be available before you use response.send(), response.status() and response.headers() methods somewhere. These getters are useful when using interceptors and terminators:

Route.get('/welcome', async ({ response }) => {
response.send({ status: 'ok' })
}).terminator(({ response }) => {
if (response.statusCode !== 200) {
// do something
}

if (response.body.status === 'ok') {
// do something
}

if (response.headers['Content-Type'] !== 'application/json') {
// do something
}
})

The responseTime getter

Get how much time your request has taken until it finish and turn back to client. This value will only be available in terminators:

Route.get('/welcome', async ({ response }) => {
response.send({ status: 'ok' })
}).terminator(({ response }) => {
console.log('Request has taken: ', response.responseTime, 'ms', ' to finish.')
})

The getFastifyResponse() method

Retrieve the vanilla Fastify response object to use more advanced getters and methods from Fastify:

Route.get('/welcome', ({ response }) => {
const fastifyResponse = response.getFastifyResponse()

/*....*/
})

The data object

Use the data object to define properties that will be available in your entire request flow. This is really useful for some cases where you want to transfer data from a middleware to a controller for example. Let's see an example setting default pagination values if client has not sent page and limit:

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

@Middleware()
export class PaginationMiddleware {
public async handle({ request, data }: Context) {
const page = request.queries.page ? parseInt(request.queries.page) : 0
const limit = request.queries.limit ? parseInt(request.queries.limit) : 10
const resourceUrl = `${Config.get('http.domain')}${request.baseUrl}`

data.pagination = {
page,
limit,
resourceUrl,
}
}
}

And now is very simple to get this pagination object inside your handler:

Route.get('/products', ({ response, data }) => {
return response.send({ paginationObj: data.pagination })
}).middleware('PaginationMiddleware')

The context object in middlewares

Middleware context

The context of a middleware is the same of a Controller. It uses the same Context interface from @athenna/http package.

Interceptor context

In interceptors Athenna uses the InterceptContext. This context is quite the same of Context, but it has additional property status.

Terminate middleware context

In terminators Athenna set the TerminateContext. This context is quite the same of Context, but it has additional properties status and responseTime.