# API Design: REST vs GraphQL and Beyond

> REST, GraphQL, gRPC: what each API style demands of client and server, and how to choose on your use case rather than on fashion.

- Date : 2025-09-18
- Lecture : 5 min
- Catégorie : devsecops
- Tags : API design, REST, GraphQL, gRPC, API contract, Architecture
- URL : https://www.adservio.fr/en/insights/articles/api-design-rest-vs-graphql-et-au-dela

## TL;DR

- REST isn't defined by JSON, /api/v1/-style URLs, or CRUD, but by a resource-oriented, stateless architecture that uses HTTP verbs and status codes correctly.
- GraphQL solves REST's over-fetching and under-fetching by letting clients describe exactly the fields they need, at the cost of higher caching and tooling complexity.
- The right choice depends on context: REST for simple CRUD with strong HTTP caching needs, GraphQL for multiple clients with heterogeneous data needs.
- A hybrid approach combining REST for simple operations and GraphQL for composite views is often the best answer.
- Cross-cutting best practices, consistent naming, pagination suited to data volume, and explicit rate limiting, matter as much as the REST-versus-GraphQL choice.

## Introduction

Your API is your contract with the world. It defines how your clients interact with your system. REST has dominated for 20 years, GraphQL promises to solve its limitations, but what's the right approach for your use case?

Spoiler: it's not "either/or".

## REST: the fundamentals (often misunderstood)

What REST actually means. REST isn't just using JSON over HTTP, prefixing your URLs with /api/v1/, or doing basic CRUD: those are common habits, not the definition of the architectural style.

REST is above all a resource-based architecture, with stateless communication, correct use of HTTP verbs, and, in its fullest form, HATEOAS (Hypermedia as the Engine of Application State). In practice, that means exposing resources identified by a URL, GET /api/v1/users/123, DELETE /api/v1/users/123,rather than actions disguised as endpoints like POST /api/getUserById or GET /api/users?action=delete&id=123, nesting related resources (GET /api/v1/users/123/orders), and exposing filtering, sorting, and pagination through query parameters (GET /api/v1/orders?status=pending&sort=-created_at&page=2&limit=20).

### Versioning

The question of versioning comes up quickly. Three approaches coexist: URL versioning (/api/v1/... then /api/v2/...), the most explicit and the most widely used in practice; header versioning (Accept-Version: v2); and content negotiation via a custom Accept header (application/vnd.company.v2+json). The first option remains the simplest to document and maintain over time.

### HTTP status codes

Choosing the right HTTP status code is also part of the contract. On the success side: 200 for a successful read or update, 201 for a creation (with a Location header pointing to the created resource), and 204 for a deletion with no content to return. On the client error side: 400 for a validation error, 401 when authentication is required, 403 when it's present but insufficient, 404 for a resource that doesn't exist, 409 for a conflict (duplicate, concurrent update), and 422 for a semantic error. On the server side, 500 signals an internal error and 503 a temporary unavailability (maintenance, overload).

## GraphQL: when and why

The problem GraphQL solves: over-fetching. In REST, an endpoint like GET /api/users/123 often returns the entire resource, name, email, phone, address, bio, preferences, settings, and around fifty fields in total, even when the client only needs the name and email. With GraphQL, the client formulates a query that describes exactly the fields it wants (query { user(id: "123") { name email } }) and receives only that.

The other problem is under-fetching: rebuilding a screen often requires several successive REST calls, for example fetching the user, then their posts, then their followers. GraphQL lets you bundle this need into a single nested query that retrieves the user, their posts and comments, and their followers, in one round trip.

### Schema, resolvers, and the N+1 problem

Technically, a GraphQL API relies on a schema that types the entities (User, Post, Comment) and the available operations, Query for reads, Mutation for writes, Subscription for real-time updates. Each field in the schema is then wired to a resolver, a function that fetches the corresponding data from a database or a service.

A classic pitfall of nested resolvers is the N+1 problem: resolving the posts of N users triggers one query per user on top of the initial query. The DataLoader pattern solves this by batching the requested identifiers within the same execution tick into a single grouped query of the form WHERE author_id IN (...), bringing the number of queries down from N+1 to 2.

