Introduction to GraphQL
GraphQL is a query language for your API that allows you to ask for specific data from a server, and get exactly what you need in a single request. It was created by Facebook in 2012 and has gained popularity in recent years as an alternative to REST APIs.
Why use GraphQL?
One of the main advantages of GraphQL is that it allows you to request only the data you need, rather than getting a fixed set of data from an endpoint like you would with a REST API. This makes it more flexible and efficient, as you don’t have to make multiple requests to get all the data you need.
Another benefit of GraphQL is that it has a strong type system, which helps catch errors before they are sent to the server. This can save time and resources, as you can catch errors on the client side rather than having to wait for a server response.
Getting started with GraphQL
To get started with GraphQL, you’ll need a server that supports it. There are many options for setting up a GraphQL server, including using a pre-built solution like Graphcool or Apollo Server, or building your own server from scratch.
Once you have a server set up, you can use a GraphQL client like Apollo Client or Relay to make requests to the server. GraphQL clients allow you to send queries and mutations (operations that modify data) to the server, and receive responses in the form of JSON data.
1
2
3
4
5
6
7
8
query {
user(id: 1) {
name
email
}
}
This query asks for the name and email of the user with an id of 1. The server will then respond with a JSON object containing the requested data.
Making a GraphQL mutation
To make a GraphQL mutation, you’ll need to send a request to the server with a mutation string. A mutation string is similar to a query string, but it describes an operation that modifies data rather than just retrieving it.
Here’s an example of a simple GraphQL mutation:
1
2
3
4
5
6
7
8
mutation {
createUser(name: "John Smith", email: "[email protected]") {
name
email
}
}
This mutation creates a new user with the name “John Smith” and email “[email protected]”, and returns the name and email of the new user.
Types, Inputs and Schemas
In GraphQL, types are used to define the shape of the data that is being transferred. Each type has a set of fields, and each field has a type and a description. This helps ensure that the data being transferred is consistent and predictable.
In addition to types, GraphQL also has the concept of a schema, which is a way of defining the overall structure of an API. A schema consists of a set of types and the relationships between them, as well as the root types (query, mutation, and subscription) that define the entry points for the API.
Here is an example of a simple GraphQL type and schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User]
}
type Mutation {
createUser(name: String!, email: String!): User
updateUser(id: ID!, name: String, email: String): User
deleteUser(id: ID!): User
}
schema {
query: Query
mutation: Mutation
}
In this example, we have defined a User type with three fields: id, name, and email. We have also defined a Query type with two root fields: user, which allows us to retrieve a single user by id, and users, which returns a list of all users. Finally, we have a Mutation type with three root fields: createUser, updateUser, and deleteUser, which allow us to create, update, and delete users.
GraphQL also has input types. input types are used to pass complex data structures as arguments to queries and mutations. They allow you to pass multiple fields as a single argument, rather than passing each field as a separate argument.
Input types are defined in the schema using the input keyword. Here is an example of an input type:
1
2
3
4
5
6
7
input UserInput {
name: String!
email: String!
age: Int
address: String
}
In this example, the UserInput input type has four fields: name, email, age, and address. These fields can be used as an argument in a mutation like this:
1
2
3
4
5
6
7
mutation {
createUser(input: UserInput) {
id
name
email
}
}
To pass values for the fields of an input type, you can use an object literal in the arguments of a query or mutation. Here is an example of how to pass values for the UserInput input type:
1
2
3
4
5
6
7
8
9
10
11
12
client.mutate({
mutation: createUser,
variables: {
input: {
name: 'John Smith',
email: 'john@example.com',
age: 30,
address: '123 Main St.'
}
}
});
Input types are useful for passing complex data structures as arguments, and can make your GraphQL API more flexible and efficient.
It’s important to note that input types are different from regular GraphQL types. Regular types are used to define the shape of the data that is being transferred, while input types are used to pass data as arguments to queries and mutations.
Non-nullables and Optionals
You’ve probably seen the !
operator a bunch of times now. What does it mean in gql. Well, in GraphQL, you can use the !
symbol to indicate that a field is non-nullable. This means that the field must be present in the response, and cannot have a value of null. If a field is mandatory it only means the server must provide a value for it, it does not mean that the client must request it. The client is always free to do what it wants.
If the server can not return a value for a mandatory (non-nullable) field, it will return an error, even if it has the values for all of the other fields in the response.
Here is an example of a GraphQL type with non-nullable fields:
1
2
3
4
5
6
7
type User {
id: ID! //the ! means the id field must have a value
name: String! //the ! means the name field must have a value
email: String! //the ! means the email field must have a value
age: Int // the lack of ! means the age field can be nullable, meaning the server could return null as it's value
address: String // the lack of ! means the age address can be nullable, meaning the server could return null as it's value
}
Optionals
In GraphQL, optionals are fields that may or may not be present in a response. Optionals are indicated by a ?
at the end of the field name.
Here is an example of a GraphQL type with optionals:
1
2
3
4
5
6
7
type User {
id: ID!
name: String!
email: String!
age: Int?
address: String?
}
In this example, the age and address fields are optionals, which means they may or may not be present in a response.
When making a query or mutation, you can specify which optionals you want to include in the response by including them in the query string. If you don’t include an optional field in the query, it will not be included in the response.
Here is an example of a query that includes optional fields:
1
2
3
4
5
6
7
8
9
query {
user(id: 1) {
name
email
age
address
}
}
In this example, the response will include all of the fields for the user, including the optional age and address fields if they are present.
It’s important to note that optionals are not the same as nullable fields. A nullable field is a field that can have a value of null, while an optional field may or may not be present in a response. Key take away is that all fields are nullable by default in graphql, and you must explicitly suffix the field with a
!
symbol to indicate that a field is non-nullable.
Optionals are an important concept in GraphQL, as they allow you to specify which fields you want to include in a response. By using optionals, you can make your GraphQL API more efficient and flexible, and better tailored to the needs of your clients.
Resolvers
In order to actually retrieve or modify data, GraphQL relies on functions called resolvers. Resolvers are responsible for fetching the data for a field, or performing an operation when a mutation is called.
Here is an example of some simple resolvers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const resolvers = {
Query: {
user: (parent, args, context, info) => {
// fetch user data from database or API
return { id: 1, name: 'John Smith', email: 'john@example.com' }
},
users: (parent, args, context, info) => {
// fetch list of users from database or API
return [{ id: 1, name: 'John Smith', email: 'john@example.com' }, { id: 2, name: 'Jane Doe', email: 'jane@example.com' }]
}
},
Mutation: {
createUser: (parent, args, context, info) => {
// create new user in database or API
return { id: 3, name: args.name, email: args.email }
},
updateUser: (parent, args, context, info) => {
// update user in database or API
return { id: args.id, name: args.name, email: args.email }
},
deleteUser: (parent, args, context, info) => {
// delete user from database or API
return { id: args.id }
}
}
}
In this example, we have defined resolvers for each of the root fields in our schema.
In the Query type, we have defined two resolvers: user and users. The user resolver retrieves a single user by id, while the users resolver retrieves a list of all users. In the Mutation type, we have defined three resolvers: createUser, updateUser, and deleteUser, which allow us to create, update, and delete users.
It’s important to note that the actual implementation of the resolvers will depend on the specific use case and the data source being used. For example, the resolvers may fetch data from a database, make an HTTP request to a REST API, or perform some other operation.
Fragments and variables
In GraphQL, fragments are reusable pieces of a query that can be used to avoid repeating the same fields in multiple queries. This can make your GraphQL code more DRY (Don’t Repeat Yourself) and easier to maintain.
Here is an example of a fragment:
1
2
3
4
5
6
fragment UserFields on User {
id
name
email
}
This fragment defines a set of fields that can be used in multiple queries or mutations. Here is an example of how it could be used:
1
2
3
4
5
6
7
8
9
10
11
query {
user(id: 1) {
...UserFields
}
}
mutation {
createUser(name: "John Smith", email: "[email protected]") {
...UserFields
}
}
In this example, the …UserFields syntax tells GraphQL to include all of the fields defined in the UserFields fragment.
Variables
GraphQL also supports variables, which allow you to pass dynamic values into a query or mutation. Variables are defined in the query or mutation using the $
symbol, and their values are passed as an argument to the GraphQL client.
Here is an example of a query using variables:
1
2
3
4
5
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}
To execute this query, you would pass a value for the $id variable when making the request. For example:
1
2
3
4
5
6
7
client.query({
query: GetUser,
variables: {
id: 1
}
});
Conclusion
GraphQL is a powerful and flexible tool for building APIs. It allows you to request only the data you need, and makes it easy to evolve your API over time. Whether you’re building a new API or looking to modernize an existing one, learning how to use GraphQL can help you create more efficient and flexible APIs.