Post

GraphQL Cheatsheet

They call it GraphQL because the data is all connected like a Graph.. And the QL part, well that stands for Query Language. For example, Reviews are connected to authors. and you only need to make 1 graph ql query to fetch all the reviews an author has created.

GraphQL make it possible to create some really creative yet complex queries just by structuring a query in a nested fasion. As all the data types are connected. Its a somple matter of building out your queries to get back the data you want.

Here we are retrieving data for the game with the id of 2. We are getting the title, review rating and the author name. 3 different data types, 1 query.

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
27
28
29
30
31
32
33
34
Query {
    game(id: "2") {
        title,
        review {
            rating,
            author {
                name
            }
        }
    }
}

GraphQL is a query language for APIs and a runtime for executing those queries with your existing data. Unlike REST, 
GraphQL gives clients the power to ask for exactly what they need and nothing more.

Here we query/retreive the data for exactly what we want:

```graphql
Query {
    course(id: "1") {
        id,
        title,
        thumbnail_url,
        author {
            name,
            id,
            courses {
                id,
                title
                thumbnail_url
                }
            }
        }
    }

typeDefs / Schema.js

Scema is a document which lists all the data types, properies and restrictions of the data types. The schema js will list all the data types as well as the entry points into the data These typeDefs are exported as jsx so that they can be imported into your App for use. Note the ! means its required when you pull it in.

Query

The query data type below is something that every graphql schema needs. its job is to define what data entry points are allowed. If a data entry point is allowed it will for instance send back a list of Game objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export const typeDefs = `graph
    type Game {
        id: ID!
        title: String!
        platform: [String] //array
        }
    type Review{
        id: ID!
        rating: Int!
        content: String //array
        }
    type Author {
        id: ID!
        name: String!
        verified: Boolean!
        }
    type Query {
        reviews: [Review]
        games: [Game]
        authors: [Author]

        }

Resolver Functions

How do you want to handle requests and queries for the data? These resolver functions are how you need to handle all the differeent types of queries: ie: nested queries, single id queries, related requeries.

Graphql is CRUD

Where postman is for testing rest. Appollo is for testing Graphql

Making queries

Appolo has a sanbox you can connect to for testing queries.

Here we create a custom query called “Review Query”

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Query ReviewQuery {

reviews {
    rating, 
    content, 
    id
    }
}



## What is GraphQL?

GraphQL is:
- A query language for your API
- A single endpoint that handles all requests
- Strongly typed (schema-based)
- Self-documenting
- Client-driven (request exactly what you need)

## GraphQL vs REST

| Feature | REST | GraphQL |
|---------|------|---------|
| Endpoints | Multiple (`/users`, `/posts`) | Single (`/graphql`) |
| Data fetching | Fixed structure per endpoint | Request exactly what you need |
| Over-fetching | Common (get all fields) | Never (specify fields) |
| Under-fetching | Common (multiple requests) | Rare (one request) |
| Versioning | URL versioning (`/v1/`, `/v2/`) | Schema evolution |
| Documentation | Manual (Swagger/OpenAPI) | Auto-generated from schema |

## Basic Concepts

### Schema

The schema defines your API's type system:

```graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
  createdAt: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
  post(id: ID!): Post
  posts: [Post!]!
}

type Mutation {
  createPost(title: String!, body: String!): Post!
  updatePost(id: ID!, title: String, body: String): Post!
  deletePost(id: ID!): Boolean!
}

Queries

Fetch data with queries:

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
27
28
29
30
31
32
33
34
35
# Basic query
query {
  users {
    id
    name
    email
  }
}

# Query with arguments
query {
  user(id: "123") {
    name
    email
  }
}

# Nested query
query {
  user(id: "123") {
    name
    posts {
      title
      createdAt
    }
  }
}

# Query with variables
query GetUser($userId: ID!) {
  user(id: $userId) {
    name
    email
  }
}

Mutations

Modify data with mutations:

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
27
28
29
30
# Create
mutation {
  createPost(title: "Hello GraphQL", body: "My first post") {
    id
    title
    createdAt
  }
}

# Update
mutation {
  updatePost(id: "456", title: "Updated Title") {
    id
    title
  }
}

# Delete
mutation {
  deletePost(id: "456")
}

# Mutation with variables
mutation CreatePost($title: String!, $body: String!) {
  createPost(title: $title, body: $body) {
    id
    title
    createdAt
  }
}

Fragments

Reusable pieces of query logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fragment UserDetails on User {
  id
  name
  email
}

query {
  user(id: "123") {
    ...UserDetails
    posts {
      title
    }
  }
}

Aliases

Query the same field with different arguments:

1
2
3
4
5
6
7
8
query {
  firstUser: user(id: "1") {
    name
  }
  secondUser: user(id: "2") {
    name
  }
}

GraphQL with JavaScript/React

Using Fetch API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const query = `
  query {
    users {
      id
      name
      email
    }
  }
