Sergey Reznichenko , Scala / Java Developer
In this article, you'll become familiar with a way to interact with HTTP headers and cookies when using GraphQL, Play 2 Framework and Sangria in a Scala application.
When we're writing a RESTful API with Play 2, we can easily interact with cookies and HTTP headers:
1 2 3 4 5
processEndpoint() runs some business logic and then adds new headers and cookies into the HTTP response using the functions
withCookies(). Therefore, when developing RESTful applications, we can have multiple endpoints handled by individual controllers thus dividing the functionality and letting us access HTTP data directly.
With GraphQL and Sangria, however, we don't have this luxury of accessing the HTTP request and response objects directly. All we have is a JSON object returned after a GraphQL query is executed. Nevertheless, we can return headers and cookies from the
execute() method and change them. How do we achieve that?
All we need to do is create a set of global mutable collections and store additional data in them when executing the GraphQL query and then use them when creating an HTTP response.
Here's our Scala application with the result:
Now, how do we create those global mutable collections? We use the Sangria context.
The Sangria context is all you need
Sangria features a tool called context, which we can use to create the global collections to store HTTP headers and cookies in our Scala and GraphQL API. In simple terms, Sangria can create its own execution context and use it in resolve functions that we define in a GraphQL schema.
What's the context? It's an object passed to the GraphQL query execution and it almost never changes. We do change it, though, to handle cookies and headers. As you'll see further, this object gets passed around in the schema, validators, and services.
In our sample Scala application, we created the following custom context (look for
1 2 3 4
Context, the following parameters need to be passed:
requestHeaders, contains the headers that came with the HTTP request
requestCookies, contains cookies that came with the HTTP request
newHeaders, stores new headers that we can add to the HTTP response
newCookies, stores new cookies to be added to the HTTP response
Pay attention that
newCookies are mutable collections and we can change their state by adding, removing, or changing an element — a header or cookie respectively — from the list.
AppController.graphqlBody() after the GraphQL query is parsed and ready to be passed to the executor:
1 2 3 4 5 6 7 8 9
Note that most Sangria's tools have the general type
Ctx that defines the type of context your Scala application is dealing with. By default, this type is
Unit meaning no context is available. And the context must be the same for the entire application: You can't create another instance of
Context in some other method.
Check out this small code example. This is a chunk of
PostSchema with the GraphQL query
posts to return all posts:
1 2 3 4 5 6 7 8
Pay attention to the implementation of
ObjectType, which has two generic parameters — one type for the context and another for the returned data. The
Field type follows the same structure: The first generic type is the context and the second is
Unit (the list of
Queries can return different types of data, hence
Unit is passed).
Theory talks are good, but practice is better. Let's use the described approach to access and modify HTTP headers and cookies in a Scala app.
Interacting with the Sangria context on the level of HTTP server
We get the headers and cookies from an HTTP request and writing new headers and cookies into the HTTP response.
AppController contains the method
executeQuery(), and we need to pass the context to the Sangria
1 2 3 4 5 6 7 8 9 10
But before we call
executeQuery() with the context, we must create it and pass headers and cookies from the request
maybeQuery variable created in
After the GraphQL query is executed, we can add new headers and cookies into the HTTP response using the methods
withCookies (look into the same method
Similarly, you can add headers and cookies in any other HTTP Scala server.
Notice that the GraphQL query gets executed first, and only then does it get mapped to new headers and cookies. This means you need to extend the context with new headers and cookies during query execution in a schema, services, or validators.
Using a custom Sangria context in a Scala and GraphQL application
To demonstrate the use of the Sangria context in a Scala and GraphQL application, let's implement user authentication with the help of unique identifiers that will be stored in cookies on the client.
Our demo Scala app implements the functionality of anonymous forums where users don't need to register and where user authentication is implemented with IDs added to each post object. Therefore, the Post entity defines four fields:
1 2 3 4
authorId, logically, is used to authenticate this user.
Now, let's create a method that accepts our custom Sangria context and verifies the availability of an ID in a cookie. If there's an ID, then we pass it to the callback. And if there's no ID, then we'll create a new UUID, add it to the list of
newCookies, and pass it to the callback.
The following implementation you can find in
1 2 3 4 5 6 7 8
Next, our GraphQL schema
PostSchema must be changed to use the authorization method before actually executing the mutation query. Here's the
addPost mutation function in
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
In this implementation, we simply authenticate the user if they weren't authenticated earlier.
Let's also add the administrator to our application. For that functionality, we need to add a class for storing secret configurations:
1 2 3 4
app/config/AccessConfig stores a fake token used for verification. Let's create a method to verify if a user has the administrator rights (look for
1 2 3 4 5 6
This method accepts the context we created in
AppController and the next operation declared as a callback (this will be our GraphQL resolve function). The method verifies if the cookie
admin-access-token comes with the admin token and compares it with the token in
Let's get back again to
PostSchema and use the validator to verify if a mutation query came with the cookie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Let's check if GraphQL queries are executed correctly by adding a post:
You can see the result on the right: There's a response confirming that a post was added.
Add a couple of other posts with test data and request all of them:
Now, clean cookies using the developer tools in your browser, add a new post, and request all posts again:
As we can see, the author identifier changed for the latest post. We can now check if the access right is verified using this mutation query:
Our mutation query was denied because we didn't provide the authorization token!
Let's add the token
"some_token" to the cookie
admin-access-token in the developer tools and run the mutation query again:
Our Scala application works as expected.
To sum up, we advise not to store the HTTP request in the Sangria context as this approach breaks the single responsibility pattern. Because context isn’t related to business logic (read: the resolver layer), you should avoid passing it in parameters into GraphQL resolve functions. Use services or validators instead, as we did in our demo Scala app.
We discussed the key point of using HTTP entities with Sangria in a GraphQL application. Try it out in your Scala application and comment below if you have any questions.