Skip to main content

Command Palette

Search for a command to run...

GraphQL vs REST: When to Use Each (A Pragmatic Guide)

Published
8 min read
E

Backend Developer | Golang & Python I enjoy building reliable APIs, distributed systems, and automation tools. Writing here about backend engineering, system design, and real-world dev experiences.

Introduction

I've built APIs both ways. REST APIs that worked beautifully. GraphQL APIs that were a nightmare. And vice versa.

The "GraphQL vs REST" debate is exhausting. Everyone has strong opinions. GraphQL fanboys say REST is dead. REST purists say GraphQL is overengineered.

Here's the truth: both have their place. I've shipped production systems with both. Some decisions were great. Some I regret.

In this post, I'll show you when to use each, based on real experience—not hype.

The Real Difference

REST in 30 Seconds

Multiple endpoints, each returns fixed data structure:

GET /users/123          → User data
GET /users/123/posts    → User's posts
GET /posts/456          → Specific post
GET /posts/456/comments → Post's comments

You get what the server decides to send. Need more data? Make another request.

GraphQL in 30 Seconds

One endpoint, you specify exactly what you want:

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

You get exactly what you asked for. Nothing more, nothing less.

When REST Wins

1. Simple CRUD Operations

If you're building a basic API with standard create/read/update/delete:

POST   /users      → Create user
GET    /users/123  → Get user
PUT    /users/123  → Update user
DELETE /users/123  → Delete user

REST is perfect. It's simple, standardized, everyone understands it.

Don't overthink it. If your API fits REST naturally, use REST.

2. File Uploads/Downloads

REST handles binary data naturally:

POST /users/123/avatar
Content-Type: multipart/form-data

GraphQL with file uploads? Possible, but awkward. You'll add libraries, workarounds, complexity.

Use REST for files. Save yourself the headache.

3. Caching is Critical

HTTP caching just works with REST:

GET /users/123
Cache-Control: max-age=3600
ETag: "abc123"

CDNs, browsers, proxies—everything caches GET requests automatically.

GraphQL? Every request is a POST to the same endpoint. Standard HTTP caching doesn't work.

Yes, you can implement caching in GraphQL. But it's extra work.

If caching matters, REST is easier.

4. Public APIs

Building an API for third-party developers?

REST wins because:

  • Simpler to understand

  • Works with standard HTTP tools (curl, Postman)

  • Better documentation tools (Swagger/OpenAPI)

  • Developers already know it

GraphQL has a learning curve. Not everyone wants to learn your schema.

5. Microservices Internal Communication

Service-to-service calls in a microservices architecture?

REST or gRPC. Not GraphQL.

Why?

  • Simple request-response

  • No need for flexible queries

  • Performance matters (gRPC is faster)

  • You control both sides of the API

When GraphQL Wins

1. Mobile Apps with Limited Bandwidth

Mobile apps need different data at different times. With REST:

GET /users/123           → Full user (waste bandwidth)
GET /users/123/posts     → All posts (only need 5)
GET /posts/X/comments    → Repeat for each post

Result: Multiple round trips, over-fetching, under-fetching.

With GraphQL:

query {
  user(id: 123) {
    name
    avatar
    posts(limit: 5) {
      title
      excerpt
    }
  }
}

One request. Exactly what you need. Huge win for mobile.

Mobile app with complex data needs? GraphQL.

2. Rapidly Evolving Frontend

Your frontend team needs different data every sprint. With REST:

// Week 1: Need user name
GET /users/123 → { name, email, created_at, ... }

// Week 2: Need user + posts
GET /users/123
GET /users/123/posts

// Week 3: Need user + posts + follower count
Backend: *creates new endpoint*
GET /users/123/profile

You keep adding endpoints. API becomes a mess.

With GraphQL:

// Week 1
query { user(id: 123) { name } }

// Week 2
query { user(id: 123) { name, posts { title } } }

// Week 3
query { user(id: 123) { name, posts { title }, followerCount } }

Frontend evolves independently. Backend adds fields, doesn't break existing queries.

Frontend changes fast? GraphQL gives flexibility.

3. Complex, Nested Data Requirements

Your UI shows deeply nested data:

User
├─ Posts
│  ├─ Comments
│  │  └─ Author
│  └─ Likes
└─ Followers
   └─ Posts

With REST, you're making 10+ requests or creating bloated endpoints.

With GraphQL, one query gets it all:

query {
  user(id: 123) {
    posts {
      comments { author { name } }
      likes { count }
    }
    followers { posts { title } }
  }
}

Complex nested data? GraphQL shines.

4. Multiple Client Types

You have:

  • Web app (needs full data)

  • Mobile app (needs minimal data)

  • Smartwatch app (needs even less)

With REST:

  • Create separate endpoints for each? Messy.

  • Return everything, let clients filter? Wasteful.

With GraphQL: Each client queries exactly what it needs. One API serves all.

Multiple client types with different needs? GraphQL.

5. Developer Productivity (with Good Tooling)

GraphQL tooling is incredible:

  • GraphiQL/Playground: Interactive API explorer

  • Schema introspection: Self-documenting API

  • Type safety: Auto-generated types for TypeScript/Go

  • Real-time updates: Built-in subscription support

This boosts developer productivity—if you invest in the tooling.

The Ugly Truth About GraphQL

