GraphQL vs REST: Which to Use

REST and GraphQL both expose data over HTTP, but they think about APIs in opposite ways. This guide compares how each works, where GraphQL's single-endpoint flexible queries win, where REST's simplicity and caching still rule, and how to pick the right one for your project.

Written by Projectech7 min readPublished
For B.E./B.Tech Computer Science and IT students designing APIs for web projects who know REST basics and want to understand when GraphQL is worth it Topics: GraphQL, REST, Node.js, APIs
Illustration comparing GraphQL and REST API styles, showing a single flexible GraphQL query endpoint beside multiple REST resource endpoints.
Illustration generated for this guide.
In this guide

If you have built any web app, you have built a REST API — probably without thinking twice. Endpoints like GET /api/users/42 and POST /api/products are the default shape of the modern web. Then someone mentions GraphQL and suddenly you wonder if your perfectly good REST API is obsolete.

It is not. But GraphQL solves a real pain that REST has, and knowing which one to reach for is a decision every backend developer makes regularly. This guide explains both honestly, with the trade-offs that actually matter for student projects and interviews.

What REST really is

REST (Representational State Transfer) is an architectural style, not a protocol. In practice, REST APIs share these traits:

  • Resource-based URLs — things, not actions: /users/42, /orders/17/items
  • HTTP methods carry meaning — GET reads, POST creates, PUT/PATCH updates, DELETE removes
  • Stateless — each request contains everything the server needs
  • Standard HTTP caching — GET responses can be cached by browsers, CDNs, and proxies

A typical REST interaction for a social app profile page looks like this:

GET /api/users/42
GET /api/users/42/posts
GET /api/users/42/friends

Three round trips to assemble one screen. That is REST's famous weakness — and the reason GraphQL exists.

What GraphQL really is

GraphQL is a query language for APIs, developed at Facebook and released in 2015. Instead of many endpoints, you get one endpoint (usually /graphql) and the client asks for exactly the data it wants:

query {
  user(id: "42") {
    name
    avatar
    posts(limit: 5) {
      title
      likes
    }
    friends {
      name
    }
  }
}

One request, exactly the fields you need — no more, no less. The server has a schema that describes every type and field available, and tools like GraphiQL give you autocomplete and documentation for free from that schema.

Note: GraphQL is not a database technology and not a replacement for your database. It is a layer between your client and your data sources — your resolvers still talk to Postgres, MongoDB, or other APIs underneath.

The real differences, side by side

REST GraphQL
Endpoints Many, one per resource One (/graphql)
Data fetching Fixed shape per endpoint Client chooses fields
Over-fetching Common (you get fields you ignore) Rare (you ask for exactly what you need)
Under-fetching Common (N requests for N resources) Rare (nested data in one query)
Caching Easy — standard HTTP caching, CDNs Harder — all requests are POST to one URL
Tooling/docs Swagger/OpenAPI (hand-maintained) Schema is self-documenting, introspection built in
Learning curve Gentle Steeper (schema, resolvers, query language)
File uploads Trivial (multipart forms) Awkward (needs extensions or separate REST endpoint)

Over-fetching and under-fetching, concretely

These are the two words interviewers expect you to know:

  • Over-fetching: GET /api/users/42 returns 40 fields when your mobile screen shows 4. Wasted bandwidth — painful on slow networks.
  • Under-fetching: you need the user, their posts, and their friends, so you make 3+ requests. More latency, more code.

GraphQL eliminates both by letting the client specify the exact shape. REST handles both with careful endpoint design (query params like ?fields=name,avatar or embedding related data).

Caching: REST's quiet superpower

This is the trade-off people underestimate. Because REST uses GET requests with unique URLs, the entire HTTP caching infrastructure works out of the box: browser caches, reverse proxies, CDNs. A GET /api/products response can be cached at the edge and served to thousands of users without touching your server.

GraphQL typically sends queries as POST bodies to a single URL, so none of that applies automatically. You can cache GraphQL (persisted queries, application-level caching, DataLoader for batching), but you build it yourself. For read-heavy public content — blogs, catalogues, documentation — REST's free caching is a genuine advantage.

