Skip to main content

πŸ”„ REST API vs GraphQL

πŸ“– Definition​

REST API is a traditional API architecture designed around resources using HTTP methods. GraphQL is a query language developed by Facebook that provides a flexible API approach where clients can request exactly the data they need. REST has multiple endpoints, while GraphQL handles all data through a single endpoint.

🎯 Understanding Through Analogy​

Restaurant vs Buffet​

REST API = Restaurant Menu
β”œβ”€ Can only order from predefined menu
β”œβ”€ Each menu has fixed composition
β”œβ”€ "One pizza please" β†’ Get entire pizza
β”œβ”€ Hard to remove or add specific toppings
└─ Simple and predictable

GraphQL = Buffet
β”œβ”€ Pick and choose only what you want
β”œβ”€ Select only what you need
β”œβ”€ "Just cheese please" β†’ Get only cheese
β”œβ”€ Freely combine items
└─ Flexible but can be complex

Library Book Lending​

REST API = Traditional Lending
Librarian: "Which book do you need?"
Me: "Computer books please"
Librarian: "Here are all the books" (10 books)
Me: "I only need chapter 1..." (rest is unnecessary)

GraphQL = Smart Lending
Me: "I only need chapter 3 of the computer book"
Librarian: "Here's just chapter 3" (exactly what's needed)
Me: "Perfect!"

βš™οΈ How It Works​

1. Data Request Method Comparison​

REST API: Multiple Endpoints
GET /users/1 β†’ User information
GET /users/1/posts β†’ User's posts
GET /posts/1/comments β†’ Post's comments

Total 3 requests needed!

GraphQL: Single Endpoint
POST /graphql
{
user(id: 1) {
name
posts {
title
comments {
text
}
}
}
}

All data in 1 request!

2. Over-fetching vs Under-fetching​

REST API Problems

Over-fetching (receiving unnecessary data)
GET /users/1
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"phone": "123-456-7890",
"address": "123 Main St...",
"createdAt": "2024-01-01",
// Receive all information when only name is needed!
}

Under-fetching (insufficient data)
GET /users/1 β†’ User information
GET /users/1/posts β†’ Need additional request
GET /users/1/friends β†’ Need another request
// Multiple requests required!

GraphQL Solution

Only what's needed
{
user(id: 1) {
name // Request only name!
}
}
β†’ { "name": "John Doe" }

Everything at once
{
user(id: 1) {
name
posts { title }
friends { name }
}
}
β†’ All data in 1 request!

3. API Design Philosophy​

REST: Resource-Centric
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ /users β”‚ β†’ List of users
β”‚ /users/1 β”‚ β†’ Specific user
β”‚ /posts β”‚ β†’ List of posts
β”‚ /posts/1 β”‚ β†’ Specific post
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Endpoint for each resource

GraphQL: Query-Centric
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ /graphql β”‚ β†’ All requests
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Query β”‚ β†’ Read data
β”‚Mutationβ”‚ β†’ Modify data
β”‚Subscribeβ”‚ β†’ Real-time subscription
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘ Real-World Examples​

REST API Example (Express.js)​

// Express.js REST API Implementation
const express = require('express');
const app = express();

app.use(express.json());

// Data (database would be used in practice)
const users = [
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
age: 25
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
age: 30
}
];

const posts = [
{
id: 1,
userId: 1,
title: 'Introduction to REST API',
content: 'REST is...'
},
{
id: 2,
userId: 1,
title: 'Introduction to GraphQL',
content: 'GraphQL is...'
}
];

// ========== GET: Retrieve all users ==========
app.get('/api/users', (req, res) => {
res.json(users);
});

// ========== GET: Retrieve specific user ==========
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});

// ========== POST: Create user ==========
app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name,
email: req.body.email,
age: req.body.age
};
users.push(newUser);
res.status(201).json(newUser);
});

// ========== PUT: Update user ==========
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}

user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
user.age = req.body.age || user.age;

res.json(user);
});

