Understanding the HttpContext

The HttpContext object contains unique information about individual requests our application receives. In this lesson, we'll cover what it is, how it's passed, and how we can mutate it.

Published
Sep 04, 21
Duration
7m 1s

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

The HttpContext object is an object that we'll be heavily working with as we develop our Adonis application. It's an object that's going to be provided to us via our middleware, route handlers, HTTP hooks, and exception handlers as we progress throughout our request's lifecycle.

It's an object that's unique to each of our application's individual requests. So, anytime a user requests a page, submits a form, submits an API request, etc we'll receive a unique HttpContext object about that user's request.

HttpContext Mutation Behavior

The way Adonis is going to pass our HttpContext object from one request lifecycle method to the other is by reference. This means that if we mutate our HttpContext object inside our middleware that mutation will remain intact when we receive that request within our route handler.

If an error were then thrown for that request in our route handler, the same mutation we made within our middleware will again be carried through to the exception handler.

By mutating our HttpContext, we can easily and effectively pass additional information, for or about a request, as that request progresses from one portion of our application to another.

HttpContext Contents

What our application's HttpContext contains actually varies depending on which packages our application has installed and registered.

For example, in this series, we've yet to cover or install the Auth module. Since we've yet to install the Auth module, we're not going to have an auth property on our HttpContext. Once we do install and configure the Auth module within our application, however, we'll then have an auth module on our HttpContext that'll allow us to authenticate a user, deauthenticate a user, get the currently authenticated user, and more.

Our Application's HttpContext

Let's start by understanding what our web structured Adonis application has within its HttpContext out-of-the-box.

  • Inspect
    This is a helper method that allows us to inspect the contents of an HttpContext object.

  • Logger
    We can use the logger to output information into our terminal when working locally or our application's logs when in production.

  • Params
    This is an object containing any route parameters for the requested route. The key is the param name and the value is the URL value for the key.

  • Request
    An object containing information about the user's specific request. We can get the URL, query string info, the request body, files, etc using the request object.

  • Response
    An object that allows us to mutate how our application is going to respond to the particular request. We can set headers, cookies, etc here and define response data using this object as well.

  • Route
    An object containing information about the matched route definition

  • RouteKey
    A unique string for the route that was requested.

  • Session
    An object that allows us to get or store information for the user's session. We can also use the session to get or set flash messages, which live only for a single request.

  • Subdomains
    An object containing all of the route's subdomain. This is only applicable if the requested route contains a subdomain definition.

  • View
    An object that allows us to render our pages within our application.

The HttpContextContract

Since Adonis uses TypeScript, our HttpContext object does have its own type, which is called the HttpContextContract.

Type Definition

In most cases, Adonis is going to be able to provide the HttpContextContract type for our HttpContext object, so we won't need to explicitly define the type. However, there are a few instances where you'll need to manually define the type for your HttpContext in order to get TypeScript support. Controllers, which we'll get into later in this series, are one of those areas.

We can easily import the type from @ioc:Adonis/Core/HttpContext then apply the type for our HttpContext object.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

class ExampleController {
  // ctx, contains our HttpContext object
  public async index(ctx: HttpContextContract) {

  }

  // we can also destructure the HttpContext object
  public async show({ request, view }: HttpContextContract) {

  }
}

Extending the HttpContextContract

If we do need to extend our HttpContext object, we'll also need to extend the TypeScript interface definition for our HttpContextContract in order to get TypeScript support for our extended method or property.

declare module '@ioc:Adonis/Core/HttpContext' {
  interface HttpContextContract {
    someNewProperty: string | null
  }
}

This will inform TypeScript about our new method or property, preventing it from complaining that the method or property doesn't exist.

Extending the HttpContext

Just as packages can extend our application's HttpContext, we can extend our HttpContext as well. There's a couple of different ways to go about it, we're just going to be covering one for right now.

