How GraphQL Works
Table of contents
GraphQL is a query language and a runtime. As a query language, unlike SQL(Structured Query Language), it never interacts with a database. It speaks with the API directly about what it needs and a server-side program that runs your queries using the type of system you create for your data. This article explains what graphQL is, the different parts that make it work, and how these components interact.
If you find this topic and what will be discussed interesting, I am inviting you to grab a seat or anywhere comfortable to recline while reading. That is all you will be needing.
GraphQL Basic Concepts
Object Type: It is found in a graphQL schema. It is an important part of the schema that defines the object returns from the graphQL service and the object's field.
type User { id: ID! name: String! age: Int! isHomeless: Boolean occupation: String }
In the example above, the User object type has fields such as name and age. The words after the colon refer to the type of data the field will return. The fields with the exclamation mark at the end of the datatype are not nullable. Being not nullable means that the fields will always return with their corresponding value, at no time will the field be returned empty. Object types are used when returning a collection of values like the user's data and not a single value. The fields in the example above are all scalar types. To explain what a scalar type is, follow this link.
Input type: They are similar to the object type but are used to pass in an object for creation. They use the keyword input instead of type. The fields in the input type can also be defined as an argument on the mutation method as an input type.
input UserInput { name: String! age: Int! isHomeless: Boolean occupation: String }
Query: GraphQL query is similar to the GET HTTP request. It is used by the client to read data from the graphQL server.
type Query { getUser(id: ID): User }
Queries may have arguments. The example above will return a single user data based on its id. It is an example of a query type found in the schema. The request from the client side will look like this:
{ getUser{ id name age } }
The getUser part of the query on the client side is known as the root field, while the rest in the bracket is the requested payload. If we need more data, we need to update the payload section with more data as specified by the schema.
Mutation: GraphQL mutation enables a user to create new data or manipulate existing data.
type Mutation { createUser(input: UserInput): User }
On the client end, the request for mutation is similar to that of the query but comes with a mutation keyword, as seen below. Also, the arguments attached to it are necessary to either create or manipulate existing data
mutation { createUser({name: "prince", age: 20}) }
If two or more query requests are sent in at a time. They will all return at the same time. But in case of multiple mutation requests at a time, it returns one after the order in the same they were sent in.
Schema: Schemas are created using the GraphQL Schema Definition Language (SDL). This serves as a form of contract between the server and the client. It is similar to a menu at a restaurant. The menu tells you what the restaurant has available, and the customer is expected to order what is on the menu. So the schema tells the client what the server has to offer.
type User { id: ID! name: String! age: Int! isHomeless: Boolean occupation: String } input UserInput { name: String! age: Int! isHomeless: Boolean occupation: String } type Query { getUser(id: ID): User } type Mutation { createUser(input: UserInput): User }
Resolver: A resolver is a function that interacts with other backend components, e.g. database, REST API, etc., to return the necessary response for a graphQL query. Resolvers optionally accept four positional arguments, which are root, args, context value, and info.
root: This returns the results from the previous resolver in the resolver chain.
args: This is an object containing arguments passed by the query.
context: This is an object shared by all resolvers in a particular query.
info: This contains information about the execution state of the query.
How Does GraphQL Execute its Queries
Whether it's a mutation or a query, graphQL executes its queries following three(3) processes
Parse
Validate
Execution
Parse: The incoming request comes in as a string. This string is broken into meaningful substrings and parsed into an Abstract Syntax Tree (AST). The AST is based on GraphQL specifications found here. If an error exists, the server stops at this point and returns the error.
Validate: Once the parsing is done, the schema and the parsed request are sent in for validation. They are validated against some specific sets of rules. A detailed description of such rules is found here. It is important to note that if a request has been validated before, then this step might be skipped, and we move to the execution phase. If any error is found, the server stops, and the error message is returned.
Execution: This is the last phase, where validation occurs again. Once validation is passed, the appropriate resolver function is applied to each field. This is done recursively until all fields return a value. If a resolver function returns a promise, the execution is paused until the promise is fulfilled. The value in each field is converted to the agreed data type in the schema.
When all this is done, the response is packaged in a structure that is similar to the original query and is sent out typically in a JSON format.
Conclusion
If you have made it here, thanks. This article has tried to explain basic concepts that make graphQL work, also it has also explained how graphQL queries are executed. Finally, if you need to read in detail about how all these work, you can check out the graphQL specification document here.