When to choose GraphQL

  • Your clients have different data needs — a mobile app wants 5 fields, a dashboard wants 50. GraphQL serves both from one API.
  • Nested, relational data — social graphs, project management tools, anything where one screen aggregates many related entities.
  • Rapid frontend iteration — frontend teams can add fields to queries without waiting for backend endpoint changes (as long as the schema supports them).
  • You want self-documenting APIs — the schema plus GraphiQL is genuinely better documentation than most hand-written Swagger.

When to choose REST

  • Simple CRUD apps — most student projects, admin panels, and straightforward apps. REST is simpler to build, test, and debug.
  • Heavy caching needs — public content, high read traffic. HTTP caching is battle-tested and free.
  • File uploads/downloads — multipart forms in REST are simple; GraphQL file upload is a known pain point.
  • Your team is small or learning — REST has a gentler learning curve and every HTTP tool (curl, Postman, browser devtools) understands it natively.
  • Microservices with simple contracts — service-to-service calls are often simpler as REST.

Note: "Which is faster?" is the wrong question. Performance depends on your resolvers, database queries, and caching — not the API style. A badly written GraphQL resolver that fires 100 database queries (the N+1 problem) is slower than any REST endpoint.

The N+1 problem (and DataLoader)

The classic GraphQL pitfall: your query asks for 10 users and each user's posts. A naive resolver runs 1 query for users + 10 queries for posts = 11 database queries. Ask for 100 users and it is 101 queries. This is the N+1 problem.

The standard fix is DataLoader (or your framework's equivalent): it batches those 10 separate "posts for user X" calls into a single "posts for users [1..10]" query. If you use GraphQL, learn DataLoader early — it is the difference between a demo and a working API.

A practical decision checklist

Ask these in order:

  1. Is my data simple CRUD with predictable shapes? → REST. Done.
  2. Do different clients need very different slices of the same data? → GraphQL.
  3. Is aggressive HTTP caching central to my design? → REST.
  4. Am I aggregating many related entities per screen? → GraphQL.
  5. Is this a learning project with a deadline? → REST. You will ship faster.
  6. Am I preparing for interviews at product companies? → Learn GraphQL basics anyway; "explain the trade-offs" is a common interview question and this guide is your answer.

You can also mix: many production systems expose GraphQL to clients while services talk REST internally. The styles are not enemies.

Designing a GraphQL schema

If you choose GraphQL, the schema is your API contract — design it deliberately, not as a mirror of your database tables.

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post!]!
}

type Query {
  user(id: ID!): User
  users(limit: Int = 20): [User!]!
}

type Mutation {
  createPost(title: String!, body: String!): Post
}

The conventions that matter:

  • Query for reads, Mutation for writes. This separation is a GraphQL rule, not a suggestion — clients and tools rely on it.
  • ! means non-null. Fields are nullable by default. Mark a field non-null only when it genuinely cannot be absent; overusing ! turns partial failures into whole-query failures.
  • Design for client needs, not table shapes. If the frontend always shows a user with their recent posts, expose that nesting — do not force the client to assemble it from normalized tables.
  • Deprecate, do not break. Mark old fields @deprecated(reason: "use fullName") and keep them working. GraphQL's no-versioning promise only holds if you honor it.
  • Paginate lists from day one. users(limit: 20) with cursor pagination beats returning unbounded arrays that someone eventually queries at 100,000 rows.

Common mistakes

  • Choosing GraphQL because it sounds modern. If your app is CRUD with one web client, REST ships faster and debugs easier. Hype is not a requirement.
  • Exposing your entire database through GraphQL. Just because the schema can expose everything does not mean it should. Design the schema for client needs, apply the same authorization thinking you would for REST endpoints.
  • Ignoring the N+1 problem. Test with realistic data volumes, not 3 rows.
  • No rate limiting or query cost analysis. A deeply nested GraphQL query can be expensive; limit query depth and complexity, especially on public APIs. See the rate limiting guide for the general technique.
  • Versioning confusion. REST versions with URLs (/v1/, /v2/); GraphQL evolves by adding fields and deprecating old ones — never breaking existing queries. Both need a deliberate strategy.

Where to go from here

More project guides

More in Web Development