If you head into your application and view the start/routes.ts file, you'll see a single route definition provided by Adonis for our application's welcome page. It looks like the below.

// start/routes.ts

import Route from '@ioc:Adonis/Core/Route'

Route.get('/', async ({ view }) => {
  return view.render('welcome')
})

Ignore everything for right now except for the callback function. This callback function is the route's handler, this is what's being passed the HttpContext object. By default, Adonis is destructuring out the view property from the HttpContext object. Let's change this to just be ctx for context so we have access to the full HttpContext object.

// start/routes.ts

Route.get('/', async (ctx) => {
  return view.render('welcome')
})

Now that we have access to the full HttpContext object, we can extend it the same as any normal object by plopping a property or method directly on the HttpContext.

// start/routes.ts

Route.get('/', async (ctx) => {
  ctx.test = 'testing'

  return view.render('welcome')
})

That adds the property onto our HttpContext object, however, we also need to inform TypeScript about this property and its type as well. So, let's create a new file in our contracts directory called httpContext.ts and extend our HttpContextContract interface with our new property.

// contracts/httpContext.ts

declare module '@ioc:Adonis/Core/HttpContext' {
  interface HttpContextContract {
    test: string | null
  }
}

This extends our HttpContextContract, defined within @ioc:Adonis/Core/HttpContext, with our test property and defines its type as string or null.

Wrapping Up

That's all we're going to cover with the HttpContext for right now. My goal with this lesson was to get you familiar with what the HttpContext is, what it contains, how it's passed, and how we can extend it. This knowledge will help make understanding routing, route handling, and middleware a little easier going forward.

Join The Discussion! (4 Comments)

Please sign in or sign up for free to join in on the dicussion.

  1. Anonymous (SoleWinnah843)
    Commented 2 years ago

    Nice

    0

    Please sign in or sign up for free to reply

  2. Anonymous (BobcatRayna988)
    Commented 2 years ago

    muito bom esse conteúdo!

    0

    Please sign in or sign up for free to reply

  3. Commented 15 days ago

    The adonis 6 docs talk about extending httpcontext through macros and getters. Does that mean we can't just add a property like you do here? Also, contracts are out now, can I just add that interface definition in my app types file? I want to put together an object of properties I need throughout a request and add it to httpcontext. I was thinking of doing it in the route stack middleware. As usual, their documentation only explains about half of what you need to know so I am confused. You explain all this shit way better than they do.

    1

    Please sign in or sign up for free to reply

    1. Commented 15 days ago

      Yeah, the approach is still relatively the same. The only change is the type's name and namespace. In v6 you can continue just plopping additional properties directly on the HttpContext object, v5 actually had macros and getters as well :)

      The types namespace is now @adonisjs/core/http and the name is HttpContext, so you can extend it's type like so:

      declare module '@adonisjs/core/http' {
        export interface HttpContext {
          organization: Organization
        }
      }
      Copied!

      Where you put the types doesn't matter much, you can put it in the same file you're existing the context within if you'd like or you can create a specific types directory off the root of your project.

      Here's an example of adding an organization property on to the HttpContext within a middleware:

      import type { HttpContext } from '@adonisjs/core/http'
      import type { NextFn } from '@adonisjs/core/types/http'
      import { inject } from '@adonisjs/core'
      import OrganizationService from '#services/organization_service'
      import Organization from '#models/organization'
      
      @inject()
      export default class InertiaMiddleware {
        constructor(protected organizationService: OrganizationService) {}
      
        async handle(ctx: HttpContext, next: NextFn) {
          const organization = await this.organizationService.active()
      
          // adding the organization onto the HttpContext
          ctx.organization = organization
      
          return next()
        }
      }
      
      // adding the organization onto the HttpContext interface
      declare module '@adonisjs/core/http' {
        export interface HttpContext {
          organization: Organization
        }
      }
      Copied!

      Hope this helps!! :)

      0

      Please sign in or sign up for free to reply

Playing Next Lesson In
seconds