# REST vs GraphQL: Choosing the Right API Design

> REST or GraphQL? Compare performance, flexibility, tooling, and when each wins. Real examples of REST endpoints vs GraphQL queries for the same data model.

URL: https://www.matthewswong.com/en/blog/api-design-rest-graphql/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-01-22
- Updated: 2025-01-22
- Category: Software Development

REST API design and GraphQL are two dominant approaches to building APIs, and the choice between them shapes how your frontend and backend teams collaborate, how your clients fetch data, and how your API evolves over time. Both are mature, battle-tested approaches with real trade-offs. This post gives you concrete comparisons — same data model, both approaches — so you can make an informed choice for your next project.

## The Core Difference: Resources vs Queries

REST organizes your API around resources — nouns like /users, /posts, /orders — and uses HTTP verbs (GET, POST, PUT, DELETE) to express intent. GraphQL exposes a single endpoint and lets clients describe exactly the data they need using a query language. The fundamental trade-off is this: REST is simple to understand and cache at the HTTP layer, while GraphQL eliminates over-fetching and under-fetching by giving clients precise control over data shape.

### The N+1 and Over-fetching Problem in REST

REST's biggest challenge in rich UI applications is that a single screen often requires multiple resources. A blog post page needs the post, the author, the comments, and the tags — that's four round-trips. Each round-trip adds latency, especially on mobile. The response for each resource also includes every field the server knows about, even if the client only needs two of them. GraphQL was designed specifically to solve these problems.

> REST over-fetching can be partially solved with sparse fieldsets (?fields=id,title,author) and compound documents following the JSON:API specification. This doesn't eliminate all round-trips but significantly reduces payload size for REST-based mobile APIs.

## Building REST APIs in TypeScript

A well-designed REST API follows a consistent URL structure, uses appropriate HTTP status codes, returns meaningful error bodies, and versions thoughtfully. In Next.js App Router, each route handler maps cleanly to a resource endpoint. TypeScript ensures your response shapes are consistent, and Zod gives you runtime validation of request bodies.

### REST Conventions That Matter

Use plural nouns for collections (/posts, not /post). Use nested resources sparingly — /posts/42/comments is fine, but /users/7/posts/42/comments/5/likes is a smell. Return 201 Created with a Location header for POST requests. Return 204 No Content for successful DELETE. Use 422 Unprocessable Entity for validation errors with a structured error body. Consistent conventions reduce the cognitive load for API consumers.

### API Versioning Strategies

Versioning via URL path (/api/v1/posts) is simple and explicit, making it easy to run two versions simultaneously. Versioning via Accept header (Accept: application/vnd.api.v2+json) is more RESTful but harder to test in a browser. Versioning via query string (?version=2) is the least intrusive. For internal APIs between your own services, consider using additive-only evolution (never remove fields) instead of versioning at all.

## GraphQL: Flexibility and Its Trade-offs

GraphQL solves the over-fetching and under-fetching problems elegantly, but it introduces its own complexity. You need to think about authorization at the field level, not just the resource level. You need a DataLoader to batch database queries and prevent N+1 problems in your resolvers. Caching is more complex because every query is a POST to the same endpoint, making HTTP-level caching ineffective.

### The N+1 Problem in GraphQL Resolvers

When resolving a list of posts with their authors, a naive GraphQL resolver makes one database query per post to fetch the author — the classic N+1 problem. DataLoader solves this by collecting all author IDs during a single event-loop tick and issuing a single batched query. This is essential for any GraphQL API serving more than trivial data volumes.

> **GraphQL Is Not Automatically Faster**

> GraphQL's flexibility can lead to unbounded queries if you don't implement query depth limiting and complexity analysis. A malicious or careless client can craft a deeply nested query that triggers thousands of database calls. Libraries like graphql-depth-limit and graphql-query-complexity help, but you must actively configure them. A REST API with a fixed response shape has a naturally bounded performance profile.

### GraphQL Authorization at the Field Level

