Introduction to GraphQL - Part I
Aneshka Goyal
AWS Certified Solutions Architect | Software Development Engineer III at Egencia, An American Express Global Business Travel Company
What is Graph QL
GraphQL gets its name as it gives us a query language to traverse our applications data graph and produce the query results tree. Our application data can easily be represented as graph of nodes liked by edges. For example we are working on employee management system here, that has each employee and his associated department, in this case one department can have multiple employees but one employee belongs to a single department. We can have the graph for this data in our application as in figure. The query that we specify in GraphQL starts from the root and traverses the edges(employee, name, department etc) to get to the nodes that have data.
Does that mean we can only use graph databases when working with graphQL?? Answer is NO. GraphQL is datastore agnostic which means it does not force us to use any particular type of database/source in order for the queries to process the results. It can be so diverse that we can fetch some part of the result form a miro-service call and others from SQL/noSQL databases without out client applications knowing anything.
More formally GraphQL can be thought of as a query language for our API. It gives the power to build descriptive APIs where the client describes what he/she needs and gets the data relevant to the query. It can even be described as a wrapper over REST, to make REST APIs more efficient and understandable.
Why do we need GraphQL when we have REST
Before answering this question lets first take a look at what is REST
REST stands for representational state transfer. REST is not a protocol but an architectural style for building APIs. RESTful architecture has 6 constraints.
Identification of resources - Use a URI(uniform resource identifier) to identify a resource
Manipulation of resources through these representations - Use HTTP standards for communication like GET, POST etc.
Self-descriptive messages?- Use the MIME type and standard RDF(Resource definition framework for resource encoding) to describe the resources
Hypermedia as the engine of application state? (HATEOAS) - Provide the URI information as links in resource itself to decouple clients from application specific URI knowledge
2. Client - Server - It allows for separation of concern and hence independent evolution of clients and servers.
3. Stateless - The request should have full information about the state of the resource and should not rely on server to have any knowledge of previous state.
4. Cacheable - The resource must declare itself cacheable if it can be cached. The caching can be done on client or server side.
5. Layered System - REST makes it possible to have a layered system with business layer separated from data and authentication layers, hence promoting decoupling and more robust software design.
6. Code on demand - REST can return executable code when needed and not just static resources.
The above gives a basic idea of what a RESTful architecture should be. But why did we need GraphQL can be answered once we look at what were the issues in REST APIs that graphQL tries to address.
These are some advantages that we get from GraphQL by declarative fetching over imperative fetching.
The next question that comes up is How GraphQL is able to achieve all this through a single endpoint.
GraphQL Schema and Resolutions
GraphQL is strongly typed and it uses its own Schema Definition Language (SDL) to define a schema that describes how the data can be queried and what all operations are allowed. This also adds an advantage of graphQL being implementation language agnostic. The SDL is called GraphQL Schema Definition Language. The schema can be thought of as an agreed contract between clients and servers.
type Employee {
id: ID
name: String
dept: Department
}
Above we define object type Employee.
领英推荐
id, name and department are the fields on the object type Employee and name field will have String values and can even be null. If name would have to have a value always when queried for, then it would be of the type String! where ! promises non nullable values. IDs in GraphQL are generally server generated identifiers and the type is not String. Department is another object type. So fields need not be just scalar types but they can even be other object types. GraphQL supports scalar types like Boolean, Float, String and Int. We can even define our own enums and custom scalar types like dates.
In a similar fashion we can define other object types and fields.
Every field on the GraphQL object can have zero or more arguments.
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
Here in the example above where a field has one argument.
We discussed above the object type in GraphQL but there are special types as well. These behave as entry points and hence are called special. These are query and mutation. While query type is used to read data, mutation can be used to create, update and delete the data. Hence using both query and mutation GraphQL is able to support the CRUD operations that we had with REST.
schema {
query: Query
mutation: Mutation
}
type Mutation{
createEmp(input: EmployeeInput): Employee
deleteEmp(id:ID!):Employee
}
type Query {
empById(id: ID): Employee
}
input EmployeeInput{
name: String
dept: String
}
type Employee {
id: ID
name: String
dept: Department
}
type Department{
id: ID
name: String!
}
The above is the schema for a simple employee management system. Where each employee belongs to a department.
Above schema specifies both query and mutation types apart from Employee and Department object types. They just like object types have fields which can have one or more arguments as specified here. Also Note that the entry points to query and mutation all return Employee type object that we have specified in the schema.
Let's see how a GraphQL query with the above schema would look like.
query {
? empById(id: "emp-1") {
? ? id
? ? name
? ? dept {
? ? ? id
? ? ? name
? ? }
? }
}
Here we specify that we want to read data since we are using the type query. The root of the query is employeeById that takes an argument to fetch the employee by id emp-1. Then we specify the fields that we want to get in the result which includes the id, name and dept of the employee. In dept we further tell what scalar fields we want to display. As can be seen here what is needed is specified or declared by the client based on the schema(contract) that the client and server agreed upon. Thus this allows clients to control what information they need, reducing the number of network calls and the data that flows on the network.
{
"errors": [],
"data": {
"empById": {
"id": "emp-1",
"name": "Ane",
"dept": {
"id": "dept-1",
"name": "HR"
}
}
},
"extensions": null,
"dataPresent": true
}
The response to the above query would look as displayed above. The response is in same format as the query. Stating with the result of employeeById and then giving details around values of each field.
mutation{
? deleteEmp(id:"253b6def-e365-43d3-b88d-c510cd05d430"){
? ? ? name
? }
}
The above mutation deletes an employee with the given id and returns the name of the deleted employee and not any other field. The word mutation tells that it is one of the create, update or delete operations. The response would look similar to the query response in structure.
{
"errors": [],
"data": {
"deleteEmp": {
"id": "253b6def-e365-43d3-b88d-c510cd05d430"
}
},
"extensions": null,
"dataPresent": true
}
Apart from query and mutations GraphQL has subscription where the clients can get updates pushed to them from the server when the data they care about changes.
Resolution in GraphQL
The next question that comes to mind is how is GraphQL able to getEmployeebyID or perform any of the CRUD operation. There is a concept of resolution in GraphQL where each query or mutation is resolved at the field level. The process starts from the root of the query/mutation, resolves all the fields at the same level concurrently and then the result of the resolution is passed by default to the fields one level below the current level.
query {
? empById(id: "emp-1") {
? ? id
? ? name
? ? dept {
? ? ? id
? ? ? name
? ? }
? }
}
For example in the above query retrieving information of an employee given its id. The root of the query is employeeById with a parameter id. As specified in the schema it returns an employee object type. This employee object type would be passed as default argument to resolvers for id, name and department. So for name field the resolver could just return name of this employee it got from root resolver execution.
Thus we got a basic idea about what is GraphQL , the issues that are addressed using GraphQL. Some basic elements of GraphQL like the most important schema and SDL.How GraphQL supports all CRUD operations on a single endpoint and how a query/mutation is able to produce result.
What's next
Sources of knowledge