// ========== DELETE: Delete user ==========
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'User not found' });
}

users.splice(index, 1);
res.status(204).send();
});

// ========== GET: Retrieve user's posts ==========
app.get('/api/users/:id/posts', (req, res) => {
const userId = parseInt(req.params.id);
const userPosts = posts.filter(p => p.userId === userId);
res.json(userPosts);
});

// ========== GET: Retrieve specific post ==========
app.get('/api/posts/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id));
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
res.json(post);
});

app.listen(3000, () => {
console.log('REST API server running: http://localhost:3000');
});

REST API Client Usage​

// ========== REST API Usage ==========

// 1. Retrieve user list
const response1 = await fetch('http://localhost:3000/api/users');
const users = await response1.json();
console.log(users);

// 2. Retrieve specific user
const response2 = await fetch('http://localhost:3000/api/users/1');
const user = await response2.json();
console.log(user);

// 3. Create user
const response3 = await fetch('http://localhost:3000/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Mike Park',
email: 'mike@example.com',
age: 28
})
});
const newUser = await response3.json();

// 4. Update user
const response4 = await fetch('http://localhost:3000/api/users/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
age: 26
})
});

// 5. Delete user
await fetch('http://localhost:3000/api/users/1', {
method: 'DELETE'
});

// ========== Problem: Multiple requests needed ==========
// How to get user and posts together?
const userResponse = await fetch('http://localhost:3000/api/users/1');
const user = await userResponse.json();

const postsResponse = await fetch('http://localhost:3000/api/users/1/posts');
const posts = await postsResponse.json();

console.log({ user, posts }); // 2 requests!

GraphQL Example (Apollo Server)​

// GraphQL Server Implementation
const { ApolloServer, gql } = require('apollo-server');

// Data (database would be used in practice)
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', age: 25 },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 30 }
];

const posts = [
{ id: 1, userId: 1, title: 'Introduction to REST API', content: 'REST is...' },
{ id: 2, userId: 1, title: 'Introduction to GraphQL', content: 'GraphQL is...' },
{ id: 3, userId: 2, title: 'Node.js Tutorial', content: 'Node is...' }
];

// ========== Schema Definition (Type Definitions) ==========
const typeDefs = gql`
# User Type
type User {
id: Int!
name: String!
email: String!
age: Int
posts: [Post!]! # User's posts (relationship)
}

# Post Type
type Post {
id: Int!
title: String!
content: String!
author: User! # Post author (relationship)
}

# Query (Read data)
type Query {
# All users
users: [User!]!

# Specific user
user(id: Int!): User

# All posts
posts: [Post!]!

# Specific post
post(id: Int!): Post
}

# Mutation (Modify data)
type Mutation {
# Create user
createUser(name: String!, email: String!, age: Int): User!

# Update user
updateUser(id: Int!, name: String, email: String, age: Int): User

# Delete user
deleteUser(id: Int!): Boolean!

# Create post
createPost(userId: Int!, title: String!, content: String!): Post!
}
`;

// ========== Resolvers (How to fetch data) ==========
const resolvers = {
Query: {
// Retrieve all users
users: () => users,

// Retrieve specific user
user: (parent, args) => {
return users.find(u => u.id === args.id);
},

// Retrieve all posts
posts: () => posts,

// Retrieve specific post
post: (parent, args) => {
return posts.find(p => p.id === args.id);
}
},

Mutation: {
// Create user
createUser: (parent, args) => {
const newUser = {
id: users.length + 1,
name: args.name,
email: args.email,
age: args.age
};
users.push(newUser);
return newUser;
},

// Update user
updateUser: (parent, args) => {
const user = users.find(u => u.id === args.id);
if (!user) return null;

if (args.name) user.name = args.name;
if (args.email) user.email = args.email;
if (args.age) user.age = args.age;

return user;
},

// Delete user
deleteUser: (parent, args) => {
const index = users.findIndex(u => u.id === args.id);
if (index === -1) return false;

users.splice(index, 1);
return true;
},

// Create post
createPost: (parent, args) => {
const newPost = {
id: posts.length + 1,
userId: args.userId,
title: args.title,
content: args.content
};
posts.push(newPost);
return newPost;
}
},

// ========== Relationship Resolvers ==========
User: {
// Get user's posts
posts: (parent) => {
return posts.filter(p => p.userId === parent.id);
}
},

Post: {
// Get post's author
author: (parent) => {
return users.find(u => u.id === parent.userId);
}
}
};

