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.

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.

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!
STAY POSTED
Get our next analyses and field notes straight to your inbox.




