GraphQL

GraphQL is a query language for APIs and a runtime for executing those queries by using a type system you define for your data. It was developed by Facebook in 2012 and released as an open-source project in 2015. GraphQL provides a more efficient, powerful, and flexible alternative to the traditional REST API.

Fundamental Concepts

  • Schema: The contract defining all data types, fields, and operations available in your API.
  • Queries: Read operations used to fetch data.
  • Mutations: Write operations used to create, update, or delete data.
  • Subscriptions: Real-time event streams built over WebSockets.
  • Resolvers: Server-side functions responsible for fetching data for each specific field in a schema.

Why use GraphQL Over REST?

Feature REST API GraphQL
Endpoints Multiple (/users, /posts/1, /comments) Single endpoint (/graphql)
Data Fetching Returns pre-defined payload (causes over/under-fetching) Client requests exact fields needed
Network Requests Requires multiple HTTP round-trips for nested resources Fetches related resources in a single query
Type SafetyRequires external tooling (OpenAPI/Swagger) Built-in strongly typed schema

Running a GraphQL Engine via Docker

One of the fastest ways to run a local GraphQL instance is using Hasura GraphQL Engine connected to a PostgreSQL database.

docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3.6'
services:
postgres:
image: postgres:15
restart: always
environment:
POSTGRES_PASSWORD: postgrespassword
ports:
- "5432:5432"

graphql-engine:
image: hasura/graphql-engine:v2.33.0
ports:
- "8080:8080"
restart: always
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_DEV_MODE: "true"
depends_on:
- postgres

  1. Start the containers:
1
docker-compose up -d
  1. Open your browser and navigate to http://localhost:8080/console.
  2. You will enter the Hasura console, where you can create database tables and immediately execute auto-generated GraphQL queries and mutations.

Understanding Data Storage in GraphQL

One important concept is that GraphQL does not store data itself. Instead, it acts as a layer between the client and the underlying data sources (like databases, REST APIs, or other services). The GraphQL server defines a schema that describes the types of data available and how to fetch them. When a client sends a query, the server resolves the query by fetching data from the appropriate sources and returning it in the requested format.

For Example:
Layer of GraphQL

The PostgreSQL database is responsible for storing the actual data. GraphQL is responsible for exposing that data through a strongly typed API.

With Hasura, the architecture looks like:
Simple Architecture

For example, the client may send:

1
2
3
4
5
6
query {
users {
id
name
}
}

Hasura translates the GraphQL operation into a database query similar to:

1
SELECT id, name FROM users;

The result is then returned to the client as JSON.

Creating the Database Tables

Consider a simple blogging application with two entities:

A user can write many posts.
A post belongs to one user.

The relationship is: One-to-Many (one user can have many posts)

Create the tables in PostgreSQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);

CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
author_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

The actual records are stored in PostgreSQL.
Creating the Database Tables

User Data

Post Data

After tracking these tables in Hasura, Hasura automatically generates GraphQL queries and mutations for them.

Querying Data and Mutations

A GraphQL query reads data.

1
2
3
4
5
6
7
query MyQuery {
users(limit: 10, offset: 0, order_by: {name: asc}, where: {}) {
id
email
name
}
}

The response will be:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"data": {
"users": [
{
"id": 1,
"email": "alice@example.com",
"name": "Alice"
},
{
"id": 2,
"email": "bob@example.com",
"name": "Bob"
},
{
"id": 3,
"email": "Marry@example.com",
"name": "Marry"
}
]
}
}

Querying Data

GraphQL uses mutations for operations that change data.

For example, creating a user:

1
2
3
4
5
6
mutation MyMutation {
insert_users_one(object: {email: "Marry@example.com", name: "Marry"}) {
email
name
}
}

Conceptually, Hasura converts this into something similar to:

1
INSERT INTO users (name, email)VALUES ('Marry', 'Marry@example.com');

The response will be:

1
2
3
4
5
6
7
8
{
"data": {
"insert_users_one": {
"email": "Marry@example.com",
"name": "Marry"
}
}
}

Mutations

Creating a Post
A post can reference an existing user through author_id.

1
2
3
4
5
6
7
8
9
10
11
12
13
mutation {
insert_posts_one(
object: {
title: "GraphQL Fundamentals"
content: "My first GraphQL article"
author_id: 1
}
) {
id
title
author_id
}
}

Create a post

Querying Relationships

Relationships are one of the most useful features of GraphQL.

After configuring a posts relationship on users, it is possible to query the user and their posts in a single GraphQL operation:

1
query { users { id name posts { id title created_at } } }

Querying Relationships

Conclusion

GraphQL provides a powerful and flexible way to interact with APIs, allowing clients to request exactly the data they need and enabling real-time updates through subscriptions. By understanding the fundamental concepts of GraphQL, developers can build efficient and scalable applications that leverage the benefits of this modern API technology.