Skip to content
S sufi.my
Back to Blog

Article

REST vs GraphQL: When to Use Each

A practical comparison of REST and GraphQL with real-world trade-offs.

February 28, 2026 · 11 min read

The Setup: Why This Matters

Your frontend needs data. Your backend has it. How do they talk?

For decades, the answer was REST. You’d request /users/123 and get a user object. Simple, standard, understood by everyone.

Then GraphQL showed up and promised to solve REST’s biggest problems. Now teams face a choice: REST or GraphQL? Or both?

The honest answer: both have trade-offs. Neither is universally better. This article walks through the real-world scenarios where each shines.

REST: The Tried and True

REST (Representational State Transfer) treats your API as resources. Each resource has a URL.

GET    /users/123           → Fetch user 123
POST   /users              → Create a new user
PUT    /users/123          → Update user 123
DELETE /users/123          → Delete user 123

Simple. Stateless. HTTP methods map to operations. This is what most APIs look like today.

REST in Practice

A typical REST endpoint:

// Request
GET /api/users/123

// Response
{
  "id": 123,
  "name": "Sufi Afifi",
  "email": "sufi@example.com",
  "avatar": "https://...",
  "role": "admin",
  "department": "engineering",
  "joinDate": "2022-03-15",
  "reportsTo": 456
}

You get everything about the user, whether your frontend needs it or not. Downloading a user ID to show on a profile? You also got their email, avatar, department, and more.

Multiple Resource Problem

What if you need a user AND their posts AND comments on those posts?

In REST, you need multiple requests:

// Request 1: Get the user
GET /api/users/123

// Request 2: Get their posts
GET /api/users/123/posts

// Request 3: For each post, get comments
GET /api/posts/1/comments
GET /api/posts/2/comments
GET /api/posts/3/comments

For a screen showing a user profile with 10 posts and comments, you might make 12 requests. This is called the N+1 problem.

GraphQL: Asking for Exactly What You Need

GraphQL flips the model. Instead of fixed endpoints returning fixed data, the client asks for exactly what it needs.

query {
  user(id: 123) {
    id
    name
    posts {
      id
      title
      comments {
        id
        text
      }
    }
  }
}

You get back:

{
  "data": {
    "user": {
      "id": 123,
      "name": "Sufi Afifi",
      "posts": [
        {
          "id": 1,
          "title": "GraphQL vs REST",
          "comments": [
            { "id": 10, "text": "Great post!" },
            { "id": 11, "text": "Thanks!" }
          ]
        }
      ]
    }
  }
}

One request. One response. Exactly what you asked for.

GraphQL Query Examples

Basic query:

{
  user(id: 123) {
    name
    email
  }
}

With arguments:

{
  posts(limit: 10, offset: 0) {
    id
    title
    author {
      name
    }
  }
}

With mutations (writes):

mutation {
  createPost(title: "Hello", content: "World") {
    id
    createdAt
  }
}

Fragments for reuse:

fragment UserInfo on User {
  id
  name
  email
}

{
  user1: user(id: 123) {
    ...UserInfo
  }
  user2: user(id: 456) {
    ...UserInfo
  }
}

REST vs GraphQL: Side-by-Side Comparison

Scenario 1: Mobile App Showing a User Card

REST:

GET /api/users/123
// Gets: id, name, email, avatar, role, department, joinDate, reportsTo
// Shows: name, avatar
// Waste: 6 unused fields

GraphQL:

{
  user(id: 123) {
    name
    avatar
  }
}

Winner: GraphQL (smaller payload, faster on slow connections)

Scenario 2: Dashboard with 5 Different Data Sources

REST:

GET /api/users/me
GET /api/posts
GET /api/comments
GET /api/notifications
GET /api/analytics
// 5 requests

GraphQL:

{
  me { ... }
  posts { ... }
  comments { ... }
  notifications { ... }
  analytics { ... }
}

Winner: GraphQL (one request, less latency)

Scenario 3: Public API with Caching Needs

REST:

GET /api/products/123
// Browser caches based on URL
GET /api/products/456
// Separate cache entries

GraphQL:

{
  products(ids: [123, 456]) { ... }
}

Winner: REST (HTTP caching built-in; GraphQL requests always go to server)

The N+1 Problem: The Main Pain Point REST Solves

This is REST’s biggest weakness.

Imagine your API returns post summaries:

GET /api/posts
[
  { id: 1, title: "Post 1", authorId: 10 },
  { id: 2, title: "Post 2", authorId: 20 },
  { id: 3, title: "Post 3", authorId: 10 }
]

Your frontend needs author names. In REST, you must fetch each author:

// 1 request for posts
// + 2 more requests for unique authors (N+1 problem)
GET /api/authors/10
GET /api/authors/20

