---
title: "GraphQL 101: Learn to Build with GraphQL APIs"
description: "Learn to build with GraphQL APIs and understand schemas, queries, mutations, and integrations through comprehensive examples and illustrations"
author: "Haimantika Mitra"
published: "2024-09-12"
url: "https://hashnode.com/blog/graphql-101-learn-to-build-with-graphql-apis"
tags: ["APIs","GraphQL","Beginner Developers","Developer","JavaScript"]
---

# GraphQL 101: Learn to Build with GraphQL APIs

Whether you are someone who is getting started with tech or a junior software engineer, APIs are something that you will come across almost every day in your job.

REST and GraphQL APIs are the most widely used ones in the industry, and in this article, we will cover the basics, along with a demo on how you can integrate GraphQL APIs.

**TL;DR**

Check out the webinar on GraphQL 101, that covers everything we mention in this article:

%[https://www.youtube.com/watch?v=2LyhUvdMCbI] 

---

## Understanding how GraphQL works

When working with REST APIs, data is retrieved from specific endpoints. Each endpoint has a defined structure for the information it returns. This means the data requirements of a client are embedded in the URL it connects to.

GraphQL takes a different approach. Instead of multiple endpoints with fixed data structures, GraphQL APIs usually expose only *a* ***single endpoint***. This is possible because the data structure is not fixed. Instead, it is flexible and allows the client to specify what data is needed.

### Key Concepts

* **Schema**: Defines the structure of your API, the types of data that can be queried, and how they relate to each other.
    
* **Fields:**  Specific pieces of data you request from a type in a query.
    
* **Queries**: The method by which you request specific data.
    
* **Mutations**: Used to modify data (create, update, delete).
    
* **Types**: The building blocks of a schema, representing the shape and structure of data (e.g., String, Int, User, etc.).
    

### Schema and types

A GraphQL server connects the client to data sources, allowing flexible and efficient data queries and manipulations. When a client sends a request, the server uses the `GraphQL Schema` to validate and execute it, then responds with the data or errors. The server relies on its `Schema` to handle these requests and define available operations.

Types are the building blocks of a GraphQL schema. They describe the shape of the data that can be queried. A `GraphQL Type` defines entities with their fields. Here are the different available types:

* Scalar Type
    
* Object Type
    
* Input Types
    
* Enumeration Type
    
* Union and Interface Type
    
* Lists and Non-Null
    

Let us understand it with an example query from [Hashnode API](https://apidocs.hashnode.com/), which checks if a custom domain is available or not:

```graphql
query CheckCustomDomainAvailability($input: CheckCustomDomainAvailabilityInput!) {
  checkCustomDomainAvailability(input: $input) {
    domainAvailable
  }
}
```

In this example:

1. **Schema elements:**
    
    * `CheckCustomDomainAvailability` is the name of the query operation.
        
    * `checkCustomDomainAvailability` is a field on the root Query type in the schema.
        
2. **Types:**
    
    * `CheckCustomDomainAvailabilityInput!` is an input type. The `!` indicates that it's a non-nullable type, meaning it's required.
        
    * There's an implied return type for the `checkCustomDomainAvailability` field, which includes a `domainAvailable` field. This return type isn't named in the query, but it exists in the schema.
        

### Mutation

A mutation in GraphQL is an operation used to modify data on the server. While queries are used to read data, mutations are used to create, update, or delete data. Mutations are essential for any operation that changes the state of the data on the server.

Let us understand with an example. The `acceptInviteToPublication` mutation in [Hashnode GraphQL APIs](https://apidocs.hashnode.com/) is used to accept an invitation to join a publication.

```graphql
mutation AcceptInviteToPublication($input: AcceptInviteToPublicationInput!) {
  acceptInviteToPublication(input: $input) {
    success
  }
}
```

In this example:

1. `mutation` is the operation type, indicating that this is a mutation rather than a query.
    
2. `AcceptInviteToPublication` is the name of the mutation operation.
    
3. `$input: AcceptInviteToPublicationInput!` is a variable declaration. It specifies that this mutation expects an input of type `AcceptInviteToPublicationInput`, and the `!` indicates that this input is required (non-nullable).
    
4. `acceptInviteToPublication(input: $input)` is the actual mutation being called. It's a field on the root Mutation type in the schema, and it takes the `input` argument.
    
5. `success` is a field on the return type of the `acceptInviteToPublication` mutation, a boolean indicating whether the operation was successful.
    

---

## GraphQL Integrations with Client

Now that you have an idea of GraphQL schema, types, and mutations, let’s go over several ways to make requests to Hashnode GrapQL API, for example. The same examples used for Hashnode GraphQL API can be used for all GraphQL APIs. Since our client application is mostly built with JavaScript, let’s see how to make Graphql API requests with just Vanilla JavaScript before using [URQL a graphql client](https://commerce.nearform.com/open-source/urql/) for the final example.

### **Basic GraphQL Query**

A basic query allows you to fetch specific fields from the GraphQL API. In the example below, we’ll get the user **“Favourite“** and these fields **name, username, profilePicture,** and **tagline** from the [Hashnode Graphql API](https://gql.hashnode.com/).

```javascript
const query = `
  query GetUserProfile {
    user(username: "Favourite") {
      id
      username
      name
      profilePicture
      tagline
    }
  }
`;

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

* **query**: Defined is the GraphQL query operation as a string.
    
* **fetch**: This sends a POST request to the GraphQL endpoint.
    
* **URL**: The Hashnode GraphQL API endpoint ([https://api.hashnode.com/](https://api.hashnode.com/)).
    
* **Headers**: The Content-Type is set to `application/json`
    
* **Body**: The request body includes the query serialized into JSON.
    

### **Query with Aliases**

GraphQL aliases allow you to fetch the same field multiple times with different arguments or rename the results for better clarity. Let’s see how we can achieve this below:

```javascript
const query = `
   query {
      favUser: user(username: "Favourite") {
        name
      }
      anotherUser: user(username: "Haimantika") {
        name
      }
  }
`

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

* From the query **favUser** and **anotherUser** are aliases that rename the results, allowing you to query multiple users - (“Favourite“ & “Haimantika“) in one request.
    

The result of the query would be in this structure, where you have different names of users with one request.

```javascript
{
  data: {
    favUser: { name: 'Favourite Jome' },
    anotherUser: { name: 'Haimantika Mitra' }
  }
}
```

### **Query with Arguments**

Arguments are inputs you provide to fields or queries to filter or modify the data you retrieve. They allow you to pass specific parameters to control the query results.

If you didn’t notice, we’ve been using arguments in the previous examples to get the user information from a specific user - **“Favourite“.** In the example below, we pass two new arguments to a field - **posts.**

```javascript
const query = `
  query GetUserWithPosts {
    user(username: "Favourite") {
      id
      name
      posts(page: 1, pageSize: 5) {
        nodes {
          id
          title
        }
      }
    }
  }
`;

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

Here, `page: 1` and `pageSize: 5` are arguments passed to the user field, telling the API to return five posts from the user publications on the first page.

### **Query with Variables**

Queries with variables allow you to pass dynamic inputs into a query, making the query flexible and reusable instead of hard-coded like the previous examples. Here’s how to query with variables:

```javascript
const query = `
  query GetUserWithPosts(
    $username: String!, 
    $page: Int!, 
    $pageSize: Int!
  ) {
    user(username: $username) {
      id
      name
      posts(page: $page, pageSize: $pageSize) {
        nodes {
          id
          title
        }
      }
    }
  }
`;

const variables = {
    username: "Haimantika",
    page: 1,
    pageSize: 5
}

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

**Explanation**:

* **Query Name:** GetUserWithPosts is the query name that accepts three variables
    
* **Variables:** The query accepts three variables:
    
    * **$username:** String! – a required string for the username.
        
    * **$page:** Int! – a required integer to define the page of posts.
        
    * **$pageSize:** Int! – a required integer to define the number of posts per page.
        
* **Query**: The query requests the `id` and `name` of the user, along with their posts `id` and title, using pagination (`page` and `pageSize`).
    

### Query with Fragments

Fragments allow you to reuse query fields in different parts of the query or across multiple queries. In the example below, we have a fragment named **PostDetails**, which will be used for the type **Post.**

```javascript
const fragment = `
  fragment PostDetails on Post {
    id
    title
    brief
    publishedAt
  }
`;

const query = `
  ${fragment}  # Include the fragment in the query
  query GetUserWithPosts($username: String!) {
    user(username: $username) {
      name
      posts(page: 1, pageSize: 2) {
        nodes {
          ...PostDetails  # Using the fragment here
        }
      }
    }
  }
`;


const variables = {
  username: "Favourite"
};

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

To see how fragment shines, in the same query, let’s get the publication details of a blog using the same fragment **PostDetails.**

```javascript
const fragment = `
  fragment PostDetails on Post {
    id
    title
    brief
    publishedAt
  }
`;

const query = `
  ${fragment}  # Include the fragment in the query
  query GetUserWithPosts($username: String!) {
    user(username: $username) {
      name
      posts(page: 1, pageSize: 2) {
        nodes {
          ...PostDetails  # Use the fragment here
        }
      }
    }
    publication(host: "blog.favouritejome.dev") {
      id
      title
      post(slug: "why-learn-nextjs-if-i-already-know-reactjs") {
          ...PostDetails # Use the fragment here
      }
    }
  }
`;


const variables = {
  username: "Favourite"
};

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

### Mutation

As mentioned earlier, mutation operation allows for the modification of data. When creating, deleting or updating action needs to be done, we use a mutation.

With Hashnode API, all mutations require an authentication header to make sure the correct user is updating their data, and you can get your Personal Access Token from the [developer settings](https://hashnode.com/settings/developer).

Once you’ve got your Personal Access Token, let’s see how we can create a draft post using the `createDraft` mutation from [Hashnode API](https://apidocs.hashnode.com/#mutation-createDraft).

```javascript
const mutation = `
  mutation CreateDraft($input: CreateDraftInput!) {
    createDraft(input: $input) {
      draft {
        id
        title
        slug
      }
    }
  }
`;

const variables = {
  input: {
    title: "My New Post",
    slug: 'new-post',
    publicationId: "PUBLICATION_ID",
    contentMarkdown: "This is the content of the post.",
    tags: [],
  }
};

fetch('https://gql.hashnode.com/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `YOUR_PERSONAL_ACCESS_TOKEN` // Replace with your API key
  },
  body: JSON.stringify({
    query: mutation,
    variables
  })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```

You need to get the `PUBLICATION_ID` from your dashboard URL:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1726146738666/647a1128-6aad-4ff5-9166-4aab2ded413d.png align="center")

## Starter Kit Code Example

Now that you have a solid foundation of GraphQL operations, that is, query and mutations, let’s go over the [Hashnode starter-kit](https://github.com/Hashnode/starter-kit), to build with GraphQL in production. In the example, we’ll be making use of the **hashnode** theme.

> To follow along, kindly clone the starter-kit repository: [https://github.com/Hashnode/starter-kit](https://github.com/Hashnode/starter-kit)

We will be using [Hashnode’s GraphQL APIs](https://hashnode.com/legacy-api), to list the followers our publication has, and this is how it will look 👇

![](https://cdn.discordapp.com/attachments/1199373948329144420/1283773013732364298/841_2x_shots_so.png?ex=66e43649&is=66e2e4c9&hm=bc454ef4bc22afb4f5166489ce11d66c2c9788324e0529ad6b11432112c21d19&= align="left")

Let’s start by creating a new file in the queries folder: ***lib/api/queries/UserFollowers.graphql***

```graphql
query UserFollowers($username: String!, $page: Int!, $pageSize: Int!) {
  user(username: $username) {
    id
    followers(page: $page, pageSize: $pageSize) {
      nodes {
        ...User
      }
    }
  }
}
```

And the user fragment: ***lib/api/fragments/User.graphql***

```graphql
fragment User on User {
  id
  username
  profilePicture
  name
  tagline
}
```

In the `pages` folder, create a `followers.tsx` file with the following code:

```javascript
import request from 'graphql-request';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import { useEffect, useState } from 'react';
import { useQuery } from 'urql';
import { AppProvider } from '../components/contexts/appContext';
import { Header } from '../components/header';
import { Layout } from '../components/layout';
import {
	PublicationByHostDocument,
	PublicationByHostQuery,
	PublicationByHostQueryVariables,
	PublicationFragment,
	UserFollowersDocument,
	UserFragment,
} from '../generated/graphql';

const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;

type Props = {
	publication: PublicationFragment;
};

const Followers = (props: Props) => {
	const { publication } = props;

	const [page, setPage] = useState(1);
	const [followersList, setFollowersList] = useState<UserFragment[]>([]); // To append followers
	const [loadingMore, setLoadingMore] = useState(false);

	const [{ data, fetching }] = useQuery({
		query: UserFollowersDocument,
		variables: {
			username: publication.author.username!,
			page: page,
			pageSize: 20,
		},
	});

	// Effect to append new followers when data changes
	useEffect(() => {
		if (data?.user?.followers?.nodes) {
			setFollowersList((prev) => [...prev, ...data?.user?.followers.nodes!]);
			setLoadingMore(false); // Stop loading state after fetching
		}
	}, [data]);

	// Load more function to increment the page
	const loadMore = () => {
		setLoadingMore(true);
		setPage((prevPage) => prevPage + 1);
	};

	return (
		<>
			<AppProvider publication={publication}>
				<Layout>
					<Head>
						<title>Followers - {publication.title}</title>
					</Head>
					<Header isHome={true} />
					<div className="feed-width mx-auto my-10 md:w-2/3">
						<h1 className="mb-5 text-3xl font-bold">Followers</h1>

						<div className="grid grid-cols-2 gap-2">
							{followersList?.map((follower) => (
								<div key={follower.id} className="flex gap-2 bg-slate-400/20 p-3">
									<img
										src={follower.profilePicture!}
										alt={follower.name}
										className="h-10 w-10 rounded-full"
									/>
									<div className="flex-1">
										<h2 className="font-bold">{follower.name}</h2>
										<p>{follower.username}</p>
									</div>
								</div>
							))}
						</div>

						{/* Load More Button */}
						<div className="mt-5 text-center">
							{!fetching && (
								<button
									onClick={loadMore}
									className="bg-primary-600 rounded-full  px-4 py-2 text-white"
									disabled={loadingMore}
								>
									{loadingMore ? 'Loading...' : 'Load More'}
								</button>
							)}
						</div>
					</div>
				</Layout>
			</AppProvider>
		</>
	);
};

export default Followers;

export const getServerSideProps: GetServerSideProps = async () => {
	const data = await request<PublicationByHostQuery, PublicationByHostQueryVariables>(
		GQL_ENDPOINT,
		PublicationByHostDocument,
		{
			host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
		},
	);

	const publication = data.publication;
	if (!publication) {
		return {
			notFound: true,
		};
	}

	return {
		props: {
			publication,
		},
	};
};
```

---

## Resources

If you have made it this far, we hope you have not only learned the basics but also how to integrate GraphQL APIs into your code.

Here are some resources to help you learn more:

* [GraphQL documentation](https://graphql.org/learn/)
    
* [Hashnode API documentation](https://apidocs.hashnode.com/)
    
* [URQL documentation](https://commerce.nearform.com/open-source/urql/docs/)
    
* [Hashnode starter kit](https://github.com/Hashnode/starter-kit)