// ========== Start Server ==========
const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
console.log(`GraphQL server running: ${url}`);
});

GraphQL Client Usage​

// ========== GraphQL Query Examples ==========

// 1. Query only name (Prevent Over-fetching)
query {
user(id: 1) {
name
}
}
// Response: { "data": { "user": { "name": "John Doe" } } }

// 2. Query user and posts together (Prevent Under-fetching)
query {
user(id: 1) {
name
email
posts {
title
content
}
}
}
// Response:
{
"data": {
"user": {
"name": "John Doe",
"email": "john@example.com",
"posts": [
{ "title": "Introduction to REST API", "content": "REST is..." },
{ "title": "Introduction to GraphQL", "content": "GraphQL is..." }
]
}
}
}

// 3. Query multiple resources simultaneously
query {
users {
name
}
posts {
title
}
}

// 4. Using variables
query GetUser($userId: Int!) {
user(id: $userId) {
name
email
}
}
// Variables: { "userId": 1 }

// ========== Mutation (Modify data) ==========

// 1. Create user
mutation {
createUser(name: "Mike Park", email: "mike@example.com", age: 28) {
id
name
email
}
}

// 2. Update user
mutation {
updateUser(id: 1, age: 26) {
id
name
age
}
}

// 3. Delete user
mutation {
deleteUser(id: 1)
}

// ========== JavaScript Client ==========
async function fetchGraphQL(query, variables = {}) {
const response = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
variables
})
});

return response.json();
}

// Query user and posts
const query = `
query GetUserWithPosts($userId: Int!) {
user(id: $userId) {
name
email
posts {
title
}
}
}
`;

const data = await fetchGraphQL(query, { userId: 1 });
console.log(data);

React Integration with Apollo Client​

// ========== Apollo Client Setup ==========
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, useMutation, gql } from '@apollo/client';

// Create client
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});

// Apply Provider to app
function App() {
return (
<ApolloProvider client={client}>
<UserList />
</ApolloProvider>
);
}