`;

fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ query }),
})
  .then(res => res.json())
  .then(data => console.log(data));

With Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const query = `
  query GetUser($userId: ID!) {
    user(id: $userId) {
      name
      email
    }
  }
`;

const variables = { userId: '123' };

fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ query, variables }),
})
  .then(res => res.json())
  .then(data => console.log(data));

Apollo Client (React)

Installation

1
npm install @apollo/client graphql

Setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://example.com/graphql',
  cache: new InMemoryCache(),
});

function App() {
  return (
    <ApolloProvider client={client}>
      <YourApp />
    </ApolloProvider>
  );
}

Query Hook

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
import { gql, useQuery } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

function Users() {
  const { loading, error, data } = useQuery(GET_USERS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Mutation Hook

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
27
28
29
30
31
32
33
34
import { gql, useMutation } from '@apollo/client';

const CREATE_POST = gql`
  mutation CreatePost($title: String!, $body: String!) {
    createPost(title: $title, body: $body) {
      id
      title
      createdAt
    }
  }
`;

function CreatePostForm() {
  const [createPost, { data, loading, error }] = useMutation(CREATE_POST);

  const handleSubmit = (e) => {
    e.preventDefault();
    createPost({
      variables: {
        title: 'New Post',
        body: 'Post content',
      },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit" disabled={loading}>
        Create Post
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

GraphQL with Drupal

Install GraphQL Module

1
2
composer require drupal/graphql
drush en graphql

Or with DDEV:

1
2
ddev composer require drupal/graphql
ddev drush en graphql

Enable GraphQL Explorer

Navigate to /admin/config/graphql and enable the GraphQL Explorer (GraphiQL interface).

Access GraphQL Endpoint

  • Endpoint: /graphql
  • Explorer: /admin/config/graphql/servers/manage/default/explorer

Example Drupal Query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
query {
  nodeQuery(filter: {conditions: [{field: "type", value: "article"}]}) {
    entities {
      ... on NodeArticle {
        nid
        title
        body {
          value
        }
        created
      }
    }
  }
}

Query with Filters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
query {
  nodeQuery(
    filter: {
      conditions: [
        {field: "type", value: "article"}
        {field: "status", value: "1"}
      ]
    }
    sort: {field: "created", direction: DESC}
    limit: 10
  ) {
    entities {
      ... on NodeArticle {
        nid
        title
      }
    }
  }
}

Drupal GraphQL with React

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
27
28
29
30
31
32
33
34
35
import { gql, useQuery } from '@apollo/client';

const GET_ARTICLES = gql`
  query GetArticles {
    nodeQuery(filter: {conditions: [{field: "type", value: "article"}]}) {
      entities {
        ... on NodeArticle {
          nid
          title
          body {
            value
          }
        }
      }
    }
  }