## When to use what

Use REST if: Simple public API (CRUD); Strong need for HTTP caching; Team not familiar with GraphQL; Frequent file uploads; Simple relationships between entities

Use GraphQL if: Multiple clients with different needs (web, mobile, IoT); Complex data graph; Need for real-time (subscriptions); Experienced team; Fine-grained control over retrieved data

Hybrid approach: nothing stops you from combining both. REST remains well suited for simple, predictable CRUD (POST/GET/PUT/DELETE on /api/v1/users), while GraphQL takes over for composite views, like a dashboard that aggregates a user's profile, recent orders, notifications, and recommendations in a single query.

> Related read: [How to build an API platform team](https://www.adservio.fr/en/insights/articles/monter-une-equipe-api-platform): Without a dedicated API platform team, developers waste time integrating APIs. Roles, golden paths and governance: the complete method for 2026.

## API design best practices

### Consistent naming conventions

Resource names should stay plural and consistent across routes (/api/v1/users, /api/v1/orders, /api/v1/products), without mixing singular and plural (/api/v1/order) or slipping a verb into the URL (/api/v1/getAllProducts), the action belongs in the HTTP verb, not the path.

### Pagination

Two approaches dominate: cursor-based pagination (GET /api/v1/posts?cursor=...&limit=20, with a hasNextPage indicator and a nextCursor in the response), recommended for large datasets because it stays stable even as data changes between calls; and offset-based pagination (GET /api/v1/posts?page=2&limit=20), simpler to implement and consume but less reliable on large, constantly changing datasets.

### Rate limiting

Exposing quotas in response headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) lets clients anticipate the limit. When it's exceeded, the API should respond with 429 Too Many Requests, a Retry-After header, and a body explaining the limit, the time window, and the delay before retrying.

> Related read: [Securing your APIs: best practices, from OAuth 2.1 to AI agents](https://www.adservio.fr/en/insights/articles/securiser-ses-apis): OAuth 2.1 and PKCE, DPoP and mTLS tokens, rate limiting, DevSecOps governance and AI agents: the modern best practices to secure your APIs end to end.

## REST vs GraphQL: comparison

In summary, REST and GraphQL differ across several criteria. The learning curve is low for REST, moderate to high for GraphQL. REST natively suffers from over-fetching and under-fetching, a problem GraphQL solves by design. Versioning is explicit in REST (v1, v2), whereas GraphQL prefers gradual field deprecation.

Native HTTP caching (CDN) works out of the box with REST, while GraphQL requires a more elaborate caching strategy. File uploads remain simpler in REST (multipart) than in GraphQL. Real-time updates require a separate WebSocket in REST, while subscriptions are native to GraphQL.

Error handling relies on HTTP status codes in REST, versus an always-200 response with an errors array in GraphQL. On tooling, REST relies on Swagger/OpenAPI and GraphQL on its own Playground. Finally, REST performance is predictable, while GraphQL's depends heavily on the queries clients send.

## Conclusion

There is no absolute winner between REST and GraphQL.

Choose based on: - The complexity of your data - The diversity of your clients - Your team's skills - Your caching needs - Your performance constraints

And don't forget: you can have both!

## FAQ

### Does REST simply mean using JSON over HTTP?

No. REST is an architectural style based on resources, stateless communication, correct use of HTTP verbs and status codes, and ideally HATEOAS. Using JSON, prefixing URLs with /api/v1/, or doing CRUD are common practices, but they don't define REST on their own.

### What is GraphQL's main advantage over REST?

GraphQL solves over-fetching (receiving too many unused fields) and under-fetching (having to chain several calls to rebuild a view), by letting the client describe exactly the data it needs in a single query.

### Do you have to choose between REST and GraphQL, or can you combine them?

Both can coexist. REST remains well suited to simple CRUD with strong HTTP caching needs, while GraphQL is relevant for multiple clients with heterogeneous needs or complex composite views. A hybrid approach, REST for simple operations and GraphQL for aggregation, is common in production.
