API-First CMS: REST vs GraphQL for Content Delivery (2026)

Caching wins, type safety wins, the N+1 problem in both worlds, and which CMSes ship which protocol.

HamiPa HamiPa
June 12, 2026 · 16 min read
API-First CMS: REST vs GraphQL for Content Delivery (2026)

REST is older and easier. GraphQL is newer and more flexible. Most blog posts comparing them stop there — and miss the actual deciding factors for picking a CMS API in 2026. This post is the technical comparison: REST vs GraphQL for content delivery, with code examples, performance trade-offs, and the deciding criteria for real CMS evaluation.

TL;DR: REST wins on simplicity, HTTP-level caching, and rate-limiting ergonomics. GraphQL wins on request precision, single-round-trip nested data, and strong typing via schema. The N+1 problem exists in both — solutions differ. CDN caching is significantly easier on REST. For most CMS use cases the right answer is "support both" — which is why Strapi, Contentful, Payload, Sanity all ship REST and GraphQL together. The real decision is which one you write your queries in, and that depends on the shape of your content and your team's preferences.

The audience: developers picking the integration approach for a CMS, or evaluating CMSes where API design is one of the deciding factors. If you're earlier in the architecture choice, see the 10-point headless CMS evaluation checklist and headless CMS vs traditional CMS: key differences.


What Each One Actually Is

The architectural difference matters, so let's nail it down.

REST (Representational State Transfer) exposes resources at URLs, with HTTP verbs as the operations. Each resource has a stable URL; the URL itself encodes what you're asking for.

GET    /api/posts            # list posts
GET    /api/posts/123        # single post
POST   /api/posts            # create
PATCH  /api/posts/123        # update
DELETE /api/posts/123        # delete
GET    /api/posts/123/comments  # nested resource

The shape of the response is fixed by the server. If /api/posts/123 returns 30 fields, you get 30 fields whether you wanted them or not. To fetch related data (post + author + categories), you typically make multiple requests or use a ?include=author,categories parameter the API has to support explicitly.

GraphQL exposes a single endpoint (typically /graphql) where you POST a query that says exactly what you want. The query is the contract; the server returns only what you asked for.

query GetPostWithAuthor {
  post(id: "123") {
    title
    body
    publishedAt
    author {
      name
      avatar
    }
    categories {
      name
      slug
    }
  }
}

One round trip. Exactly the fields you asked for. The schema is typed and introspectable — the server publishes its full schema, and tools (codegen, Apollo Studio, GraphiQL) read it to generate types and query helpers automatically.

The architectural difference flows from there. REST trades flexibility for simplicity; GraphQL trades simplicity for flexibility. Both are legitimate; both have failure modes the other doesn't.

For more on what an API-first CMS actually means in practice, see what is a headless CMS and the headless vs traditional CMS architecture comparison.


Where REST Wins

Five places REST is the better technical pick:

1. HTTP-level caching just works.

Every URL is cacheable by Varnish, Cloudflare, AWS CloudFront, or any HTTP cache. GET /api/posts/123 returns the same response for any client; the cache layer can store it under that URL key. CDN edges respect Cache-Control headers without special configuration.

GraphQL queries all hit the same /graphql endpoint with different POST bodies. CDN caches don't know how to cache POST requests by default. You can use persisted queries (where the client sends a hash and the server resolves it) to make GraphQL cacheable, but it's extra work and many teams skip it. The result: GraphQL APIs typically hit the origin on every request, which is fine for low-traffic sites and expensive at scale.

2. Lower learning curve.

Every developer who's worked on a web app knows fetch('/api/posts/123'). GraphQL requires learning the query language, understanding fragments and aliases, picking a client library (urql, Apollo, Relay), and understanding the codegen pipeline. The on-ramp is steeper.

For small teams or projects with one frontend developer, the REST learning curve is meaningfully lower. For projects with rapid onboarding (junior devs, freelancers, contractors), REST is faster to be productive in.

3. Simpler rate limiting.

Rate limits in REST are per-URL or per-token: "100 requests/minute to /api/posts." Cloudflare can enforce this at the edge without seeing the response body.

GraphQL rate limits are harder. A simple query asking for 1 post is "one request" but a complex query asking for 100 posts with all their nested authors and comments is also "one request." Real GraphQL rate limiting needs query complexity analysis (count fields, depth, breadth) — typically done at the application layer. Most CMSes support this; configuring it well takes more effort than REST.

4. HTTP semantics for free.

REST inherits HTTP's operational model: status codes (200, 404, 500), conditional requests (If-Modified-Since, ETags), cache headers, content negotiation. Tooling (curl, Postman, browser DevTools) shows you the request/response cycle naturally.

GraphQL multiplexes everything into POST 200 responses. A "not found" might come back as a 200 with errors in the JSON body. A partial failure (some fields succeeded, others errored) is a 200. Standard HTTP tooling is less useful for GraphQL debugging; you typically need a GraphQL-specific tool (GraphiQL, Apollo Studio).

5. Easier to expose narrowly.

Want to expose only /api/posts and /api/categories publicly while keeping everything else private? In REST, that's a route-level concern: define those endpoints, don't define others. In GraphQL, the schema is one big graph; restricting access requires schema-level permission rules per field per role. More powerful, more work.

For the comparison from the CMS-evaluation angle, see the 10-point headless CMS checklist — API quality is one of the 10 dimensions scored there.


Where GraphQL Wins

Four places GraphQL is the better technical pick:

1. Request only the fields you need.

The classic GraphQL win. A list page might need just title, slug, excerpt; a detail page needs everything. With REST, both endpoints return the same shape (or you build separate endpoints). With GraphQL, the same query type returns different fields based on what each page asks for.

The bandwidth saving compounds at scale. A mobile app fetching 50 posts with REST might pull 200KB of fields it doesn't render. The same fetch with GraphQL pulls 30KB if the query is narrow. For mobile-first or bandwidth-constrained projects, the difference is real.

2. Single round-trip for nested data.

Fetching "post + author + categories + comments" is one GraphQL query. The same in REST is typically:

GET /api/posts/123          # post
GET /api/users/456          # author (post.author_id)
GET /api/posts/123/categories
GET /api/posts/123/comments

Four requests. Each round-trip adds latency. On mobile or slow networks, the gap is significant.

REST APIs usually solve this with ?include=author,categories,comments parameters. The CMS has to explicitly support whichever combinations you want. GraphQL handles arbitrary combinations natively because the query is the contract.

3. Strong typing via schema.

Every GraphQL endpoint publishes its schema via introspection. Tools read it to generate types automatically. With graphql-codegen, you get TypeScript types for every query, every variable, every result — automatically, no hand-maintained types.

REST APIs can offer the same with OpenAPI/Swagger specs but the spec is hand-maintained, often out of sync with reality, and many CMSes don't ship one. GraphQL's typing is mandatory by design; REST's is opt-in by discipline.

For the deeper take on type safety as a CMS evaluation criterion, see TypeScript-first CMS platforms: why type safety matters.

4. Schema as documentation.

A GraphQL schema is browsable in GraphiQL, Apollo Studio, or any introspection tool. Developers can explore "what data is available?" by typing characters; autocompletion shows the rest. New devs onboard faster.

REST API documentation depends on what the team writes — Postman collections, OpenAPI specs, hand-written docs. The quality varies wildly. GraphQL's docs are the schema, which can't fall out of sync.

For framework-specific picks where GraphQL adoption matters, see best CMS for React developers in 2026 and best CMS for Next.js.


The N+1 Problem and How Each Solves It

The N+1 problem appears in both worlds and matters at scale: fetching a list of N items, then making N additional queries for related data.

REST N+1 example:

const posts = await fetch('/api/posts?limit=20').then(r => r.json());
// Then fetch author for each
const postsWithAuthors = await Promise.all(
  posts.map(async post => ({
    ...post,
    author: await fetch(`/api/users/${post.author_id}`).then(r => r.json())
  }))
);
// 21 round trips total

REST solves this with include parameters or embedded resources: ?include=author adds the author into the response. The CMS has to explicitly support this for the fields you want. Most CMSes do.

GraphQL N+1 example (worse than it looks):

query Posts {
  posts(limit: 20) {
    title
    author { name }   # the resolver fires once per post — 21 queries to the database
  }
}

The query is one HTTP request, but the server's resolver typically runs N+1 database queries. GraphQL solves this with DataLoader — a batching/caching layer that collects all author lookups within a tick and runs one batched query. Done right, the 21 database queries become 2 (one for posts, one for all authors).

The catch: every CMS implements DataLoader differently (or not at all). Strapi v5 has it. Sanity has GROQ which solves it natively via projection. Contentful has built-in batching. WPGraphQL on WordPress has DataLoader integration but the WordPress data model fights it.

For the performance benchmarking angle that catches N+1 in production, see CMS performance benchmarks: what to test.


Caching: Where the Production Difference Shows Up

Caching is the dimension where REST and GraphQL diverge most in production.

REST caching: Every URL is a cache key. GET /api/posts/123 returns the same response for any caller; the response can be stored at the CDN edge with Cache-Control: public, max-age=300, s-maxage=3600. Subsequent requests for the same URL hit the cache, never reach origin.

The result: anonymous read traffic on REST APIs scales nearly free once cached. An origin that handles 100 RPS can serve millions of cached requests per second through Cloudflare or Fastly.

GraphQL caching: All queries hit the same /graphql endpoint via POST. POSTs aren't cacheable by default. There are workarounds:

  • Persisted queries — hash queries server-side, clients send the hash; the GET request becomes cacheable by URL. Apollo Client supports this; Relay does too. Adds complexity to the build pipeline.
  • GET-based GraphQL — short queries can be encoded in the URL query string; the GET is cacheable. Doesn't work for complex queries (URL length limits).
  • Application-layer caching — cache the resolver outputs in Redis. Works but doesn't get you CDN-edge speed.

The caching gap matters specifically at scale. For sites under 10K daily anonymous reads, the difference is invisible. For sites at 100K+ daily reads of the same content, REST's free CDN caching is a 10-100x cost difference vs uncached GraphQL.

This is why most large headless-CMS-powered sites either use REST (Contentful CDN), or a combination of GraphQL + persisted queries + edge caching (Apollo Client + Cloudflare). Pure GraphQL without persisted queries hits origin on every request, which is fine for small sites and expensive at scale.

For the broader performance/scaling angle, see CMS performance benchmarks: what to test.


Which CMSes Ship Which

Most production headless CMSes ship both. The differences are in defaults and quality.

CMS REST GraphQL Other Default for new projects
Sanity GROQ (native query lang) GROQ
Contentful REST (Content Delivery API)
Strapi v5 ✓ (plugin) REST
Hygraph GraphQL only
Payload v3 Local API (in-process) Local API for SSR; REST for external
DatoCMS GraphQL
Storyblok — (paid tier) REST
Directus REST
WordPress via WPGraphQL plugin REST
UnfoldCMS (roadmap) (roadmap) Inertia-coupled today Inertia for monolithic; public API on roadmap

A few specifics worth knowing:

  • Sanity's GROQ is its own query language — neither REST nor GraphQL. Worth learning if you're on Sanity; not transferable to other platforms.
  • Hygraph is GraphQL-only — no REST fallback. If your team isn't GraphQL-comfortable, Hygraph is the wrong pick.
  • Strapi GraphQL is via plugin — works well but has its own performance footguns (deeply nested populates without DataLoader).
  • WPGraphQL is the de-facto GraphQL plugin for WordPress and is solid; it doesn't change the fact that WordPress's data model (postmeta serialization) makes some queries slow regardless of the API on top.
  • Payload v3's Local API is the most-recommended for Next.js apps — it bypasses HTTP entirely, runs in-process, and gives you typed queries directly in React Server Components.

For specific platform comparisons, see UnfoldCMS vs Sanity, vs Contentful, vs Strapi, and vs Payload.


When to Pick Each

The honest decision framework:

Pick REST when:

  • The CMS will serve high-traffic public anonymous reads (CDN caching is the deciding factor)
  • The team is small or includes contractors/freelancers (lower learning curve matters)
  • The frontend stack doesn't have strong GraphQL tooling (vanilla React without Apollo, plain HTML, mobile native)
  • You need fine-grained per-endpoint rate limiting
  • You're publishing public APIs other developers will consume (REST is more universally understood)

Pick GraphQL when:

  • The frontend has many distinct page types pulling different field shapes (precision saves bandwidth)
  • You need strong end-to-end TypeScript types (codegen from schema is a meaningful win)
  • The content shapes are deeply nested (post → author → posts-by-author → tags)
  • The team is comfortable with GraphQL tooling (Apollo, urql, codegen pipelines)
  • You're building primarily for authenticated users (caching matters less; auth tokens prevent CDN caching anyway)

Pick "both, choose per-call":

  • Most production CMS projects. Use REST for high-volume public reads (homepage, blog list, sitemap-fetch). Use GraphQL for complex authoring/admin queries where field precision matters.

For most teams the answer is "the CMS supports both; pick the one that fits each use case." That's what Strapi, Contentful, Payload, Sanity all encourage. The deciding factor isn't the protocol; it's the CMS's overall fit for your project. See the 10-point headless CMS evaluation checklist for the broader evaluation framework.


What to Do About It

If you're picking an API approach for a CMS project:

  1. Don't pick REST vs GraphQL in isolation. Pick the CMS first based on broader criteria (data model, editor UX, hosting, pricing). Then use whichever API the CMS does well.
  2. Default to REST for high-traffic public sites. CDN caching is the production win that compounds. GraphQL on uncached origin gets expensive fast.
  3. Default to GraphQL for authenticated, type-heavy frontends. Codegen + typed queries + nested fetching make development meaningfully faster.
  4. Test the actual queries you'll run. Pull the CMS's docs, write your two most complex queries (a list page, a detail page with nested fields), measure response time and payload size. The marketing claim and the actual experience often differ.
  5. For Next.js + Payload specifically, use the Local API. It bypasses HTTP entirely; the perf and DX wins are real. See best CMS for React developers in 2026 for the framework-specific picks.

If you're considering UnfoldCMS specifically: today we ship Inertia-coupled (no public API) which is the right shape for monolithic Laravel + React apps. Public REST + GraphQL endpoints are on the roadmap for teams that need separate frontend services. See pricing, book a demo, or the modern CMS stack: Laravel + React + Inertia for the architectural angle.


FAQ

Should I use REST or GraphQL for a headless CMS?

For most production projects, both — pick per-call based on the use case. REST for high-traffic public anonymous reads (homepage, blog list, sitemap) where CDN caching is the deciding factor. GraphQL for authenticated dashboards, complex nested queries, or frontends where TypeScript codegen matters. The CMS choice matters more than the protocol; pick a CMS that supports both well.

Is GraphQL faster than REST for a CMS?

Per request, often yes — GraphQL fetches multiple resources in one round-trip and returns only the fields you asked for. At scale with caching, REST is dramatically faster because every URL is independently cacheable at the CDN edge. GraphQL queries hit origin on every request unless you set up persisted queries (extra complexity). For high-traffic anonymous content, REST + CDN typically beats GraphQL on cost and speed.

Why do most headless CMSes ship both REST and GraphQL?

Because the trade-offs are real and different use cases benefit from different approaches. REST wins on caching, simplicity, and rate limiting. GraphQL wins on field precision, nested queries, and type safety. Shipping both lets developers pick the right tool per call instead of forcing one answer for every use case. Strapi, Contentful, Payload, Sanity, DatoCMS all do this; Hygraph is the notable GraphQL-only exception.

What's the N+1 problem in GraphQL CMSes?

When a query asks for a list of items with related data (posts { author { name } }), the resolver typically runs one DB query for posts, then one DB query per post for the author — N+1 queries. The fix is DataLoader (batching loaders that collect all needed authors and run one batched query). Quality of DataLoader integration varies by CMS — Sanity solves it natively via GROQ projection, Contentful has it built-in, Strapi v5 has it, WPGraphQL implements it but the underlying WordPress data model still struggles. See CMS performance benchmarks: what to test for how to catch N+1 in benchmarks.

Can I use REST and GraphQL on the same CMS?

Yes — most modern CMSes support both. You can call REST for one page and GraphQL for another against the same CMS instance. The CMS routes them to the same data layer. The auth tokens are usually shared or at least scoped similarly. Most production projects end up using both selectively rather than committing to one across the whole project.

What's better for SEO, REST or GraphQL?

Neither directly affects SEO — Google doesn't see your CMS API; it sees your rendered HTML. But indirectly, REST's CDN-caching advantage usually helps SEO because faster pages rank better. If your headless setup uses GraphQL without persisted queries and hits origin on every page render, your TTFB suffers, and Core Web Vitals can drop. For the SEO-specific angle, see headless CMS and SEO: what actually matters in 2026.


Sources & Methodology

This post draws on:

  • GraphQL specification (graphql.org/learn) — for the architectural definitions
  • REST architectural style (Roy Fielding's dissertation, 2000) — for the foundational REST principles
  • Apollo Client documentation — for caching patterns and persisted queries
  • CMS API documentation — Sanity (GROQ + GraphQL), Contentful (Content Delivery API + GraphQL), Strapi v5 (REST + GraphQL plugin), Hygraph (GraphQL-only), Payload v3 (Local API + REST + GraphQL), DatoCMS (GraphQL primary)
  • First-hand integration testing UnfoldCMS team has shipped projects on Sanity, Contentful, Strapi, and Payload across 2024-2026; the protocol-quality observations reflect actual production patterns
  • OWASP API Security Top 10 (2023) — for the rate-limiting and security framings

Disclosure: this post is on a CMS vendor's blog. UnfoldCMS today is monolithic-coupled with Inertia (no public REST or GraphQL endpoint). Public API support is on the roadmap; we're transparent that the recommendations above apply to teams shipping with other CMSes for the headless use case. The protocol-comparison framework is platform-agnostic — every CMS that supports both REST and GraphQL faces the same trade-offs.

For deeper coverage of related dimensions, see the headless CMS evaluation checklist, TypeScript-first CMS platforms: why type safety matters, CMS performance benchmarks: what to test, and headless CMS security: why decoupled is safer.

Share this post:

Discussion

Comments (0)

Leave a Comment

Please log in to leave a comment.

Don't have an account? Register here

No comments yet. Be the first to share your thoughts!

Keep Reading

Related Posts

Back to all posts