// If you had 100 posts from 50 authors: 1 + 50 = 51 requests!

GraphQL avoids this entirely:

{
  posts {
    id
    title
    author {
      name
    }
  }
}

One request. The server resolves author data efficiently on its side.

Over-fetching and Under-fetching

Over-fetching (REST’s Problem)

You asked for a user; you got their entire profile including password hash (hopefully not!), internal IDs, deprecated fields, etc.

GET /api/users/123
{
  id, name, email, avatar, role, department, joinDate,
  reportsTo, salary, ssn, internalNotes, deprecated_field
  // You only needed: id, name
}

GraphQL fixes this — ask for only what you need.

Under-fetching (REST’s Other Problem)

You asked for a post; you also need comments. So you make another request.

GET /api/posts/1        // Post, but no comments
GET /api/posts/1/comments  // Now you have comments
// 2 requests for 1 logical entity

GraphQL fixes this — get related data in one query.

REST teams solve under-fetching with include parameters:

GET /api/posts/1?include=comments,author,tags

This works, but becomes unwieldy at scale and isn’t standardized.

Real-World Trade-offs

REST Advantages

  1. HTTP Caching Works

    GET /api/users/123
    // Browser caches automatically
    // CDN can cache
    // Browsers respect Cache-Control headers
    
  2. Simpler to Understand

    • Junior developers get it immediately
    • No learning curve
    • Debugging with curl is straightforward
  3. Standard Tools

    • Any HTTP tool works (curl, Postman, browser)
    • Logging and monitoring are standard
    • Firewalls and proxies understand it
  4. Better for Public APIs

    • Clients can implement their own caching
    • Rate limiting per endpoint is simple
    • Contract is explicit and stable
  5. Easier Versioning

    GET /api/v1/users/123
    GET /api/v2/users/123
    

REST Disadvantages

  1. N+1 Problem — Need multiple requests
  2. Over-fetching — Get unnecessary data
  3. Under-fetching — Missing data, need more requests
  4. API Proliferation — Create custom endpoints (e.g., /api/user-with-posts-and-comments)
  5. No Type Safety — Client doesn’t know response shape upfront

GraphQL Advantages

  1. Exact Data

    • Ask for what you need, get exactly that
    • Smaller payloads, faster on mobile
  2. Single Request

    • No N+1 problem
    • Less latency
    • Better user experience
  3. Type Safety

    # The schema is the contract
    type User {
      id: ID!
      name: String!
      email: String
    }
    
  4. Introspection

    • Clients can discover API shape at runtime
    • IDE autocomplete works automatically
  5. No Versioning

    • Add new fields without breaking clients
    • Deprecate old fields gracefully

GraphQL Disadvantages

  1. Caching is Hard

    • Every GraphQL request is a POST
    • Browser caching doesn’t work by default
    • Must implement custom caching logic
  2. Complexity

    • Server is complex (resolvers, dataloaders)
    • Client needs GraphQL client library (Apollo, Urql)
    • Debugging is harder
  3. File Uploads

    • REST: multipart/form-data is standard
    • GraphQL: No standard yet (workarounds needed)
  4. Denial of Service Risk

    • Query can request deeply nested data
    • users { friends { friends { friends { ... } } } }
    • Need query depth limits and timeouts
  5. Overkill for Simple Endpoints

    • If your API is just a few simple resources, GraphQL adds complexity with no benefit

When to Use REST

Use REST when:

  • You have a public API (caching matters)
  • Your data access is simple (fixed endpoints, few relationships)
  • Cache efficiency is critical (mobile, slow networks)
  • Your team doesn’t know GraphQL
  • You’re building a microservice (simple, focused)
  • SEO matters (REST endpoints can be bookmarked, cached)

Examples: Twitter API (simplified), Stripe API, AWS APIs, most public APIs.

When to Use GraphQL

Use GraphQL when:

  • You have a diverse client base (mobile, web, dashboard, internal tools)
  • N+1 problem is severe (complex data relationships)
  • You want to avoid API versioning
  • Your backend is a GraphQL-native database (Firebase Realtime, Hasura)
  • Your team knows GraphQL and wants to use it
  • You’re building an internal/private API

Examples: GitHub API, Shopify API, most modern startups.

Hybrid Approaches: Best of Both Worlds

Approach 1: REST + Caching

Use REST with aggressive caching:

GET /api/users/123
Cache-Control: max-age=3600

// Browser caches for 1 hour
// No server hit for repeat requests

Add query parameters for flexibility:

GET /api/users/123?fields=id,name,avatar

Approach 2: GraphQL + HTTP Caching

Cache GraphQL responses by query hash:

POST /graphql?query_hash=abc123
Cache-Control: max-age=300

// URL includes query hash
// Browser/CDN can cache
// Requires fixed queries (no variables)