In REST, authorization is typically at the resource level — a middleware checks if the user can access /posts/42. In GraphQL, a single query can touch multiple resource types in one request, so authorization must happen at the resolver level for each field. Libraries like graphql-shield provide a declarative rules-based system for field-level authorization that's composable and testable.

## When to Choose REST vs GraphQL

Choose REST for public APIs where you want HTTP caching, broad tooling support, and a predictable access pattern. REST shines for resource-centric operations (CRUD), webhooks, and file uploads. Choose GraphQL when you have multiple clients (web, mobile, third-party) with different data needs, when your data is highly relational, or when your frontend teams need the flexibility to iterate on data requirements without backend changes.

### REST: When It's the Right Tool

REST is the right choice for: public APIs (better ecosystem for client generation and documentation with OpenAPI/Swagger), file uploads and binary data, simple CRUD services where a resource maps directly to a database table, when HTTP caching at CDN level is critical for performance, and when your consumers are third-party developers unfamiliar with GraphQL.

### GraphQL: When It Pays Off

GraphQL pays the most dividends when: you have a BFF (backend for frontend) pattern with multiple UI clients, your data model is a graph (social networks, knowledge graphs), your frontend teams need to iterate on data requirements without waiting for backend changes, you want a single typed schema as the contract between frontend and backend, or you're building a developer platform where flexibility and exploration are valued.

> The two aren't mutually exclusive. Many large-scale systems use REST for simple resource CRUD and file operations, while exposing a GraphQL endpoint for complex, relational data fetching by their primary UI clients.

## API Design Fundamentals That Apply to Both

Regardless of whether you choose REST or GraphQL, some principles apply universally: use consistent naming conventions, design for the client's needs (not the database schema), version thoughtfully, return meaningful errors with actionable messages, document exhaustively, and treat your API as a product that has consumers who depend on stability.

## Source links

- https://graphql.org/learn/
- https://restfulapi.net/
- https://www.apollographql.com/blog/graphql-vs-rest
- https://github.com/graphql/dataloader

## FAQ

### What is the fundamental difference between REST and GraphQL?

REST organizes your API around resources using HTTP verbs (GET, POST, PUT, DELETE) against multiple endpoints like /users or /posts. GraphQL exposes a single endpoint and lets clients describe exactly the data they need using a query language. The key trade-off is that REST is simpler to cache at the HTTP layer, while GraphQL eliminates over-fetching and under-fetching by giving clients precise control over data shape.

### How does GraphQL solve the N+1 and over-fetching problems that REST has?

In REST, a single screen may require multiple round-trips — for example, a blog post page needs the post, author, comments, and tags, which is four separate requests. GraphQL lets clients fetch all of that in a single query, eliminating excess round-trips and returning only the fields requested. However, GraphQL resolvers must use DataLoader to batch database queries and prevent their own N+1 problem when resolving related data like authors for a list of posts.

### Is GraphQL automatically faster than REST?

No — GraphQL's flexibility can lead to unbounded queries if you don't implement query depth limiting and complexity analysis. A malicious or careless client can craft a deeply nested query that triggers thousands of database calls. Libraries like graphql-depth-limit and graphql-query-complexity help guard against this, whereas a REST API with a fixed response shape has a naturally bounded performance profile.

### When should you choose REST over GraphQL, and vice versa?

Choose REST for public APIs where HTTP caching, broad tooling support (OpenAPI/Swagger), file uploads, and predictable access patterns matter, or when your consumers are third-party developers unfamiliar with GraphQL. Choose GraphQL when you have multiple clients (web, mobile, third-party) with different data needs, highly relational data, or when frontend teams need to iterate on data requirements without waiting for backend changes. The two are not mutually exclusive — many large systems use both.

### How does authorization work differently in REST versus GraphQL?

In REST, authorization is typically handled at the resource level — a middleware checks whether the user can access an endpoint like /posts/42. In GraphQL, a single query can touch multiple resource types in one request, so authorization must happen at the resolver level for each field. Libraries like graphql-shield provide a declarative, rules-based system for field-level authorization that is composable and testable.

---

- HTML version: https://www.matthewswong.com/en/blog/api-design-rest-graphql/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/api-design-rest-graphql/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