`;

function Articles() {
  const { loading, error, data } = useQuery(GET_ARTICLES);

  if (loading) return <p>Loading articles...</p>;
  if (error) return <p>Error loading articles</p>;

  return (
    <div>
      {data.nodeQuery.entities.map(article => (
        <article key={article.nid}>
          <h2>{article.title}</h2>
          <div dangerouslySetInnerHTML={{ __html: article.body.value }} />
        </article>
      ))}
    </div>
  );
}

GraphQL Tools

GraphiQL

Interactive in-browser GraphQL IDE:

  • Test queries and mutations
  • Auto-completion
  • Schema documentation
  • Available at /graphql or /admin/config/graphql/servers/manage/default/explorer (Drupal)

Apollo Studio

Online GraphQL IDE and platform:

Postman

Test GraphQL APIs in Postman:

  1. Create new request
  2. Set method to POST
  3. Set URL to GraphQL endpoint
  4. In Body tab, select GraphQL
  5. Write your query

Command Line (curl)

1
2
3
curl -X POST https://example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ users { id name } }"}'

With variables:

1
2
3
4
5
6
curl -X POST https://example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUser($id: ID!) { user(id: $id) { name } }",
    "variables": { "id": "123" }
  }'

Authentication

Bearer Token

1
2
3
4
5
6
7
const client = new ApolloClient({
  uri: 'https://example.com/graphql',
  headers: {
    authorization: `Bearer ${token}`,
  },
  cache: new InMemoryCache(),
});

Basic Auth (Drupal)

1
2
3
4
5
6
7
const client = new ApolloClient({
  uri: 'https://example.com/graphql',
  headers: {
    authorization: `Basic ${btoa('username:password')}`,
  },
  cache: new InMemoryCache(),
});

OAuth 2.0 (Drupal)

1
2
composer require drupal/simple_oauth
drush en simple_oauth

Request token:

1
2
3
4
5
6
curl -X POST https://example.com/oauth/token \
  -d "grant_type=password" \
  -d "client_id=your-client-id" \
  -d "client_secret=your-client-secret" \
  -d "username=your-username" \
  -d "password=your-password"

Error Handling

GraphQL returns errors in a standardized format:

1
2
3
4
5
6
7
8
9
{
  "errors": [
    {
      "message": "Cannot query field 'invalidField' on type 'User'.",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["user", "invalidField"]
    }
  ]
}

Handle errors in Apollo Client:

1
2
3
4
5
6
7
const { loading, error, data } = useQuery(GET_USERS);

if (error) {
  console.error('GraphQL Errors:', error.graphQLErrors);
  console.error('Network Errors:', error.networkError);
  return <p>Error: {error.message}</p>;
}

Best Practices

1. Use Fragments for Reusability

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fragment UserFields on User {
  id
  name
  email
}

query {
  currentUser {
    ...UserFields
  }
  users {
    ...UserFields
  }
}

2. Use Variables Instead of String Interpolation

1
2
3
4
5
6
// Bad
const query = `query { user(id: "${userId}") { name } }`;

// Good
const query = `query GetUser($userId: ID!) { user(id: $userId) { name } }`;
const variables = { userId: '123' };

3. Request Only What You Need

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Bad - requesting everything
query {
  users {
    id
    name
    email
    address
    phone
    bio
    avatar
  }
}

# Good - request only what's needed
query {
  users {
    id
    name
  }
}

4. Use Aliases for Multiple Queries

1
2
3
4
5
6
7
8
query {
  published: posts(filter: {status: PUBLISHED}) {
    title
  }
  draft: posts(filter: {status: DRAFT}) {
    title
  }
}

5. Implement Pagination

1
2
3
4
5
6
query GetPosts($limit: Int!, $offset: Int!) {
  posts(limit: $limit, offset: $offset) {
    id
    title
  }
}

Common Pitfalls

Be careful with nested queries that can cause N+1 problems:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# This might cause performance issues
query {
  posts {
    author {
      posts {
        author {
          posts {
            # too deep
          }
        }
      }
    }
  }
}

Not Handling Loading States

Always handle loading and error states:

1
2
3
4
5
6
7
const { loading, error, data } = useQuery(QUERY);

if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;

return <Component data={data} />;

Caching Issues

Clear cache when needed:

1
2
client.cache.reset(); // Clear entire cache
client.refetchQueries({ include: [GET_USERS] }); // Refetch specific queries

Resources

This post is licensed under CC BY 4.0 by the author.