Our goal for this chapter is to run following query:
query {
allLinks {
id
url
description
}
}
The expected response is a list of links fetched from database.
before we will add the first model, please decide where all models will be stored. I have one recommendation but of course you can place all models in the place you want.
From now on all the domain specific models will be placed in this file, so it will be easy to find them and in the case of this tutorial, easy to compare versions and recognize if something will need to change.
Now we can go back to creating our first model.
Let’s start by defining a really basic model Link
.
As you can see, a Link
model has fewer fields than in the schema you saw in the first chapter, but no worries, we will improve the model in the future.
Now we’re focusing on completing execution stack so it would be better to keep this model simple.
Link
support to databaseOur simple database has to support this model and provide some data as well.
We just added database definition of our first model.
Context is an object that flows across the whole execution, in the most cases this object doesn’t change at all.
The main responsibility of the Context is providing data and utils needed to fulfill the query.
In our example we will put there DAO
so all queries will have access to the database.
In the future we will also put there authentication data.
In our example Context will get a name MyContext
and because it isn’t related with domain directly, I propose to keep it along
other files in the sangria
package.
For now we don’t do anything with this file, but we had to create it to get the server working. For sure we will back to this file in the future.
Time to implement the GraphQL Server. This object will be in the second layer of architecture, just after HTTP server. Proper HTTP request will be converted into JSON object and sent to this server. GraphQL Server will parse that JSON as GraphQL query, execute it and through HTTP layer send response back to the client. It will also catch GraphQL parsing errors and convert those into the proper HTTP responses.
It’s one of the most important files in entire backend server so let’s analyze it step by step:
1 We need access to the database, so it’s the step where such connection is created.
2 endpoint
responds with Route
type. It will be used directly in the routing of HTTP server. It expects JSON object as parameter.
3 Main JSON Object is extracted from the root object and it consists three children. The expected structure you can see in the following fragment
{
query: {},
variables: {},
operationName: ""
}
query
is a query itself, variables
is additional data for that query. In GraphQL you can send the query and arguments separately.
You can also set name for the query, it’s what the third object is for. Imagine that query is like a function,
usually you’re using anonymous functions, but for logging or other purposes you could add names. It’s send as operationName
.
4 We’re extracting query
from request at this point.
When we have the query, we have to parse it. Sangria provides QueryParser.parse
(5) function we can use in this case.
When it fails, the server will respond with status 400 and error description in the body of response.
After successful parsing, we’re also trying to extract the other two keys operationName
(6) and variables
(7).
At the end all those three objects are passed to the execution function (8).
9 Executor.execute
is the most important call in this class because it’s the point where the query is executed.
If the executor responds with success, the result is sent back to the client. In all other cases, the server will respond
with status code 4xx and some kind of explanation of what was wrong with the query. The Executor needs some data to fulfill
the request. Three of them are query
(11), operationName
(13) and variables
(14) which are read from the request.
The last two are: GraphQLSchema.SchemaDefinition
and MyContext(dao)
.
12 MyContext
is a context object mentioned in the section above. In our example you can see that the context is
built with the DAO object within.
GraphQLSchema.SchemaDefinition
is the last object we have to explain here. It contains our Schema - what we are able
to query for. It also interprets how data is fetched and which data source it could use (i.e. one or more databases,
REST call to the other server…). In short our SchemaDefinition
file defines what we want to expose.
There are defined types (from GraphQL point of view) and shape of the schema a client is able to query for.
Because this file is still missing we will create it in the next step.
Sangria cannot reuse case classes defined in our domain, it needs its own object of type ObjectType
. On the other hand, it allows us to decouple API/Sangria models from database representation. This abstraction allows us to freely hide, add or aggregate fields.
Link
class. First (String) argument defines the name in the schema. If you want it could differ from name of case class. In fields
you have to define all those fields/functions you want to expose. Every field has to contain a resolve
function which tells Sangria how to retrieve data for this field. As you can see there is also an explicitly defined type for that field.
Manual mapping could be boring in cases where you have to map many case classes. To avoid boilerplate code you can use the provided macro.implicit val LinkType = deriveObjectType[Unit, Link]()
It will give the same result as the example I used in the code above.
When you want to use macro-way to define objects don’t forget to import sangria.macros.derive._
val QueryType
is a top level object of our schema. As you can see, the top level object has a name Query
and it (along with nested objects) will be visible in the graphiql console that we will include later in this chapter.
In fields
definition I’ve added only one Field
at the moment Field("allLinks", ListType(LinkType), resolve = c => c.ctx.dao.allLinks)
The snippet above defines a GraphQL field with name allLinks
. It’s a list (ListType) of link items (LinkType).
AS you can see it’s a definition of a query we want to provide in this chapter. Resolver needs a allLinks
function in DAO
object so we have to implement it now.
Graphiql makes able to run queries against our server from the browser. Let’s implement it now.
Giter8 template I provided for this example also contains proper file. You can find it in src/main/resources/graphiql.html
.
All we need to do is to define the HTTP server is such way that this file will be exposed and available to be reached
in the browser.
The last thing we have to do to fulfill this chapter’s goal is to configure HTTP server.
We have to expose graphiql.html
file and open an endpoint where GraphQL queries will be send.
As you can see, a new route
definition has only two endpoints.
Every POST
to /graphql
is delegated to GraphQLServer, response to every other request else is a content of the graphiql.html
file.
And open in the browser an url http://localhost:8080/graphiql Of couse use different port number if you haven’t decided to default one during project initialization.
The response should looks like that:
{
"data": {
"allLinks": [
{
"id": 1,
"url": "http://howtographql.com",
"description": "Awesome community driven GraphQL tutorial"
},
{
"id": 2,
"url": "http://graphql.org",
"description": "Official GraphQL web page"
},
{
"id": 3,
"url": "https://graphql.org/",
"description": "GraphQL specification"
}
]
}
}
In this chapter we’ve finished configuring the entire GraphQL server stack and defined a very basic first query.