Let's Go Using request context › How request context works
Previous · Contents · Next
Chapter 11.1.

How request context works

Every http.Request that our middleware and handlers process has a context.Context object embedded in it, which we can use to store information during the lifetime of the request.

As I’ve already hinted at, in a web application a common use-case for this is to pass information between your pieces of middleware and other handlers.

In our case, we want to use it to check if a user is authenticated once in some middleware, and if they are, then make this information available to all our other middleware and handlers.

Let’s start with some theory and explain the syntax for working with request context. Then, in the next chapter, we’ll get a bit more concrete again and demonstrate how to practically use it in our application.

The request context syntax

The basic code for adding information to a request’s context looks like this:

// Where r is a *http.Request...
ctx := r.Context()
ctx = context.WithValue(ctx, "isAuthenticated", true)
r = r.WithContext(ctx)

Let’s step through this line-by-line.

I should also point out that, for clarity, I made that code snippet a bit more verbose than it needs to be. It’s more typical to write it like this:

ctx = context.WithValue(r.Context(), "isAuthenticated", true)
r = r.WithContext(ctx)

So that’s how you add data to a request’s context. But what about retrieving it again?

The important thing to explain is that, behind the scenes, request context values are stored with the type any. And that means that, after retrieving them from the context, you’ll need to assert them to their original type before you use them.

To retrieve a value we need to use the r.Context().Value() method, like so:

isAuthenticated, ok := r.Context().Value("isAuthenticated").(bool)
if !ok {
    return errors.New("could not convert value to bool")
}

Avoiding key collisions

In the code samples above, I’ve used the string "isAuthenticated" as the key for storing and retrieving the data from a request’s context. But this isn’t recommended because there’s a risk that other third-party packages used by your application will also want to store data using the key "isAuthenticated" — and that would cause a naming collision.

To avoid this, it’s good practice to create your own custom type which you can use for your context keys. Extending our sample code, it’s much better to do something like this:

// Declare a custom "contextKey" type for your context keys.
type contextKey string

// Create a constant with the type contextKey that we can use.
const isAuthenticatedContextKey = contextKey("isAuthenticated")

...

// Set the value in the request context, using our isAuthenticatedContextKey 
// constant as the key.
ctx := r.Context()
ctx = context.WithValue(ctx, isAuthenticatedContextKey, true)
r = r.WithContext(ctx)

...

// Retrieve the value from the request context using our constant as the key.
isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool)
if !ok {
    return errors.New("could not convert value to bool")
}