GraphQL isn't all sunshine. Here's what people don't tell you:

Problem 1: N+1 Query Problem

query {
  users {
    posts {
      author { name }
    }
  }
}

Naive implementation:

  1. Fetch all users (1 query)

  2. For each user, fetch posts (N queries)

  3. For each post, fetch author (N² queries)

Your database dies.

Solution: DataLoader pattern (batching). But it's extra code.

Problem 2: Complex Authorization

REST:

func GetUser(w http.ResponseWriter, r *http.Request) {
    if !canAccessUser(userID) {
        return http.StatusForbidden
    }
    // Return user
}

GraphQL: Authorization happens at the field level. Users can query anything. You need field-level permission checks.

query {
  user(id: 123) {
    name          # Public
    email         # Owner only
    socialSecurity # Super admin only
  }
}

Every field needs auth logic. It's complex.

Problem 3: Rate Limiting Hell

REST: Easy to rate limit by endpoint.

GraphQL: Every request is POST /graphql. How do you rate limit?

# Simple query
query { user(id: 123) { name } }

# Expensive query
query {
  users {
    posts {
      comments {
        author { posts { comments { ... } } }
      }
    }
  }
}

Both hit the same endpoint. How do you limit the expensive one?

You need query complexity analysis. More code.

Problem 4: Caching Complexity

REST caching: Built into HTTP.

GraphQL caching: You implement it yourself.

Options:

  • Persisted queries (complex setup)

  • Apollo Client cache (learning curve)

  • Custom Redis caching (more code)

It's doable, but not free.

My Decision Framework

Here's how I decide:

Use REST When:

✓ Simple CRUD operations ✓ Public API for third parties ✓ File uploads/downloads ✓ HTTP caching is critical ✓ Team is small or new to GraphQL ✓ Service-to-service communication ✓ You need it done fast

Use GraphQL When:

✓ Mobile app with bandwidth concerns ✓ Complex, nested data requirements ✓ Multiple clients with different data needs ✓ Rapid frontend iteration ✓ Team has GraphQL experience ✓ You can invest in proper tooling ✓ Developer experience is a priority

Red Flags for GraphQL:

🚩 Team has never used it 🚩 Simple API (under 10 endpoints) 🚩 No time for proper setup 🚩 Performance is critical (GraphQL adds overhead) 🚩 Public API for unknown clients

Hybrid Approach (What I Actually Do)

Controversial take: You can use both.

Real example from my system:

REST for:

  • Authentication: POST /auth/login

  • File uploads: POST /users/avatar

  • Health checks: GET /health

  • Webhooks: POST /webhooks/stripe

GraphQL for:

  • Dashboard data queries

  • Complex reporting

  • Mobile app data fetching

The endpoints live side by side. It works.

Don't be dogmatic. Use the right tool for each job.

Performance Reality Check

GraphQL isn't automatically slower or faster than REST. It depends on implementation.

Bad GraphQL: N+1 queries, no caching, no batching → Slow

Good GraphQL: DataLoader, query optimization, caching → Fast

Bad REST: 20 endpoints for one page, over-fetching → Slow

Good REST: Optimized queries, proper caching → Fast

Performance is about implementation, not technology choice.

Migration Strategy

REST → GraphQL

Don't rewrite everything. Incrementally add GraphQL:

  1. Add GraphQL endpoint alongside REST

  2. Implement new features in GraphQL

  3. Gradually migrate high-traffic endpoints

  4. Keep REST for simple operations

GraphQL → REST

Yes, this happens. I've seen it.

When?

  • Team couldn't manage complexity

  • Performance issues couldn't be solved

  • Caching requirements were critical

No shame in switching back if it's not working.

Tools & Libraries

GraphQL in Go:

  • gqlgen: Code generation from schema (my choice)

  • graphql-go: Reflection-based

  • 99designs/gqlgen: Production-ready, type-safe

REST in Go:

  • gorilla/mux: Full-featured router

  • chi: Lightweight, composable

  • gin: Fast, batteries-included

  • echo: High performance

Both ecosystems are mature. Tooling isn't the deciding factor.

Common Mistakes

Mistake 1: GraphQL for Everything

"We're modern developers, we use GraphQL!"

Then you implement file uploads in GraphQL. And webhooks. And server-sent events.

Stop. Use REST for these. GraphQL isn't a religion.

Mistake 2: Multiple REST Endpoints for Same Data

GET /users/123/profile
GET /users/123/dashboard
GET /users/123/settings
GET /users/123/full

Different endpoints returning different subsets of user data.

This screams for GraphQL. Let clients specify what they need.

Mistake 3: No DataLoader in GraphQL

You implement GraphQL without solving N+1 queries.

Your database gets hammered. You blame GraphQL.

Not GraphQL's fault. Implement DataLoader or batching.

Mistake 4: Over-Engineering REST

You add versioning (/v1/, /v2/), custom query languages, complex filtering...

You're reinventing GraphQL badly.

Just use GraphQL if you need that flexibility.

The Bottom Line

There is no winner.

REST isn't dead. GraphQL isn't a silver bullet.

  • Building a simple CRUD API? REST.

  • Building a complex dashboard? GraphQL.

  • Building Stripe's API? REST.

  • Building GitHub's API? GraphQL (they use both, actually).

Stop following trends. Make practical decisions.

Questions? Drop a comment!