Approach 3: REST + GraphQL Together

Expose both. Let clients choose.

REST:  GET /api/users/123
GraphQL: POST /graphql
         query { user(id: 123) { ... } }

Some endpoints REST, some GraphQL. Common in transitioning companies.

Approach 4: Backend For Frontend (BFF)

Create a dedicated endpoint for each client:

Mobile   → GET /api/mobile/user     (minimal fields, fast)
Web      → GET /api/web/user        (rich data)
Dashboard → GET /api/dashboard/user  (aggregated data)

Or use one GraphQL endpoint that different clients query differently.

Migration Strategy: REST to GraphQL

If you want to move from REST to GraphQL, do it incrementally:

Phase 1: Parallel Endpoints

REST API running
GraphQL API running
Both backed by same database
Clients can call either

Phase 2: Migrate Clients

Desktop web → GraphQL
Mobile → GraphQL (smaller payloads)
Public API stays REST (caching, stability)

Phase 3: Deprecate REST

REST API deprecated after 6 months
All internal clients on GraphQL
Public API might stay REST forever

A real example:

// Old REST endpoint
GET /api/users/123/with-posts-and-comments

// New GraphQL endpoint
query {
  user(id: 123) {
    posts {
      comments
    }
  }
}

// Old endpoint becomes GraphQL wrapper
GET /api/users/123/with-posts-and-comments
  → GraphQL query
  → same data structure
  → safe migration

Performance Comparison

Scenario: Fetch user + 10 posts + comments per post

REST (N+1 worst case):

Request 1: /api/users/123         → 2 KB
Request 2-11: /api/posts?user=123 → 20 KB total (2 KB per post)
Request 12-X: /api/comments?post=1..10 → 50 KB total
Total: 72 KB, 12 requests, 300ms latency

GraphQL (single request):

Request 1: /graphql (complex query) → 45 KB (only requested fields)
Total: 45 KB, 1 request, 150ms latency

But consider:

REST with Caching:

Request 1: /api/users/123 (CACHED) → 0 KB
Request 2: /api/posts (CACHED)     → 0 KB
Request 3+: /api/comments (fresh)  → 50 KB
Total: 50 KB, 1 network request, 50ms latency

Caching is powerful. Don’t underestimate it.

Common Mistakes

REST Mistakes

  1. Inventing custom query syntax

    // Bad
    GET /api/users?filters=role:admin,dept:eng
    

    Use standards (JSON:API, OData, etc.) or GraphQL.

  2. Ignoring caching

    // Bad: Eternal cache, stale data
    Cache-Control: max-age=31536000
    
    // Good: Validate with ETag
    Cache-Control: max-age=3600, must-revalidate
    ETag: "abc123"
    
  3. N+1 by default

    // Bad: Returns 100 posts without authors
    GET /api/posts
    
    // Good: Offer ?include parameter
    GET /api/posts?include=author
    

GraphQL Mistakes

  1. No Query Depth Limit

    # Attacker sends deeply nested query
    {
      user {
        friends { friends { friends { ... } } }
      }
    }
    # Server exhausted
    

    Always add limits:

    const depth = getQueryDepth(query);
    if (depth > 10) throw new Error("Query too deep");
    
  2. No Dataloader

    // Bad: N+1 on the server side
    posts.map(post => fetchAuthor(post.authorId)) // 100 DB queries
    
    // Good: Batch-load authors
    const authorLoader = new DataLoader(authorIds =>
      db.authors.find({ id: { $in: authorIds } })
    );
    
  3. Ignoring Performance

    // Bad: Full-text search in resolver
    resolve: (post) => {
      return db.comments.find({ text: { $regex: query } })
    }
    
    // Good: Use indexed queries
    resolve: (post, args) => {
      return db.comments.find({ postId: post.id, ...args })
    }
    

Decision Matrix

Choose based on your scenario:

FactorRESTGraphQL
Data relationshipsSimpleComplex
Caching importanceHighLow
Team familiarityHighLow
API stabilityFrequent changesInfrequent
Real-time featuresWebsocketsSubscriptions
File uploadsNativeWorkaround
Public APIGoodHarder

Summary

REST is a solid, proven choice. Use it when you want simplicity, caching, and standard HTTP semantics.

GraphQL shines when you have complex data relationships and want clients to request exactly what they need.

Reality: Most successful APIs use both. Twitter uses REST. GitHub uses both. Your startup can too.

The key is understanding the trade-offs:

  • REST gains caching; GraphQL gains flexibility
  • REST is simple; GraphQL is powerful
  • REST is proven; GraphQL is modern

Choose based on your team’s skills, your data complexity, and what your clients need. And remember: you can always add GraphQL as a layer on top of REST if you change your mind.