Working with APIs: REST vs GraphQL in Practice
APIs are the backbone of modern web applications. I've worked with both REST and GraphQL APIs extensively, and each has its strengths and weaknesses. Understanding when to use which approach can make a big difference in your application's performance and developer experience.
My journey with APIs
When I first started building web applications, REST APIs were the standard. You had endpoints for different resources, and you made multiple requests to get the data you needed. It worked, but it wasn't always efficient.
Then I discovered GraphQL, and it changed how I thought about APIs. Being able to specify exactly what data I needed in a single request was revolutionary. But I've learned that neither approach is universally better—they each have their place.
REST API basics
REST (Representational State Transfer) is an architectural style for designing APIs. It uses HTTP methods and follows resource-based URLs:
// REST API examples GET /api/users // Get all users GET /api/users/123 // Get user with ID 123 POST /api/users // Create a new user PUT /api/users/123 // Update user 123 DELETE /api/users/123 // Delete user 123 GET /api/users/123/posts // Get posts for user 123
Over-fetching and under-fetching problems
One of the biggest issues with REST APIs is data fetching efficiency:
- Over-fetching: Getting more data than you need
- Under-fetching: Not getting enough data, requiring additional requests
For example, if you want to display a list of users with their basic information, a REST endpoint might return:
{
"users": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"avatar": "https://...",
"bio": "Long biography text...",
"createdAt": "2023-01-01T00:00:00Z",
"lastLogin": "2023-12-01T00:00:00Z",
"preferences": { ... },
"stats": { ... }
}
]
}But if you're only showing name and avatar in a list, you're over-fetching a lot of data.
GraphQL to the rescue
GraphQL solves these problems by letting the client specify exactly what data it needs:
query GetUsers {
users {
id
name
avatar
}
}The server responds with exactly that data:
{
"data": {
"users": [
{
"id": 1,
"name": "John Doe",
"avatar": "https://..."
}
]
}
}Implementing GraphQL
Setting up a GraphQL API with Apollo Server:
// server.js
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
avatar: String
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
users: [User!]!
user(id: ID!): User
}
`;
const resolvers = {
Query: {
users: async () => {
// Fetch users from database
return await User.findAll();
},
user: async (_, { id }) => {
return await User.findById(id);
},
},
User: {
posts: async (user) => {
return await Post.findByAuthor(user.id);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});Schema definition
GraphQL schemas define the structure of your data:
type User {
id: ID!
name: String!
email: String!
avatar: String
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
publishedAt: DateTime!
tags: [String!]!
}
type Query {
users(limit: Int, offset: Int): [User!]!
user(id: ID!): User
postsByTag(tag: String!): [Post!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
createPost(input: CreatePostInput!): Post!
}Client-side GraphQL with Apollo Client
Using Apollo Client in React:
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
});
const GET_USERS = gql`
query GetUsers {
users {
id
name
avatar
posts {
id
title
}
}
}
`;
function UsersList() {
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}>
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.posts.length} posts</p>
</div>
))}
</div>
);
}Performance considerations
REST performance:
- Multiple endpoints can be cached independently
- Simpler server-side logic
- Predictable request patterns
- Easier to optimize with HTTP caching
GraphQL performance challenges:
- N+1 query problem
- Complex query parsing
- Cache invalidation difficulties
- Potential for expensive queries
Solving GraphQL performance issues:
DataLoader for batching:
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await User.findByIds(userIds);
return userIds.map(id => users.find(user => user.id === id));
});
const resolvers = {
Query: {
users: async () => await User.findAll(),
},
User: {
posts: async (user, _, { loaders }) => {
return await loaders.postsByUserId.load(user.id);
},
},
};Query complexity limits:
const { ApolloServerPluginQueryComplexity } = require('apollo-server-core');
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginQueryComplexity({
maximumComplexity: 1000,
onComplete: (complexity) => {
console.log('Query complexity:', complexity);
},
}),
],
});REST vs GraphQL: When to use which
Choose REST when:
- You have simple, predictable data requirements
- You're building for mobile apps with limited bandwidth
- You want to leverage HTTP caching extensively
- Your API serves many different clients with different needs
- You prefer simplicity and don't want the complexity of schemas
Choose GraphQL when:
- You have complex data relationships
- Different clients need different data shapes
- You're building for web apps where bandwidth isn't a major concern
- You want to reduce over-fetching and under-fetching
- You're willing to invest in schema design and tooling
Hybrid approaches
Many teams use both approaches:
- REST for CRUD operations: Simple create, read, update, delete
- GraphQL for complex queries: When you need flexible data fetching
- REST for file uploads: GraphQL isn't great for multipart requests
- GraphQL for real-time data: With subscriptions
Migration strategies
If you're migrating from REST to GraphQL:
- Start with a subset: Don't try to migrate everything at once
- Use schema stitching: Combine REST and GraphQL APIs
- Maintain REST endpoints: Keep them for existing clients
- Gradual migration: Move clients to GraphQL over time
My experience
I've used both REST and GraphQL extensively. REST is simpler to get started with and works well for straightforward APIs. GraphQL shines when you have complex data requirements and want to optimize for performance and developer experience.
That said, GraphQL has a steeper learning curve and requires more careful planning. If you're building a simple API, start with REST. If you're building something more complex, or if you have multiple clients with different data needs, GraphQL is worth considering.
The choice isn't always binary. Many successful applications use both approaches, choosing the right tool for each job. Focus on what works best for your team and your users.
Related articles