REST or GraphQL: which API to choose

How REST and GraphQL differ: fourteen criteria, which approach for which project, one product card through both APIs, what to set up in each and common mistakes.

Stack and technologies Updated

In short

REST and GraphQL are two ways to give data to a website, an app or another system. REST is a set of addresses, one per resource: simple, cached by any CDN, understood by every tool and the standard for integrations with CRMs, payments and partners. GraphQL is one address and a schema of types: the client asks for exactly the fields it needs in one request, which is convenient for complex screens and many different clients, but caching, error handling and load protection have to be built separately. For most sites, shops and integrations REST with an OpenAPI description is enough; GraphQL pays off when there are many clients with different data needs.

In short: which one to choose

Look at who will use the API. If it is your own site, a mobile app with a few screens, a CRM, a payment system or a partner — REST: every developer knows it, every tool supports it, and responses are cached by address. With an OpenAPI description another team connects without calls and letters.

GraphQL is worth its extra setup when many clients need different slices of the same data: a web interface, two mobile apps and a partner portal, each with its own screens. Then one schema replaces dozens of special addresses, and the front-end team stops waiting for the back end to add a field.

  • Site, shop, integrations — REST
  • Many clients, different data — GraphQL
  • Either way — a described contract

REST and GraphQL: a detailed comparison

Fourteen criteria side by side — from the shape of a response to monitoring and protection.

CriterionRESTGraphQL
Model many addresses, one per resource one address and a schema of types
Shape of the response decided by the server decided by the client
Extra fields common none, only what was asked
A complex screen several requests one request
HTTP caching by address, CDN out of the box POST requests, needs its own cache
Errors HTTP status codes often status 200 with errors inside
Contract OpenAPI, written alongside the schema itself
Changes new fields or a /v2/ version new fields, old ones @deprecated
File upload simple usually a separate REST address
Load protection limits per address limits on query depth and cost
Monitoring by address in any log by operation name, needs setup
Client any HTTP client, even curl any, but a library is more convenient
Integrations with CRMs and payments the standard, plus webhooks rare
Entry bar low higher

Which approach for which project

Ten typical projects with a recommendation and the reason.

ProjectTakeWhy
Site or shop with its own front end REST a few clear addresses, cached by CDN
Exchange with a CRM or accounting REST the other side expects REST and webhooks
Payments REST payment systems work through REST and signed webhooks
Public API for partners REST connects with any tool, described in OpenAPI
Telegram bot or Mini App REST a few addresses are enough
Mobile app with several screens REST simpler, unless the screens are very different
Web, iOS, Android and a partner portal GraphQL each client takes its own slice of one schema
Dashboard with many widgets GraphQL one request instead of dozens
A headless CMS that already offers GraphQL GraphQL use what the system gives
Files and large exports REST streams and uploads are native to it

One product card, two APIs

The same data — a product and its reviews — through REST and GraphQL. Both ran on one test server; the responses in the comments are copied from real requests.

REST: the contract

Two addresses described in OpenAPI 3.1; the file passes the Redocly validator.

openapi.yaml
# REST: the contract is described in OpenAPI — another team connects from it
openapi: 3.1.0
info:
  title: Shop API
  version: 1.0.0
servers:
  - url: https://shop.example.com
security: []   # public catalogue, no key needed for reading
paths:
  /api/products/{sku}:
    get:
      operationId: getProduct
      summary: One product with all its fields
      parameters:
        - { name: sku, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: The product
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Product" }
        "404":
          description: No such product
  /api/products/{sku}/reviews:
    get:
      operationId: getProductReviews
      summary: Reviews of the product — a separate request
      parameters:
        - { name: sku, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: The reviews
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Review" }
        "404":
          description: No such product
components:
  schemas:
    Product:
      type: object
      required: [sku, title, price, stock, description]
      properties:
        sku: { type: string }
        title: { type: string }
        price: { type: integer }
        stock: { type: integer }
        description: { type: string }
    Review:
      type: object
      required: [author, rating]
      properties:
        author: { type: string }
        rating: { type: integer, minimum: 1, maximum: 5 }

REST: the requests

The card needs two requests, and the first brings fields the page does not use.

rest.sh
# REST: a product card with ratings takes two requests,
# and the first returns every field even if the page needs only the title
curl https://shop.example.com/api/products/A-100
# {"sku":"A-100","title":"Oak table","price":24000,"stock":3,
#  "description":"Solid oak, 160 × 90 cm, oil and wax finish"}

curl https://shop.example.com/api/products/A-100/reviews
# [{"author":"Anna","rating":5},{"author":"Mark","rating":4}]

GraphQL: the schema

The schema is both the contract and the documentation; reviews are a field of the product.

schema.graphql
# GraphQL: one schema, one address — the client chooses the fields
type Product {
  sku: String!
  title: String!
  price: Int!
  stock: Int!
  description: String!
  reviews: [Review!]!
}

type Review {
  author: String!
  rating: Int!
}

type Query {
  product(sku: String!): Product
}

GraphQL: the request

One request, only the needed fields. The error for an unknown field came with HTTP status 200.

query.graphql
# GraphQL: one POST request to /graphql — the client lists exactly the fields it needs
query ProductCard {
  product(sku: "A-100") {
    title
    reviews {
      rating
    }
  }
}

# Answer:
# {"data":{"product":{"title":"Oak table","reviews":[{"rating":5},{"rating":4}]}}}

# A field that is not in the schema is rejected before execution:
# Cannot query field "color" on type "Product".

A REST API that is easy to connect to

Most complaints about REST are about a poorly designed API, not about REST. Six rules that remove them.

  1. 01

    OpenAPI from day one

    The description is the contract: documentation, tests and client code are built from it.

  2. 02

    Field selection

    ?fields=title,price removes the main argument for GraphQL — extra data.

  3. 03

    Related data on request

    ?include=reviews returns the product with its reviews in one response.

  4. 04

    Honest status codes

    404, 409, 422 and 429 instead of 200 with an error inside — monitoring sees problems by itself.

  5. 05

    Pages and limits

    Lists are always paginated, and the client knows its request limit from the headers.

  6. 06

    Signed webhooks

    Events go to the other system by themselves, and the signature does not let a stranger fake them.

If GraphQL: what to set up from the start

GraphQL hands the client a lot of freedom. These settings keep it from turning against the server.

  1. 01

    Depth and cost limits

    Without them one nested request can load the database as much as thousands of ordinary ones.

  2. 02

    Batching against N+1

    DataLoader collects the reviews of fifty products into one query instead of fifty.

  3. 03

    Persisted queries

    Known queries go by a hash — they can be cached and nobody sends arbitrary ones.

  4. 04

    Operation names in logs

    All requests go to one address, so only the name shows which one is slow.

  5. 05

    Error codes inside

    Errors carry a code in extensions, and monitoring reads the body, not only the status.

  6. 06

    Rights on fields

    Access is checked in every resolver — one schema is open to every client.

Common mistakes when choosing

  1. GraphQL for one site

    A schema, resolvers and limits for one client that would get by with five addresses.

  2. REST without a description

    The other team learns the API by trial and error and by letters.

  3. Trusting status 200

    In GraphQL a response with errors often comes as 200 — monitoring by status misses it.

  4. An open GraphQL without limits

    One crafted request is enough to stop the server.

  5. An address for every screen

    REST turns into dozens of special endpoints — field selection would have been enough.

  6. Choosing by fashion

    The partners, the CRM and the payment system decide what the API must speak.

Questions about REST and GraphQL

Is GraphQL replacing REST?

No: REST remains the standard for integrations and public APIs, GraphQL holds its place in products with many clients.

Which is faster?

GraphQL saves requests on complex screens; REST wins on caching. Speed depends more on the database.

Can they be used together?

Yes, often: GraphQL for the product’s interfaces, REST and webhooks for partners and payments.

What is OpenAPI?

A standard description of a REST API: addresses, parameters and responses. Documentation and clients are generated from it.

Does GraphQL need a special client?

No, it is an ordinary POST with JSON; libraries such as Apollo or urql add a cache and convenience.

Is GraphQL secure?

As secure as its settings: depth limits, rights on fields and persisted queries are mandatory.

What about gRPC?

It is for exchange between internal services; browsers and partners still get REST or GraphQL.

Online form

Discuss
the API

I build REST APIs described in OpenAPI, with signed webhooks — another system connects from the documentation. Tell me about the project — I answer within one working day.

Or write to [email protected]