Core concepts in GraphQL
The Schema Definition Language (SDL)
GraphQL has its own type system that’s used to define the schema of an API.
Here is an example how we can use the SDL to define a simple type Person
type Person {
name: String!
age: Int!
}
This type has two fields, they’re called name
and age
and are respectively of type String
and Int
.
Note :The ! following the type means that this field is required.
It’s also possible to express relationships between types.
type Book{
title: String!
author: Person!
}
Generally, a schema is simply a collection of GraphQL types. However, when writing the schema for an API, there are some special root types:
type Query { ... }
type Mutation { ... }
type Subscription { ... }
The Query
, Mutation
, and Subscription
types are the entry points for the requests sent by the client. To enable the allPersons
-query that we saw before, the Query
type would have to be written as follows:
type Query {
allPersons: [Person!]!
}
allPersons
is called a root field of the API. Considering again the example where we added the last
argument to the allPersons
field, we’d have to write the Query
as follows:
type Query {
allPersons(last: Int): [Person!]!
}
Putting it all together, this is the full schema for all the queries and mutation that you have seen in this chapter:
type Query {
allPersons(last: Int): [Person!]!
}type Mutation {
createPerson(name: String!, age: Int!): Person!
}type Subscription {
newPerson: Person!
}type Person {
name: String!
age: Int!
posts: [Post!]!
}type Post {
title: String!
author: Person!
}
For example, consider the following query to get user with id “abc”
query {
user(id: "abc") {
id
name
}
}
Fetching Data with Queries
GET request
When receiving an HTTP GET request, the GraphQL query should be specified in the “query” query string. For example, if we wanted to execute the following GraphQL query:
query
{
{
me {
name
}
}
}
This request could be sent via an HTTP GET like so:
http://myapi/graphql?query={me{name}}
POST request
A standard GraphQL POST request should use the application/json
content type, and include a JSON-encoded body of the following form:
{
"query": "...",
"operationName": "...",
"variables": { "myVariable": "someValue", ... }
}
operationName
and variables
are optional fields. operationName
is only required if multiple operations are present in the query.
Response
Regardless of the method by which the query and variables were sent, the response should be returned in the body of the request in JSON format. As mentioned in the spec, a query might result in some data and some errors, and those should be returned in a JSON object of the form:
{
"data": { ... },
"errors": [ ... ]
}
If there were no errors returned, the "errors"
field should not be present on the response.
Writing Data with Mutations
Next to requesting information from a server, the majority of applications also need some way of making changes to the data that’s currently stored in the backend. With GraphQL, these changes are made using so-called mutations. There generally are three kinds of mutations:
- creating new data
- updating existing data
- deleting existing data
Mutations follow the same syntactical structure as queries, but they always need to start with the mutation
keyword. Here’s an example for how we might create a new Person
:
mutation {
createPerson(name: "Bob", age: 36) {
name
age
}
}
Realtime Updates with Subscriptions
Another important requirement for many applications today is to have a realtime connection to the server in order to get immediately informed about important events. For this use case, GraphQL offers the concept of subscriptions.
When a client subscribes to an event, it will initiate and hold a steady connection to the server. Whenever that particular event then actually happens, the server pushes the corresponding data to the client. Unlike queries and mutations that follow a typical “request-response-cycle”, subscriptions represent a stream of data sent over to the client.
Here’s an example where we subscribe on events happening on the Person
type:
subscription {
newPerson {
name
age
}
}
After a client sent this subscription to a server, a connection is opened between them. Then, whenever a new mutation is performed that creates a new Person
, the server sends the information about this person over to the client:
{
"newPerson": {
"name": "Jane",
"age": 23
}
}
GraphQL Server Basics: GraphQL Schemas, TypeDefs & Resolvers
The GraphQL schema provides a clear contract for client-server communication.
The GraphQLSchema
object is the core of a GraphQL server.
For the example above, the GraphQLSchema
object looks as follows:
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString }
}
})const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLID }
}
}
}
})
})
Resolvers implement the API
In its most basic form, a GraphQL server will have one resolver function per field in its schema. Each resolver knows how to fetch the data for its field. Since a GraphQL query at its essence is just a collection of fields, all a GraphQL server actually needs to do in order to gather the requested data is invoke all the resolver functions for the fields specified in the query.
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLID }
},
resolve: (root, args, context, info) => {
const { id } = args // the `id` argument for this field is declared above
return fetchUserById(id) // hit the database
}
}
}
})
})const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: {
type: GraphQLID,
resolve: (root, args, context, info) => {
return root.id
}
},
name: {
type: GraphQLString,
resolve: (root, args, context, info) => {
return root.name
}
}
}
})
root
: the result of the previous call (initial value isnull
if not otherwise specified).args
: This argument carries the parameters for the querycontext
: An object that gets passed through the resolver chain that each resolver can write to and read from (basically a means for resolvers to communicate and share information).info
: An AST(Abstract Syntax Tree) representation of the query or mutation.