Drupal as a Backend: GraphQL, JSON:API, RESTful, and the Expensive Mistake Hiding in the API Choice
A CTO once asked me, halfway through a decoupled Drupal planning meeting, “So which API should we use?”
The room went quiet for a second. Frontend wanted GraphQL. Backend wanted JSON:API. One integration vendor had already assumed REST. The product owner just wanted the mobile app to stop waiting on website releases.
That small question usually sounds technical. It isn’t. It is a governance question, a budget question, and sometimes a hiring question wearing a developer hoodie.
Drupal can be a very capable backend for decoupled products. It already has structured content, roles, permissions, workflows, revisions, translations, media handling, taxonomy, and a mature module ecosystem. The awkward part is deciding how that content leaves Drupal. Core Drupal has native JSON:API support, GraphQL is available through a contributed module, and Drupal’s RESTful Web Services module remains an option for custom resource-style endpoints. Drupal’s own decoupled documentation states the split plainly: JSON:API is in core, GraphQL is contributed, and Drupal can expose content to an external frontend through APIs.
And yet teams still choose badly.
They choose GraphQL because it sounds modern. They choose REST because everyone has used it. They choose JSON:API because it is there. None of those are strategies.
Here is the sharper version: for most Drupal-backed content platforms, JSON:API should be the default starting point. GraphQL should be earned. REST should be reserved for the cases where the other two are too broad or too opinionated.
That will annoy some people. Good.
Start with the boring winner: JSON:API
JSON:API is boring in the best possible way.
Drupal’s core JSON:API module implements the JSON:API specification for Drupal entities. It gives a zero-configuration, opinionated way to expose CRUD operations for site content, and it is tied into Drupal’s Entity API, Field API, caching, authentication, and authorization systems.
That sentence contains the part executives should care about: core module.
Core matters. Core usually means fewer vendor surprises, easier maintenance planning, a clearer security posture, and less “we need the one contractor who understands the custom API layer” risk. Drupal’s JSON:API documentation says the module exposes APIs around entity types and bundles, supports filtering, includes, pagination, sorting, revisions, translations, GET, POST, PATCH, DELETE, file uploads, and resource customization through events.
A practical example: imagine a media company with articles, authors, topics, landing-page teasers, and images. The frontend team needs article pages in Next.js. The mobile app needs the same article body, hero image, author name, and related topics. JSON:API can expose those Drupal entities without making the backend team invent an API language from scratch. Related resources can be embedded through includes, and collections can be filtered or paginated.
Is the payload pretty? Sometimes. Sometimes it looks like a committee designed it after a long lunch.
But that is also the point. JSON:API is a specification, not a developer’s personal taste. The official JSON:API site describes it as a specification for building APIs in JSON, with shared conventions that reduce arguments over response shape and help teams use general tooling. Its media type is application/vnd.api+json, and version 1.1 was finalized on September 30, 2022.
There is a hidden management benefit here. A standardized response format means onboarding is less tribal. A new frontend developer may still curse the nested data, attributes, and relationships structure. Fine. At least they are cursing something documented.
The catch nobody wants to mention
JSON:API exposes Drupal’s content model pretty directly. That can be a strength, especially when Drupal is the editorial source of truth. It can also leak too much of Drupal’s internal shape into the frontend.
If your content model is clean, JSON:API feels efficient. If your content model is a museum of old compromises, JSON:API makes the mess visible.
I’ve seen “Article” content types with fields like field_new_body_2021, field_mobile_summary, field_legacy_related, and field_do_not_use. JSON:API does not magically turn that into a graceful product API. It exposes the model you actually have. A little cruel, maybe. Useful though.
Drupal’s documentation also makes a boundary clear: JSON:API handles entity-oriented CRUD, but business rules such as login, password reset, and account creation are outside JSON:API’s job. If your application needs domain actions like “approve vendor,” “calculate renewal quote,” or “submit claim,” do not force those into content-entity endpoints just because the module is convenient.
That path gets ugly fast.
GraphQL feels elegant because it moves the pain
GraphQL is seductive. The frontend asks for exactly the fields it wants. The response has the same shape as the query. It has a typed schema. It feels designed for component-based frontends because, frankly, it was. The official GraphQL site describes GraphQL as an open-source query language and server-side runtime for APIs with a strongly typed schema, and it shows the client asking for selected fields rather than receiving a fixed server response.
For frontend teams, that is a real improvement.
Take a homepage made of components: hero, featured articles, events, sponsor slots, navigation, regional alerts. With REST, the frontend may call several endpoints. With JSON:API, it may request a collection and include related resources, but the structure still follows entity relationships. With GraphQL, the query can look closer to the screen. That matters when teams ship product surfaces quickly.
Drupal’s GraphQL module lets developers craft and expose a GraphQL schema for Drupal 10 and 11. It is built around webonyx/graphql-php, supports the official GraphQL specification, includes GraphiQL at /graphql/explorer, and gives developers data producer plugins plus custom code for schema work.
Read the wording carefully: “craft and expose.”
The current Drupal GraphQL module is not a magic mirror that simply reflects every Drupal field in a neat API. Drupal.org says the older 3.x version automatically generated a schema from Drupal entities and exposed Drupal details through the API, while 4.x leaves schema design to the developer so Drupal internals can be hidden. The same page says 4.x requires the developer to set up and map the schema.
That is not a footnote. That is the invoice.
GraphQL can give you a clean contract between product and content storage. But someone has to design that contract. Someone has to maintain it when editors add fields, when the design system changes, when personalization arrives, when legal asks for regional consent logic, when the mobile team needs offline sync.
Actually, that is too polite. Someone has to own it. Ownership is the missing line item in many GraphQL proposals.
When GraphQL is worth the cost
GraphQL makes sense when the API is a product in its own right.
A large university with dozens of microsites, a student mobile app, digital signage, faculty profiles, course catalogs, and a design system may benefit from a GraphQL layer. The consumers are varied. The content graph is deep. The frontend teams need selective access. The organization may want an API contract that hides the eccentricities of Drupal bundles.
GraphQL also fits when Drupal is only one source among several. Maybe content comes from Drupal, prices from an ERP, availability from a booking engine, and user entitlements from a CRM. GraphQL can present a coherent schema to clients while resolvers fetch from different systems. The GraphQL model was built around a strongly typed schema and client-specified response shape, which is exactly why it works well in this kind of composition layer.
But for a normal marketing site with a headless frontend? I would be skeptical. Mildly annoyed, even.
If the real need is “React should read articles from Drupal,” GraphQL may be a costume party. You will spend architecture money to avoid learning JSON:API includes and filters. Worse, you may create a bespoke API layer that only two developers understand.
Drupal’s GraphQL project page reports that stable releases are covered by Drupal’s security advisory policy and lists recent releases for Drupal 10 and 11, including 8.x-4.14 released on April 29, 2026 and a 5.0.0 beta for Drupal 10.4 and 11. That is healthy. It does not remove the design work.
A contributed module can be mature and still be the wrong default.
RESTful Web Services: old, useful, and usually overused
REST has become one of those words that means whatever the speaker needs it to mean. Sometimes it means clean resource-oriented HTTP. Sometimes it means “we return JSON from a controller.” Sometimes it means “the vendor gave us an endpoint and a PDF.”
Roy Fielding’s dissertation introduced REST as an architectural style for distributed hypermedia systems, with constraints such as client-server separation, stateless communication, cache, a uniform interface, layered systems, and optional code-on-demand. That original idea is stricter than much of what gets called REST in project plans.
Drupal’s RESTful Web Services module is useful because it can expose resources with specific methods, serialization formats, and authentication. Drupal study material describes REST resources for entities, support for methods such as GET, POST, PATCH, and DELETE, JSON or XML serialization, and authentication through Basic Auth, cookies, or other modules such as OAuth. It also notes that unsafe methods require an X-CSRF-Token request header.
This makes REST good for narrow integration work.
A payment provider posts transaction status to Drupal. A warehouse system pulls a list of updated product manuals. A partner portal needs one carefully shaped endpoint for approved assets. A legacy mobile app already expects /api/v1/articles/{id} and cannot be rewritten this quarter.
REST gives you control. It also gives you blank-page syndrome.
Unlike JSON:API, where conventions are already chosen, a custom REST endpoint needs decisions: URL shape, response format, error format, pagination style, filtering syntax, versioning, authentication, cache headers, documentation, deprecation policy. OpenAPI can help document that surface, and the OpenAPI Specification is a standard, language-agnostic way to describe HTTP APIs so humans and computers can understand them without reading source code.
Still, you have to make the choices.
And those choices stick. A sloppy endpoint designed in week two can haunt the product for years because some app version in the wild still depends on it.
The decision is not “which API is best?”
That framing is lazy. Sorry, but it is.
The better question is: what kind of coupling can your organization afford?
JSON:API couples consumers to Drupal’s entity model. That is often acceptable when Drupal is the main content system and the frontend is a replacement presentation layer. The reward is speed and lower backend invention.
GraphQL couples consumers to a schema designed by your team. Done well, that schema hides Drupal and expresses the product domain. Done badly, it becomes a second CMS model maintained in code.
REST couples consumers to custom endpoints. That is excellent for specific integrations and dangerous for broad content delivery, because every endpoint becomes a miniature product with its own maintenance tail.
Here is the version I would put in front of an executive steering group.
| Situation | My recommendation | Why |
|---|---|---|
| Headless website or app reading Drupal content | Start with JSON:API | It is in Drupal core, exposes entities quickly, and uses Drupal permissions and caching. |
| Many frontends need different shapes of the same content | Consider GraphQL | Clients can request selected fields through a typed schema, but Drupal GraphQL requires schema design and mapping. |
| Partner integration or one-off system connection | Use REST | Custom REST resources can be shaped around the integration and controlled by method, format, and authentication. |
| Drupal model is messy but cannot be cleaned soon | GraphQL or custom REST may protect consumers | A designed API can hide internal fields, though it adds ownership and maintenance cost. |
| Small team, tight deadline, mostly content delivery | JSON:API | Less custom API code usually means fewer surprises. Drupal’s JSON:API is zero-configuration and entity-aware. |
Tables are a bit sterile, but this one saves meetings.
What project managers should watch
The API choice changes the project plan more than people admit.
With JSON:API, the critical path is content modeling. If the content model is thoughtful, the API work can move quickly. If the content model is chaotic, frontend development slows down because every query exposes another editorial compromise. Put senior attention on field naming, reusable paragraph patterns, media relationships, taxonomy, and permissions before the frontend team starts wiring pages.
With GraphQL, the critical path is schema ownership. Who designs the schema? Who reviews breaking changes? Who writes resolvers? Who handles performance problems like nested queries that trigger too many backend loads? The Drupal GraphQL module gives tools and extension points, including data producer plugins and GraphiQL, but it does not remove the need for backend engineering discipline.
With REST, the critical path is contract discipline. Every endpoint needs documentation, tests, authentication rules, error semantics, and a versioning story. REST looks cheap when the first endpoint is built. The fifth endpoint tells the truth.
One more thing. Authentication is often underestimated. Drupal JSON:API is tied to Drupal’s authentication and authorization systems, and REST can use authentication providers such as Basic Auth and cookies, with OAuth commonly added through contributed modules. The Lupus Decoupled Drupal documentation points to Simple OAuth as a way to authenticate API requests by token in decoupled setups.
Do not leave that for the sprint before launch. That sprint is already cursed.
Performance: the argument everyone simplifies
GraphQL fans say it avoids over-fetching. True. JSON:API fans say includes reduce round trips. Also true. REST fans say HTTP caching is straightforward. True again.
Now the unpleasant part: all three can be fast, and all three can be slow.
GraphQL can reduce payload size, but complex nested queries can punish the backend if resolvers are careless. JSON:API can use Drupal’s response caching and includes, but payloads may be verbose. REST can map neatly to HTTP caching, but custom endpoints often forget cacheability until performance testing starts. Fielding’s REST constraints include cache because cache can reduce network traffic and latency, but stale data and cache invalidation remain real trade-offs. Drupal’s JSON:API documentation explicitly ties the module to Drupal response caching, while the GraphQL module is categorized under Decoupled, Developer tools, and Performance on Drupal.org.
So, no, GraphQL is not automatically the “performance option.”
The first performance win is usually better content modeling. The second is cache strategy. The third is avoiding silly client behavior, like calling the same endpoint twelve times because nobody wrote a data access layer.
Glamorous? No. Effective? Yes.
A blunt selection framework
If I were advising a founder or CTO, I would start with this sequence.
First, use Drupal’s normal rendered pages unless you have a reason to decouple. Decoupling adds hosting, preview, routing, cache, deployment, authentication, and debugging complexity. Drupal’s own decoupled documentation separates standard Drupal from decoupled Drupal by noting that layout moves to the frontend and the backend exposes content through APIs. That shift is not free.
Second, if you are decoupling mainly for a modern frontend, start with JSON:API. Build one real page, not a demo. Include media, menus or navigation, related content, preview needs, authentication if required, and cache behavior. You will learn more from one ugly page than from six architecture diagrams.
Third, move to GraphQL only when the consumer experience justifies the schema work. Good reasons include multiple frontend consumers, a need to hide Drupal internals, complex content graphs, or aggregation across systems. Bad reasons include “our frontend developer likes Apollo” and “the investor deck says GraphQL.”
Fourth, use REST for integrations that need tight boundaries. A custom endpoint for a partner export can be clean. A whole content platform made of hand-rolled endpoints can become a drawer full of unlabeled keys.
And clean up the Drupal model. Please. No API layer can fully rescue a content model that nobody owns.
The politics of “future-proof”
Executives like the phrase “future-proof.” I understand why. Nobody wants to approve a backend rebuild and hear, eighteen months later, that the chosen API painted the company into a corner.
But future-proofing is usually less about picking the fanciest API and more about choosing where change is allowed to happen.
JSON:API says, “Drupal’s model is the contract.” That is honest and fast.
GraphQL says, “Our product schema is the contract.” That is powerful and expensive.
REST says, “This endpoint is the contract.” That is precise and easy to multiply into a maintenance problem.
There is no universal winner. There is only fit.
My bias is simple: start with the least custom thing that can support the product. In Drupal, that usually means JSON:API. Add GraphQL when you can name the owner and the budget. Add REST when the integration is specific enough to deserve its own doorway.
The worst choice is the one made for fashion, because fashion never shows up for maintenance.