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>
);
}
|
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:
- Create new request
- Set method to
POST - Set URL to GraphQL endpoint
- In Body tab, select
GraphQL - 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
}
}
|
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