Beginner’s Guide to GraphQL with Apollo: From Server to Client
A complete beginner guide explaining how GraphQL APIs work, how to build them with Apollo Server, connect them with Apollo Client

Engineer @Ciena | Software Engineering | Full Stack Development | Typescript , Java, React Node | DSA LeetCode (600+) | System Design |
Modern applications aren’t simple anymore — they have dashboards, analytics, notifications, and nested data everywhere. REST APIs start feeling clunky as your frontend grows more dynamic. That’s where GraphQL shines — a modern query language that gives the client exactly what it needs, in one request.
What Is GraphQL?
GraphQL is a query language for APIs and a runtime to execute those queries. You can think of it as a flexible and intelligent middleman between your frontend and backend.
Why do we even need GraphQL when REST already works?
The Problem with REST APIs
Traditional REST APIs organise data around endpoints. For example, a blog API might look like this:
// Each endpoint returns a fixed data structure, often too much or too little information.
GET /users/1
GET /users/1/posts
GET /posts/5/comments
This leads to two big pain points:
Overfetching
You get more data than you need.
Eg: You just need a user’s name, but
/users/1returns their entire profile, preferences, posts, and settings. Wasted bandwidth. Slower load times.Underfetching
You get less data than you need — forcing multiple API calls.
Eg: To display a user’s name and their latest posts, you might have to call both
/users/1and/users/1/posts. More round trips. More complexity.
How GraphQL Fixes This
GraphQL flips the model:
You don’t hit multiple endpoints.
You hit one single endpoint =>
/graphql.You describe exactly what data you need.
Example:
query {
user(id: 1) {
name
posts {
title
comments {
text
}
}
}
}
This query says:
“Give me the user’s name, their posts, and each post’s comments, but only these fields.”
Server response:
{
"data": {
"user": {
"name": "Ram",
"posts": [
{
"title": "Learning GraphQL",
"comments": [{ "text": "Nice article!" }]
}
]
}
}
}
No overfetching
No underfetching
No multiple requests
GraphQL vs REST
| Concept | REST | GraphQL |
| Endpoint | Multiple (/users, /posts, /comments) | Single (/graphql) |
| Data Fetching | Fixed structure | Client decides structure |
| Overfetching | Common | Avoided |
| Underfetching | Common | Avoided |
| Real-Time | Needs extra setup | Built-in via Subscriptions |
How GraphQL Works Internally
GraphQL works on three core concepts:
Schema - Defines what data and operations exist.
Resolver - Functions that tell how to fetch that data.
Query / Mutation / Subscription - The operations clients perform.
The Schema: Defining Your Graph
The schema defines what’s possible - what you can ask for and what you’ll get back.
This gives GraphQL a strong type system, meaning:
You know every field’s data type.
You can validate queries before running them.
You can use auto-complete and documentation tools easily.
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
}
Example Flow:
Flow Summary:
Client Query → Schema Validation → Resolver Function → Data Fetch → Formatted Response

Step 1: Client sends a query:
query {
todos {
id
title
completed
}
}
Step 2: GraphQL server matches it to a resolver function:
const resolvers = {
Query: {
todos: () => getAllTodosFromDB()
}
}
Step 3: Resolver fetches data and returns it.
Step 4: GraphQL formats the result according to the schema and sends it back.
The Apollo Server — A Popular GraphQL Implementation
There are many GraphQL server libraries, but Apollo Server is by far the most popular.
It provides:
A ready-to-use HTTP layer for
/graphqlSchema definition tools
Middleware integrations
Developer tools like GraphQL Playground
Common Apollo Functions & Concepts:
| Function / Concept | Description |
ApolloServer | The main GraphQL server instance |
typeDefs | The schema definition (string or SDL) |
resolvers | Object mapping queries/mutations to functions |
server.start() | Starts the Apollo Server |
expressMiddleware(server) | Connects Apollo with Express |
context | Shared object across resolvers (for auth, db, etc.) |
GraphQL Architecture Overview
GraphQL has three main layers:
Schema Definition
Defines the shape of your data and the operations available.Resolvers
Actual functions that run to fetch the data from your database or APIs.Client Query
The query that requests specific data fields.
Schema Example
const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`;
Why:
The schema tells GraphQL what the data looks like — like a contract between client and server.
Resolver Example
const resolvers = {
Query: {
users: async () => await UserModel.find(),
user: async (_, { id }) => await UserModel.findById(id),
},
Mutation: {
createUser: async (_, { name, email }) => {
const user = new UserModel({ name, email });
return user.save();
},
},
User: {
posts: async (parent) => await PostModel.find({ userId: parent.id }),
},
};
Why:
Resolvers are functions that tell GraphQL how to fetch the data for each field.
Setting up the GraphQL Server
Let’s use Apollo Server (v4) with Express.js.
Install dependencies
npm install @apollo/server graphql express body-parser cors
Server Setup
import express from "express";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import bodyParser from "body-parser";
import cors from "cors";
const app = express();
app.use(cors(), bodyParser.json());
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
app.use("/graphql", expressMiddleware(server));
app.listen(4000, () => console.log("Server running on http://localhost:4000/graphql"));
Why:
ApolloServerruns your schema and resolvers./graphqlacts as the single endpoint for all queries and mutations.You can test your queries in Apollo Sandbox or GraphQL Playground.
Client Setup with Apollo Client
Let’s now connect the frontend to this server.
Install dependencies
npm install @apollo/client graphql
Apollo Client Setup
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const client = new ApolloClient({
link: new HttpLink({
uri: "http://localhost:4000/graphql",
}),
cache: new InMemoryCache(),
});
export default client;
What’s happening here:
| Component | Purpose |
ApolloClient | The main class that connects to the GraphQL server |
HttpLink | Defines how queries/mutations are sent over HTTP |
InMemoryCache | Stores query results locally for instant re-use and caching |
Example Queries & Mutations (Client Side)
Query Example
import { gql, useQuery } from "@apollo/client";
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
export default function UserList() {
const { data, loading, error } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.users.map((u) => (
<li key={u.id}>{u.name} — {u.email}</li>
))}
</ul>
);
}
How this works:
React component loads → executes the
useQueryhook.Apollo Clientsends the query to/graphql.Apollo Servermatches it with the resolver → fetches DB data.Response is cached and returned to the client.
Mutation Example
import { gql, useMutation } from "@apollo/client";
const CREATE_USER = gql`
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
export default function AddUser() {
const [createUser, { data, loading }] = useMutation(CREATE_USER);
const handleAdd = () => {
createUser({ variables: { name: "John Doe", email: "john@example.com" } });
};
return (
<div>
<button onClick={handleAdd}>Add User</button>
{loading && <p>Creating...</p>}
{data && <p>Created user: {data.createUser.name}</p>}
</div>
);
}
How this works:
When button clicked → mutation executes.
Apollo Client sends mutation request.
Server runs resolver logic, updates DB, returns new user.
Apollo caches response automatically.



