What is GraphQL? An Overview of Concepts

Abhinav Vinci
6 min readMay 5, 2023

--

What is GraphQL?

GraphQL is a query language and runtime engine for executing those queries. Its used to define APIs.

  • It allows clients to define the structure of the data they need and the server to return only the requested data, making network requests more efficient and reducing overfetching and underfetching of data.
  • In GraphQL, clients send queries to a server specifying the data they need, and the server responds with a JSON object containing the requested data.

At a high level, a GraphQL runtime is responsible for

  • parsing and validating incoming queries,
  • executing them against a data source, and returning the results to the client.
  • The runtime must also handle error cases and ensure that the response conforms to the GraphQL specification.

GraphQL example: Here is a simple example of a GraphQL query that retrieves the name and age of a user:

query {
user(id: "123") {
name
age
}
}

In this case, the client is requesting the name and age fields of the user object. The user object is identified by the id argument passed to the user field.

Why we need GraphQL ??

  1. Efficient and Flexible: GraphQL allows clients to request only the data they need. This can lead to faster network requests and better app performance.
  2. Strong typing and validation: GraphQL schemas provide strong typing and validation of data, ensuring that clients can only request valid data and reducing the risk of runtime errors.
  3. Client-driven development: GraphQL allows clients to drive the development of the API, as clients can specify exactly what data they need and how it should be returned. This can lead to a more collaborative and iterative development process between front-end and back-end teams.
  4. API consolidation: GraphQL provides the ability to combine data from multiple sources in a single query which can make it easier to consolidate multiple APIs into a single GraphQL API, reducing complexity and improving performance.

Additionally, GraphQL is highly flexible, allowing clients to request data from multiple sources and aggregate it in a single query

https://www.rapid7.com/blog/post/2022/11/14/graphql-security-the-next-evolution-in-api-protection/

Key concepts in GraphQL

Schema

A GraphQL schema is a contract between the client and the server, defining the types of data that can be retrieved or modified using GraphQL operations. A schema includes types, fields, queries, mutations, and subscriptions. It defines the structure of the data, as well as the operations that can be performed on it. Ex :

type Query {
book(id: ID!): Book
}
type Book {
id: ID!
title: String!
author: String!
}

This schema defines a Query type with a single field book, which takes an ID argument and returns a Book object with an id, title, and author field.

Types: In GraphQL, types define the shape of the data that can be queried or returned by the server. There are several built-in scalar types, such as String, Int, and Boolean, as well as custom object types defined in the schema.

Fields: Fields are the properties of an object that can be queried or returned by the server. Fields can be scalar types, such as String or Int, or custom object types.

Queries: Queries are GraphQL operations that retrieve data from the server. Queries define the structure of the data to be returned and can include arguments to filter or paginate the results.

Mutations

Mutations are GraphQL operations that modify data on the server. Mutations define the input data and the expected output data and can include arguments to specify which data to modify.

mutation {
createBook(input: { title: "My Book", author: "John Doe" }) {
id
title
author
}
}

This mutation creates a new Book object with a title of "My Book" and an author of "John Doe", and returns the id, title, and author fields of the newly created Book object.

Advanced Concepts:

Subscriptions: Subscriptions are GraphQL operations that enable real-time data fetching. Subscriptions define a set of events that the client is interested in and receive updates from the server when those events occur.

Variables: Variables allow clients to parameterize GraphQL operations, providing dynamic values for arguments and reducing the need to generate different queries for different input data.

Fragments:

Fragments in GraphQL allow you to define reusable pieces of query syntax. You can define a fragment once and then reference it in multiple queries or other fragments, making your queries more modular and easier to maintain.

Here’s an example of how to define and use a fragment in GraphQL. Suppose you have the following GraphQL schema:

type Query {
book(id: ID!): Book
}
type Book {
id: ID!
title: String!
author: String!
pages: Int!
publisher: String!
year: Int!
}

You want to retrieve the title, author, and pages fields of a book in one query and then retrieve the publisher and year fields of the same book in another query. To avoid duplicating the common fields, you can define a fragment for the common fields and use it in both queries.

Here’s an example implementation in GraphQL:

fragment bookFields on Book {
id
title
author
pages
}
query {
book(id: "1") {
...bookFields
}
}
query {
book(id: "1") {
...bookFields
publisher
year
}
}

Directives:

Directives in GraphQL allow you to provide additional instructions to the server on how to execute a query or mutation. Directives can be used to conditionally include or exclude fields, alter the shape or type of the response data, or provide metadata to the server.

Here’s an example of how directives can be used in a GraphQL query:

query ($includeEmail: Boolean!) {
user(id: "123") {
id
name
email @include(if: $includeEmail)
posts {
id
title
}
}
}

The @include directive allows you to conditionally include a field based on a Boolean variable ($includeEmail in this case),

In this example, if the value of $includeEmail is true, the email field will be included in the response. If the value is false, the email field will be excluded from the response.

Resolvers:

In GraphQL, resolvers are used to resolve queries or mutations.

GraphQL query is a request for specific data and its shape is defined by the schema. Each field in the query corresponds to a resolver function which is responsible for returning the data for that field.

Suppose you have a database of users and their respective purchases. Each purchase has a price and a date associated with it. You want to use GraphQL to retrieve the total amount spent by each user in a given time range.

type User {
id: ID!
name: String!
purchases(startTime: DateTime!, endTime: DateTime!): [Purchase!]!
totalSpent(startTime: DateTime!, endTime: DateTime!): Float!
}
type Purchase {
id: ID!
user: User!
price: Float!
date: DateTime!
}

The User type has a field called purchases. This field returns an array of Purchase objects made by the user within the specified time range.

It also has a field called totalSpent that returns a float representing the total amount spent by the user within the specified time range.

To perform the aggregation, we would need to write a resolver function for the totalSpent field. Ex:

const resolvers = {
User: {
totalSpent: async (parent, { startTime, endTime }, context) => {
const purchases = await context.db.Purchase.find({
user: parent.id,
date: {
$gte: startTime,
$lte: endTime,
},
});
const total = purchases.reduce((acc, purchase) => acc + purchase.price, 0);
return total;
},
},
};

This resolver function uses the find method from the database library to retrieve all purchases made by the user within the specified time range. It then uses the reduce method to calculate the total amount spent by summing up the prices of all purchases.

With this resolver function in place, we can now query the totalSpent field for each user within a given time range:

query {
users {
id
name
totalSpent(startTime: "2023-01-01T00:00:00.000Z", endTime: "2023-05-01T00:00:00.000Z")
}
}

Why Resolvers?

https://www.apollographql.com/docs/technotes/TN0022-graphql-adoption-patterns/

Resolvers are important because they allow you to fetch data from different sources, combine data from multiple sources, and transform data as needed. For example, you might use resolvers to fetch data from a database, a REST API, or a third-party service.

  • Resolvers also allow you to handle complex data structures and relationships between types in the schema. For example, you might have a query that returns a list of users, and each user has a list of posts. The resolver for the “users” field might fetch the user data from a database, and the resolver for the “posts” field might fetch the post data for each user from a separate API.
  • In addition to fetching data, resolvers can also perform validation, authentication, and authorization checks. For example, you might use a resolver to check if the current user has permission to view or modify a particular field.

--

--