// ========== Using Queries ==========
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
posts {
title
}
}
}
`;

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

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

return (
<div>
{data.users.map(user => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
<ul>
{user.posts.map(post => (
<li key={post.title}>{post.title}</li>
))}
</ul>
</div>
))}
</div>
);
}

// ========== Using Mutations ==========
const CREATE_USER = gql`
mutation CreateUser($name: String!, $email: String!, $age: Int) {
createUser(name: $name, email: $email, age: $age) {
id
name
email
}
}
`;

function CreateUserForm() {
const [createUser, { data, loading, error }] = useMutation(CREATE_USER);

const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);

createUser({
variables: {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
}
});
};

if (loading) return <p>Creating...</p>;
if (error) return <p>Error: {error.message}</p>;
if (data) return <p>Created: {data.createUser.name}</p>;

return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<input name="age" type="number" placeholder="Age" />
<button type="submit">Create User</button>
</form>
);
}

// ========== Caching and Optimization ==========
const GET_USER = gql`
query GetUser($id: Int!) {
user(id: $id) {
id
name
email
}
}
`;

function UserProfile({ userId }) {
const { loading, error, data, refetch } = useQuery(GET_USER, {
variables: { id: userId },
// Caching policy
fetchPolicy: 'cache-first', // Cache first
// fetchPolicy: 'network-only', // Always request from server
// fetchPolicy: 'cache-and-network', // Show cache and update
});

// Manual refresh
const handleRefresh = () => {
refetch();
};

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

return (
<div>
<h2>{data.user.name}</h2>
<p>{data.user.email}</p>
<button onClick={handleRefresh}>Refresh</button>
</div>
);
}

πŸ€” Frequently Asked Questions​

Q1. Should I choose REST API or GraphQL?​

A: Choose based on your project characteristics:

βœ… Choose REST API when:
β”œβ”€ Simple CRUD application
β”œβ”€ Clear resource structure
β”œβ”€ Caching is important (utilize HTTP caching)
β”œβ”€ Team is familiar with REST
β”œβ”€ Heavy file upload/download
└─ Example: Blog, basic e-commerce features

βœ… Choose GraphQL when:
β”œβ”€ Complex data relationships
β”œβ”€ Mobile app (data saving important)
β”œβ”€ Diverse clients (web, app, tablet)
β”œβ”€ Want rapid frontend development
β”œβ”€ Need real-time data subscription
└─ Example: Social media, dashboard, real-time apps

🀝 Can use both together:
β”œβ”€ Main API uses GraphQL
β”œβ”€ File upload uses REST
└─ Leverage advantages of each

Q2. What's the performance difference?​

A:

// REST API Performance Characteristics
Pros:
- Can utilize HTTP caching
GET /api/users/1
Cache-Control: max-age=3600

- Easy CDN caching
- Simple endpoints are fast

Cons:
- Over-fetching transfers unnecessary data
GET /api/users/1
β†’ Returns all fields (100KB)

- Under-fetching requires multiple requests
GET /api/users/1 // 1st request
GET /api/users/1/posts // 2nd request
GET /api/posts/1/comments // 3rd request
// Total 3 requests!

// GraphQL Performance Characteristics
Pros:
- Only exactly needed data (10KB)
query {
user(id: 1) {
name
email
}
}

- All data in one request
query {
user(id: 1) {
name
posts {
title
comments { text }
}
}
}
// Done in 1 request!

Cons:
- HTTP caching difficult (uses POST requests)
- Complex queries burden server
- N+1 problem (solved with DataLoader)

Real Benchmark:
REST: 3 requests, 200KB total, 300ms
GraphQL: 1 request, 50KB total, 150ms
β†’ GraphQL advantageous in mobile environments

Q3. What is the N+1 problem in GraphQL?​

A: Can be solved with DataLoader:

// ========== N+1 Problem ==========
// Query 10 users and their posts

// Problem: Inefficient queries
const resolvers = {
Query: {
users: () => {
return db.users.findAll(); // 1 query
}
},
User: {
posts: (user) => {
return db.posts.findByUserId(user.id); // 1 query per user!
}
}
};

// Total queries: 1 + 10 = 11
// 1: Retrieve users
// 10: Retrieve posts for each user

// ========== Solved with DataLoader ==========
const DataLoader = require('dataloader');

// Batch retrieve posts
const postLoader = new DataLoader(async (userIds) => {
// Retrieve all posts at once
const posts = await db.posts.findByUserIds(userIds);

// Group by user
const postsByUser = {};
posts.forEach(post => {
if (!postsByUser[post.userId]) {
postsByUser[post.userId] = [];
}
postsByUser[post.userId].push(post);
});

// Return each user's posts
return userIds.map(id => postsByUser[id] || []);
});

const resolvers = {
User: {
posts: (user) => {
return postLoader.load(user.id); // Batch process!
}
}
};

// Total queries: 1 + 1 = 2
// 1: Retrieve users
// 1: Retrieve all posts (batch)
// β†’ Significant performance improvement!

// ========== Practical Example ==========
const { ApolloServer } = require('apollo-server');
const DataLoader = require('dataloader');

const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
// Create new DataLoader per request
loaders: {
postLoader: new DataLoader(batchGetPosts),
userLoader: new DataLoader(batchGetUsers)
}
})
});

// Use in resolver
const resolvers = {
User: {
posts: (user, args, context) => {
return context.loaders.postLoader.load(user.id);
}
}
};

Q4. REST API Versioning vs GraphQL Schema Evolution?​

A:

// ========== REST API Versioning ==========

// Method 1: Version in URL
GET /api/v1/users
GET /api/v2/users // New version

// Method 2: Version in header
GET /api/users
Accept: application/vnd.myapp.v1+json

// Problems:
// - Maintain multiple versions
// - Client update required
// - Old version support burden

// Example: Field name change
// v1
{
"name": "John Doe"
}

// v2
{
"fullName": "John Doe" // name β†’ fullName
}

// ========== GraphQL Schema Evolution ==========

// Easy to add
type User {
id: Int!
name: String!
email: String! # Existing
phone: String # Added (new field)
address: Address # Added (new type)
}

// Change: Use @deprecated
type User {
id: Int!
name: String! @deprecated(reason: "Use fullName instead")
fullName: String! # New field
email: String!
}

// Client updates gradually
query {
user(id: 1) {
name # Still works (deprecated)
fullName # Use new field
}
}

// Advantages:
// - Single endpoint
// - No version management needed
// - Gradual migration
// - Client updates at their convenience

Q5. How to handle real-time data?​

A:

// ========== REST API: Polling or SSE ==========

// 1. Polling
setInterval(async () => {
const response = await fetch('/api/messages');
const messages = await response.json();
updateUI(messages);
}, 3000); // Request every 3 seconds
// Cons: Inefficient, server burden

// 2. Server-Sent Events (SSE)
const eventSource = new EventSource('/api/messages/stream');
eventSource.onmessage = (event) => {
const message = JSON.parse(event.data);
updateUI(message);
};
// Cons: Only one-way communication

// ========== GraphQL: Subscription ==========

// Server setup
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const typeDefs = gql`
type Message {
id: Int!
text: String!
userId: Int!
createdAt: String!
}

