AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

REST vs GraphQL: Understanding Modern APIs

📚 Web APIs⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Two students, the same app, two very different amounts of data

Imagine two students, Ananya and Rohan, are each building a train-search screen for a college project — something like a simplified IRCTC search page. Ananya's server sends back a full passenger and train record for every search, the way most systems built ten years ago do. Rohan's server sends back only the three or four fields his screen actually displays. Both apps look identical on screen. Both work correctly. But every time a user on a slow railway-platform Wi-Fi or a limited mobile recharge pack taps "search," Ananya's app pulls down noticeably more data than Rohan's — data the screen never even shows.

This is not a coincidence of coding style. It is the direct, structural difference between two ways of designing an API: REST, the older and still most common style, and GraphQL, a newer style that was invented specifically to fix this kind of waste. By the end of this chapter you will be able to read a REST endpoint or a GraphQL query and know exactly what data crosses the network, why, and which style a real engineering team would reach for in a given situation.

What an API actually is, in one precise sentence

An API (Application Programming Interface), in the context of the web, is an agreed contract that lets one program (a client — your phone's app or a website running in a browser) ask another program (a server, sitting in a data centre) for data or actions, over the internet, using a request-and-response exchange. The client sends a request; the server computes an answer and sends back a response. REST and GraphQL are two different sets of rules for how that request and that response are shaped. Neither one is "the internet" itself — both sit on top of ordinary HTTP, the same protocol your browser uses to load any web page.

REST: treat every piece of data as a resource with an address

REST (Representational State Transfer, a term coined by computer scientist Roy Fielding in his year-2000 doctoral dissertation) is built on one core idea: every piece of data the server knows about is a resource, and every resource gets its own URL, the way every file on a computer gets its own path. A single train is a resource. A single user's profile is a resource. A collection of trains matching a search is a resource. You don't send instructions like "compute me a discount" to REST — you say "give me this resource" or "create a new resource here," and you say it using one of a small, fixed set of HTTP verbs:

  • GET — read a resource, without changing anything on the server.
  • POST — create a new resource (for example, book a new ticket).
  • PUT — replace an existing resource entirely with new data.
  • PATCH — update part of an existing resource, leaving the rest unchanged.
  • DELETE — remove a resource.

The server also replies with a numeric status code that tells the client, at a glance, how the request went: 200 OK means success, 201 Created means a new resource was made, 400 Bad Request means the client's request was malformed, and 404 Not Found means the URL doesn't point to any resource that exists.

A worked example: fetching one user's profile

Suppose Ananya's app needs to show a "Welcome back" screen after login. It sends this REST request:

GET /v1/users/42 HTTP/1.1
Host: railyatri-demo.example.com

The URL /v1/users/42 is read almost like a sentence: "in API version 1, give me the user resource whose id is 42." Because the verb is GET, nothing on the server changes — this is a pure read. The server looks up user 42 in its database and replies with a JSON body. Here is the actual response:

{
  "id": 42,
  "name": "Ananya Sharma",
  "email": "ananya.sharma@example.com",
  "phone": "+91-98765-43210",
  "dateOfBirth": "2011-03-14",
  "loyaltyPoints": 1250,
  "address": {
    "city": "Pune",
    "state": "Maharashtra",
    "pincode": "411001"
  },
  "avatarUrl": "https://example.com/avatars/42.png"
}

Let's trace this response the way you'd trace a piece of code, field by field, because the exact count matters for what comes next. Reading top to bottom, the object has exactly 8 top-level fields: (1) id, (2) name, (3) email, (4) phone, (5) dateOfBirth, (6) loyaltyPoints, (7) address — itself a nested object bundling three more pieces of information (city, state, pincode) inside a single field — and (8) avatarUrl. Count them again if you like: id, name, email, phone, dateOfBirth, loyaltyPoints, address, avatarUrl. That's eight, no more and no less.

Now — Ananya's "Welcome back" screen only displays two things: the user's name and their loyaltyPoints. Everything else in that response — id, email, phone, dateOfBirth, address, and avatarUrl, which is 6 of the 8 fields — was downloaded onto the user's phone and then simply thrown away. This is called over-fetching: the shape of the response is fixed by the server, not chosen by the client, so the client is stuck receiving whatever that particular endpoint always sends, even when it needs a small fraction of it.

The mirror-image problem: under-fetching

REST has a second, opposite problem that shows up when a screen needs data assembled from more than one resource. Suppose the same app now needs to show, for a searched route, every matching train and how many seats are free in each travel class — sleeper, third AC, second AC. A typical REST design keeps the seat-availability endpoint separate from the train-search endpoint (this is normal REST practice, since "trains matching a search" and "seat counts for one train" are different resources). So the client first calls:

GET /v1/trains?from=Pune&to=Delhi&date=2026-08-20 HTTP/1.1

which returns a short list of matching trains — for this walkthrough, invented purely as teaching data (these numbers and names don't correspond to any real service), say two trains: number 20101, "Pune Superfast," and number 20102, "Pune Mail." Their basic details come back, but not their seat availability — that lives at a different resource. So for each train in the list, the app must fire a second request:

GET /v1/trains/20101/seats HTTP/1.1
GET /v1/trains/20102/seats HTTP/1.1

Count the total round trips to the server for this one screen: 1 request for the train list, plus 1 request per train (2 trains) for seat data, equals 3 separate requests before the screen can render. If the search had returned 10 trains, it would be 11 requests. This pattern — needing to chain many small requests together because no single REST endpoint returns everything a screen needs — is called under-fetching, and in real systems it is often nicknamed the "N+1 problem" (1 list request, then N follow-up requests, one per item in the list).

Over-fetching and under-fetching are two faces of the same root cause: in REST, the server decides the exact shape of every response ahead of time, once, for all clients. A phone app, a smartwatch app, and a web dashboard querying the same endpoint all get the identical shape, whether or not it fits what each of them actually needs.

GraphQL: let the query describe the exact shape of the answer

GraphQL, developed inside Facebook starting in 2012 and released as an open specification in 2015, attacks this problem from a different angle. Instead of many URLs, one for each resource, a GraphQL server exposes a single endpoint (commonly something like /graphql). Instead of the verb-plus-URL of REST, the client sends a query — a piece of text that looks like the JSON it wants back, but with the values stripped out and only the field names left in. The server reads that query, fetches precisely those fields from wherever they live (possibly several different internal data sources), assembles them into one JSON object shaped exactly like the query, and sends back exactly one response.

Crucially, GraphQL is not a free-for-all where a client can ask for any field name it dreams up. Every GraphQL server publishes a schema — a strict type definition of every object, every field, and every field's type, written in GraphQL's own schema language. The schema is the contract; a query is only valid if every field it asks for actually exists in the schema. Here is a schema for our train-search service:

type SeatClass {
  code: String!
  seatsAvailable: Int!
}

type Train {
  number: String!
  name: String!
  from: String!
  to: String!
  departureTime: String!
  arrivalTime: String!
  classes: [SeatClass!]!
}

type Query {
  trains(from: String!, to: String!, date: String!): [Train!]!
  train(number: String!): Train
}

Read this the way you'd read a function's type signature. Query is the entry point — it's the list of "questions" this server knows how to answer. It exposes two fields: trains(...), which takes a from station, a to station, and a date (the ! after a type means that value is required — the query is rejected without it) and returns a list of Train objects; and train(number: String!), which takes one train number and returns a single Train (or nothing, if no train has that number — notice this field has no ! after Train, meaning the answer may be empty). Each Train has scalar fields like name and departureTime, plus a classes field that is itself a list of SeatClass objects, each carrying a code (like "SL" or "3A") and a seatsAvailable count.

Solving the earlier problem in one round trip

Now Rohan rebuilds the same train-search-with-seats screen using this GraphQL schema. He sends a single query to the single endpoint:

query {
  trains(from: "Pune", to: "Delhi", date: "2026-08-20") {
    number
    name
    departureTime
    classes {
      code
      seatsAvailable
    }
  }
}

Trace what the server does with this, field by field. It sees the top-level field trains, matches it against the Query type in the schema, and runs the search with the three arguments supplied. For each train found, it builds an object containing exactly the four fields the query asked for at that level — number, name, departureTime — plus, nested inside, a classes list where each entry has exactly code and seatsAvailable, because those are the only two sub-fields the query requested under classes. Notice arrivalTime, from, and to were never asked for, so the server never puts them in the response — not because they don't exist, but because this particular query didn't request them. The response, for our two example trains, comes back like this:

{
  "data": {
    "trains": [
      {
        "number": "20101",
        "name": "Pune Superfast",
        "departureTime": "06:15",
        "classes": [
          { "code": "SL", "seatsAvailable": 42 },
          { "code": "3A", "seatsAvailable": 6 }
        ]
      },
      {
        "number": "20102",
        "name": "Pune Mail",
        "departureTime": "21:40",
        "classes": [
          { "code": "SL", "seatsAvailable": 0 },
          { "code": "3A", "seatsAvailable": 11 }
        ]
      }
    ]
  }
}

One request. One response. Zero unused fields, and zero follow-up calls — compare that to the 3 separate REST requests the identical screen needed earlier. The shape of the JSON that comes back is, quite literally, a mirror image of the shape of the query that was sent: wherever the query had a field name, the response has that field name with its value filled in; wherever the query nested one field inside another (like classes inside a train), the response nests the same way. This is the defining trick of GraphQL — you write the query to look like the answer you want.

Correcting a common misconception

A mistake many beginners make at this point is to conclude: "So in GraphQL, the client can ask the server for literally any field it wants, whenever it wants." This is false, and the schema above is exactly why it's false. A GraphQL server cannot simply accept any field name a client dreams up — every field in every query must already be declared in the schema, with a matching type. If Rohan's app mistakenly asked for a field that was never defined, for example:

query {
  trains(from: "Pune", to: "Delhi", date: "2026-08-20") {
    number
    secretDiscount
  }
}

the server would reject the entire query before running it, with an error such as Cannot query field "secretDiscount" on type "Train". The client gets to choose which of the fields that already exist to include in a given request — it cannot invent new fields, new types, or new capabilities that the schema author didn't design in. The schema is still the server's decision; GraphQL only moves the decision of which subset of those fields to fetch, per request, onto the client. That distinction — flexible selection from a fixed, published vocabulary, not unlimited freedom — is the whole point.

REST and GraphQL, side by side

AspectRESTGraphQL
EndpointsMany URLs, one per resource (/users/42, /trains, /trains/20101/seats)Usually one single endpoint (e.g. /graphql) for everything
Response shapeFixed by the server for that endpoint; every client gets the same fieldsChosen per request by the client's query; only requested fields come back
Over-fetchingCommon — extra fields you didn't ask for often arrive anywayStructurally avoided — the response mirrors the query
Under-fetching / multiple round tripsCommon when a screen needs data from several resourcesStructurally avoided — nested data comes back in one response
HTTP cachingEasy — each GET URL can be cached by browsers and CDNs using standard HTTP caching rulesHarder — most queries go through POST to one URL, so simple URL-based caching doesn't apply directly
VersioningOften via URL, e.g. /v1/ vs /v2/, when the shape must changeFields are added freely; old fields are marked deprecated rather than removed, so old queries keep working
Learning curve for a new APIRead the docs for each endpoint separatelyExplore the one schema; many GraphQL servers support self-describing tools that list every available type and field

A visual summary of the round-trip difference

REST: 3 round trips Client (app) Server 1. GET /trains?... list: 2 trains, no seats 2. GET /trains/20101/seats seats for train 20101 3. GET /trains/20102/seats seats for train 20102 Screen ready only after 3 requests GraphQL: 1 round trip Client (app) Server query { trains(...) { number name departureTime classes { code seatsAvailable } } } one JSON object shaped exactly like the query — both trains, both seat classes Screen ready after 1 request

So which one should a real team choose?

Neither style is simply "better" in every situation — each trades one kind of simplicity for another. REST's many small, resource-shaped URLs are easy to cache with ordinary HTTP infrastructure (a browser or a content-delivery network can cache a GET to /trains/20101 the same way it caches an image), easy to test with a browser address bar, and easy to reason about because a URL alone tells you what you're getting. This is why public, simple, cacheable data feeds — including much of the open data ISRO and other government bodies publish — are typically REST. GraphQL earns its keep when a single screen needs data assembled from many different underlying pieces and different clients (a phone app, a smartwatch, a web dashboard) need different-shaped slices of the same underlying data — which is precisely the mobile-scale product problem GraphQL was invented at Facebook to solve. Neither replaces the other everywhere; a large real system, including many you use daily, may run both side by side for different parts of its API.

Check yourself — active recall

  1. In the GET /v1/users/42 example, name all 8 top-level fields in the response, in order.
  2. An app only needs a user's name and loyaltyPoints from that same response. How many of the 8 fields are downloaded but never used? Is this over-fetching or under-fetching?
  3. Rewrite GET /v1/users/42 as an HTTP request that instead updates user 42's phone number only, leaving every other field unchanged. Which HTTP verb fits, and why not PUT?
  4. Explain, using the seat-availability example, why the REST version needed 3 requests while the GraphQL version needed 1. What made the difference — the network, or the API design?
  5. A classmate says: "GraphQL lets the client fetch any field it wants, so the server has no control over the data." Using the secretDiscount example, explain what is wrong with this statement.
  6. Using the schema given in this chapter, write a GraphQL query that uses the train(number: String!) field to fetch just the name and departureTime of the train numbered "20101" (the fictional practice number used earlier in this chapter).

Summary

  • Both REST and GraphQL are API styles built on top of ordinary HTTP requests and responses; they define how a client asks a server for data, not the network itself.
  • REST models data as resources, each with its own URL, accessed through a small set of HTTP verbs (GET, POST, PUT, PATCH, DELETE). The response shape for a given endpoint is fixed by the server for every client.
  • REST commonly causes over-fetching (unused fields arrive anyway, as in the 8-field user response where only 2 fields were needed) and under-fetching (a screen needing several resources must chain multiple requests, as in the 3-request seat-availability example).
  • GraphQL exposes one endpoint and lets the client send a query shaped like the desired response; the server returns exactly the fields requested, nested exactly as requested, in a single round trip.
  • GraphQL is governed by a strict, published schema of types and fields. A client can only request fields that already exist in the schema — it cannot invent new ones, so the server never loses control over what data is exposed.
  • REST's per-resource URLs make it easy to cache with standard HTTP tools and easy to reason about from the URL alone; GraphQL's single flexible endpoint shines when many different clients need differently-shaped slices of richly connected data. Real-world systems often use both, chosen per use case rather than as a single universal rule.
← Fetch API Deep-Dive: Making HTTP RequestsWebSockets: Real-time Communication →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn