You should use context for request-scoped information. You can use a struct to hold the request-scoped information within the context, if you like.

I guess it depends on what you mean by storing the request-specific information in structs. If you're storing it on some kind of global struct, or you have your own signature like this:

Go:
1
func Handler(session Session) http.Handler

Don't do that. Use context instead.

The main value of using context specifically for http things is that it prevents you from having to pollute your code with custom constructors for request-scoped information - As the type signatures are fixed for http.Handler and http.HandlerFunc. It also helps that changing the context on a request is really cheap and easy.

For situations where you're not dealing with a fixed API, using a struct is probably the right move instead of context except in the case of tracing things.

In any case, when dealing with context, you should assume that any value you attempt to retrieve from context may be missing.

Here is that the authentication information is technically optional - if it's missing then the method is supposed to return an error. If the information is a hard requirement, e.g. a method returning the name of the current user, then you need a new error case for "missing user id".

Go:
1
2
3
4
5
6
7
8
9
10
11
func GetUserCtx(ctx context.Context) (string, error) {
 id, ok := ctx.Value(userKey).(string) 
 // fun fact, no compile check that the type is correct
 if !ok {
    return "", fmt.Errorf("missing id")
 }
 if id == "" {
    return "", fmt.Errorf("empty id")
 }
 ...
}