type Subscription {
messageAdded: Message!
}

type Mutation {
addMessage(text: String!): Message!
}
`;

const resolvers = {
Mutation: {
addMessage: (parent, args) => {
const message = {
id: messages.length + 1,
text: args.text,
userId: 1,
createdAt: new Date().toISOString()
};
messages.push(message);

// Notify subscribers
pubsub.publish('MESSAGE_ADDED', {
messageAdded: message
});

return message;
}
},

Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED'])
}
}
};

// Client (React)
import { useSubscription, gql } from '@apollo/client';

const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageAdded {
messageAdded {
id
text
userId
createdAt
}
}
`;

function ChatRoom() {
const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION);

useEffect(() => {
if (data) {
console.log('New message:', data.messageAdded);
addMessageToUI(data.messageAdded);
}
}, [data]);

return <div>{/* UI */}</div>;
}

// Advantages:
// - Real-time bidirectional communication
// - WebSocket based
// - Efficient

πŸŽ“ Next Steps​

Now that you understand REST API and GraphQL, try learning:

  1. What is API? (Coming soon) - API basics
  2. JWT Token (Coming soon) - API authentication
  3. What is WebSocket? (Coming soon) - Real-time communication

Practice​

# ========== REST API Practice (Express) ==========
mkdir rest-api-demo
cd rest-api-demo
npm init -y
npm install express

# After writing server.js
node server.js

# Test
curl http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com"}'

# ========== GraphQL Practice (Apollo Server) ==========
mkdir graphql-demo
cd graphql-demo
npm init -y
npm install apollo-server graphql

# After writing server.js
node server.js

# Open GraphQL Playground
# http://localhost:4000
# Run queries in browser!

# ========== React + GraphQL ==========
npx create-react-app my-app
cd my-app
npm install @apollo/client graphql

# After Apollo Client setup
npm start

🎬 Conclusion​

REST API and GraphQL each have their pros and cons:

  • REST API: Simple, familiar, and easy caching
  • GraphQL: Flexible, efficient, and real-time support
  • Selection Criteria: Project complexity, team experience, performance requirements
  • Hybrid: Use both approaches as needed

Build great applications with proper API design! πŸ”„