Introduction
After a long time, it’s time to talk about GraphQL. In our previous tutorials A walk-through to REST design pattern and Implementing a RESTful API with node.js, mongoDB, mongoose and express (2) we talked about REST design and continued implementing a RESTful API service using node.js, express and mongodb. In this tutorial, we will be focusing on the theory behind GraphQL and then, in the next article we will implement a GraphQL API. So, let’s start with it.
What is GraphQL?
GraphQL is a technology developed by Facebook in 2012 and three years later it became available to the public, where big companies started being interested in it, and eventually adopted it. GraphQL is a specification for a client-server communication that defines two things:
A Query Language
A Type System
GraphQL is language-agnostic and its core features are:
Strongly typed: queries are validated against the schema of a GraphQL service.
Hierarchical queries: queries are expressed in the form of objects, with each one to have different levels of nesting.
Introspection: the client can query the schema, retrieve information about it, and render it to the UI.
Understanding the GraphQL Client-Server Communication
GraphQL does not change the HTTP protocol that services often use to communicate, rather it defines a layer above it, which instructs on how data is sent. Saying that, GraphQL is an HTTP POST request that sends and expects data as a JSON. For example, the following GraphQL query returns a JSON with a list of objects, with each object to contain the title of a job position.
curl --location --request POST 'https://api.graphql.jobs' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query { jobs { title slug } }"}'
With curl client we request information from jobs API, and we pass in -d flag a JSON that contains a query property followed with a sequence of characters that the service parses, validates, and finally processes. That string follows the syntax of GraphQL query language, and it passes an argument with the name of the pokemon that we search, returning its name and image.
Now, when the request reaches the server, because the query is in the form of a string and not flexible enough, the GraphQL service parses it and creates an Abstract Syntax Tree (AST) – commonly known as lexical analysis –. An AST is a tree representation of GraphQL query composed by a set of objects that contain the necessary information that the server needs. After that, the service validates the query against its own schema, and if it’s valid, it moves to the resolvers, which handles the processing and returns a response to the client.
So, what is GraphQL Query Language?
Anyone who has worked with relation database management systems (RDBMS), he knows that SQL (Structure Query Language) is a query language that allows to query, modify and retrieve information from a database. The same appears to exist for GraphQL, but in a different environment, which defines a language on how a client interacts with a GraphQL server.
The Main Parts of GraphQL Language
Now, let try to explain the main components of a GraphQL query. To run any of the following queries, paste the content into this GraphQL playground.
query {
jobs {
title
slug
}
}
The query starts with the keyword query, which says the root operation that the query belongs to – query, mutation, subscription –, and within the first pair of curly brackets, we call the field jobs. A field in GraphQL works like any function that accepts arguments (inputs) and returns outputs. Next, within the second pair of curly brackets we select the properties title and slug. This part specifies the properties we expect in the response payload and you will often hear it mentioned as selection set.
While the previous query returns two fields, with each one expressed as a string, GraphQL exceeds when it has to deal with more complex queries. As a result, we extend the previous query adding some additional information, like the name of the company and the name of the cities that role is available. The name of the company is returned as a string, however, the names of the cities are returned as a list of strings.
query {
jobs {
title
slug
company {
name
}
cities {
name
}
}
}
Let's say that we have finished with our search and we are interested in an advertisement that we think it's a good fit for us. Using the following query we can filter the results and retrieve details for the job we are interested to. To do that, we pass an argument within the job field – remember that it's a function –, which is an object containing the jobSlug and companySlug. This means that the API will recognize the parameter we pass to it and retrieve the record from the database that matches our input.
query {
job(input:{jobSlug: "full-stack-javascript-developer", companySlug: "unrealists"}) {
title
slug
company {
name
websiteUrl
logoUrl
createdAt
updatedAt
}
cities {
name
}
}
}
If your are interested to improve the existing query, we can recreate it using Fragments and Variables. A fragment is a construct that helps up to use a set of fields in different locations and avoid repetition. In our case we simply create a fragment and use it to replace the selection set related to the Company type. We do the same for the City type as well. In addition, rather than hardcoding the arguments passed to the query, we use GraphQL variables. A graphql variable is prefixed with the $ symbol followed with the name of the variable. Its used as a placeholder for any variables passed to our query. As you may see, the query is much cleaner now and variables are now passed dynamically from our client.
query job($input:JobInput!) {
job(input:$input) {
title
slug
company {
...CompanyFragment
}
cities {
...CityFragment
}
}
}
fragment CompanyFragment on Company {
name
websiteUrl
logoUrl
createdAt
updatedAt
}
fragment CityFragment on City {
name
}
GraphQL Schema Definition Language
In order a client to interact with a GraphQL service, the back-end service needs to implement a schema, with the schema to contain at least one query. The notion of a schema is similar to the meaning of a schema in relational database systems (RDBMS). In that case, the schema acts as a container of relationships and encapsulates all the information needed to describe the internal structure of the database.
The same appears to exist for GraphQL, with the sole purpose of a schema to set the internal structure of a service. The end result is a collection of objects nested to each other indicating the relationships between them. A schema always starts with the root operations:
Query: is compulsory and has to be an object type
Mutation: is optional and if it’s provided it needs to be an object type
Subscription: is optional and if it’s provided it needs to be an object type
Those three operations determine the type of requests that a service accepts, and any query which is not included results in a validation error.
The Schema Definition Language (SDL) is the syntax/language developed in GraphQL to declare a schema of a service, but you may also hear it as GraphQL Type System. The GraphQL Type System includes two main categories of types which are named and wrapping types. The former includes six built-in types:
ScalarType
EnumType
InputObjectType
ObjectType
InterfaceType
UnionType
while the latter:
List
Non-Null
ScalarType
A ScalarType represents a primitive value with built-in as well as custom types. The main built-in ScalarTypes of GraphQL are:
Int: a 32-bit signed numerical non-fractional value
Float: a signed double-precision fractional value
String: a UTF-8 encoded value used to represent text
Boolean: true or false
ID: a unique identifier of an object which is serialised/deserialised in the same way as a string.
Nonetheless, custom scalar types are also possible assuming that we provide the parsing and serialization details. The code snippet below shows a custom Random type containing the fields id, username, isValid, price and year returning an ID, String, Boolean, Float, Int, respectively. At the bottom of the code snippet you can see a custom scalar type named Time.
type Random {
id: ID
username: String
isValid: Boolean
price: Float
year: Int
}
scalar Time
ObjectType
An object type acts as a collection of fields and each field may belong in one of the following types: Scalar, Enum, Object, Interface and Union. Below, we specify two object types, a Post and a User. Each object type acts as a container of fields with each field to return either a ScalarType or an ObjectType. Bear in mind that the root operation types are some common examples of object types.
type Post {
title: String!
content: String!
user: Post!
}
type User {
id: ID!
username: String!
email: String!
posts: [Post!]!
}
InterfaceType
An interface is a set of named fields that are forced to appear in an object type that implements it. Below, we declare an interface with the name Node and it contains a field returning an ID. The User type implements the interface, meaning that it’s forced to include the field ID with the exact same way as it has been specified in the Node interface.
interface Node {
Id: ID!
}
type User implements Node {
Id: ID!
username: String!
email: String!
}
UnionType
An object that returns a type from a list of GraphQL Object Types. The example below sets the Developer and Analyst types. Rather than declaring two different getUser queries returning different types, we declare a union that includes both of them and return the Employee Union.
type Query {
getEmployee(id:ID!): Employee
}
type Developer {
id: ID!
name: String!
}
type Analyst {
id: ID!
name: String!
}
union Employee = Developer | Analyst
EnumType
It describes a set of possible values and its often used to allow a range of values. The example below creates an enum type with the name Role and is used by the User type. This means that the only acceptable values of a user’s role are either ADMIN or BASIC.
enum Role {
ADMIN
BASIC
}
type User {
Id: ID!
username: String!
email: String!
role: Role!
}
InputObjectType
It defines a set of input fields which are accepted only as arguments of a query. The inputs fields are either Scalars or Enums or other Input objects. The code snippet below creates an input type with the name queryInputType and is used as an argument for getUsers query. This approach adds some flexibility to our query since we are allowed to search for a user using his id or username or email.
type Query {
getUser(query:queryInputType!): User
}
input queryInputType {
id: ID
username: String
email: String
}
type User {
id: ID!
username: String
email: String
createdAt: Time!
updatedAt: Time!
}
Non-Null
While all named types are nullable, a non-null type acts as a wrapper for named types to disallow the return of a null value. A non-null is declared with an exclamation(!) and we have already used it in several of our code snippets. Non-null values make an application more robust and it’s recommended whenever it’s possible.
List
A list indicates the presence of a collection of types and allows GraphQL queries to return more than one type. We specify a list by using square brackets ([]) around a named type. You will see below that we add a getUsers query in our service and we return a list of Users. Pay attention to the non-null types around the square brackets as well as to the non-nullable User. This means that we expect to get a list, and within the list we don’t accept null values.
type Query {
getUsers: [User!]!
}
type User {
id: ID!
username: String
email: String
createdAt: Time!
updatedAt: Time!
}
Summary
In summary, GraphQL is a technology that defines two things, a Query Language and a Type System. The query language is mainly used in the client-side, while the type system is used to define a schema in a GraphQL server. The schema of a service includes named and wrapping types and are used to develop a schema that meets the requirements of a project. When a query is sent from a client, the query is sent through HTTP, and when it reaches the server, it's parsed, validated and passed to the resolvers to return a response. The form of the response will mirror the form of the sent query.
We have covered a lot and you probably feel exhausted. However, if you want to read more about GraphQL have a look to GraphQL specification and the official site. Now you should feel confident enough on writing your first GraphQL server. See you in the next tutorial.