GraphQL vs. REST
🎥 This article is based on our recent video where we explore the pros and cons of GraphQL versus REST Should you use GraphQL?
What makes GraphQL different?
GraphQL is a protocol to access data. It solves a similar set of problems as REST but comes with a different set of trade-offs. More flexible queries, built-in typed schema, a different approach to security, and subscriptions are the key difference points that make GraphQL powerful and more complex than REST.
Flexible queries
Unlike REST, GraphQL places more control over the shape and content of responses in the hands of an API consumer. With REST, the shape of a response is typically fixed for each API endpoint. Adding an extra property to a response requires changes to the API backend and would affect all API consumers.
const filmId = '2baf70d1-42bb-4437-b551-e5fed5a87abe'
await fetch(`https://ghibliapi.herokuapp.com/films/${filmId}`)
.then(r => r.json())
// {
// "id": "2baf70d1-42bb-4437-b551-e5fed5a87abe",
// "title": "Castle in the Sky",
// "original_title": "天空の城ラピュタ",
// ... 14 more properties ...
// }
NOTE: The examples in this article use the public Studio Ghibli API (both REST and GraphQL).
With GraphQL, every API call requires an explicit query specifying the shape of the response. Thus, every response could have a different shape, and adding an extra property is trivial if this property is available in the GraphQL API at all.
const query = /* GraphQL */ `
query FilmQuery ($filmId: ID!) {
film(id: $filmId) {
title
original_title
vehicles {
name
description
}
}
}
`
const filmId = '2baf70d1-42bb-4437-b551-e5fed5a87abe'
await fetch('https://ghibliql.herokuapp.com', {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify({
query,
variables: {filmId},
}),
}).then(r => r.json())
// {
// "data": {
// "film": {
// "title": "Castle in the Sky",
// "original_title": "天空の城ラピュタ",
// "vehicles": [
// {
// "name": "Air Destroyer Goliath",
// "description": "A military airship utilized by the government to access Laputa"
// }
// ]
// }
// }
// }
Typed API schema
Both REST and GraphQL allow you to create typed API schemas. However, with REST, a schema is an optional addition, similar to API documentation. Good APIs would have it, but creating it requires using separate tooling and formats like OpenAPI. With GraphQL, the schema is a required part of any API, and it can be introspected using the GraphQL API itself.
This leads to better tooling support - most GraphQL APIs allow using the same standard GraphiQL tool both as an API explorer and as a playground. Additionally, most IDEs have plugins and extensions that automatically introspect GraphQL APIs and use the extracted schema for auto-completion, inline documentation, and build-time type checking.
Figure 1: using GraphiQL to explore a GraphQL API
Approach to security
Both REST and GraphQL have ways to secure access to the API, though there is no single standard for either of them.
With REST, the “natural” granularity level for access control rules is “per endpoint per action type”. For example, “reading the film data (by sending a GET
to the /films/:id
endpoint) requires a USER
role”. This approach fits well to the REST model, it is straightforward to implement, and so it is commonly used. Implementing it could look something like ⬇️ (using express.js
in this example).
app.get('/film/:id', [
auth.hasValidJwt,
auth.hasRole('USER'),
FilmsController.getById
]);
With GraphQL, the “natural” granularity level for access control rules is “per field”. For example, “reading any film properties (by querying the Query.film.*
fields) requires a USER
role”.
This security model is more flexible and simultaneously more complex, as it allows the creation of multiple overlapping rules. For example, both Query.film.*
and Film.*
address the same title
field on the Film
entity. Though, the former only applies when fetching the film
query field whereas the latter applies in any query.
The exact implementation heavily depends on the specific GraphQL server technology. In StepZen adding an access control rule like that would look like ⬇️
access:
policies:
- type: Query
policyDefault:
condition: 'false'
rules:
- condition: '$jwt.`CUSTOM/roles`: String has "USER"'
fields: ['film']
Subscriptions
Subscribing to changes in server-side data and receiving events when it changes, in near real-time, – doing something like that with REST is complicated. On the other hand, GraphQL with its “subscriptions” has first-class support for it.
In GraphQL subscriptions can be thought of as “standing queries”. When sending a regular GraphQL query a client gets a response once, whereas when sending a subscription GraphQL query it gets a response whenever the GraphQL server detects a change in the underlying data. This is possible because GraphQL supports the stream-oriented web socket protocol and the message-oriented HTTP, and a GraphQL server accepts connection on both https:
and wss:
. When using subscriptions, GraphQL clients open a web-socket connection to the server and generate events whenever a new message comes through.
When to choose REST over GraphQL
GraphQL has many useful features, but they come at a cost. Building a GraphQL API backend is typically more complex than building a REST API. Choosing REST is a good idea if the extra complexity of GraphQL is not needed or is not justifiable.
- Client pages map 1-to-1 to REST endpoints. This is typically the case for CRUD UIs such as admin panels.
- The client and the server are built by the same team. In this case, developing the client and server in parallel is a natural process, and there is an extra benefit of added flexibility.
- There is no team to own the GraphQL API. The benefits of a GraphQL API materialize over a long time period, but building it may require a larger initial investment than building a REST API. Especially when the set of underlying data sources is large. It may be unwise to embark on a GraphQL journey if there is no team familiar with building GraphQL APIs. This is where using an external solution like StepZen can help.
When to choose GraphQL over REST
- Client pages have varying data needs, and they do not map well to REST endpoints. The flexibility that GraphQL gives to client developers helps to keep network communication efficient and yet avoid creating unique REST endpoints for each use case.
- The server and the client are built by different teams. In this case uncoupling the two teams may have a notable impact on the development velocity, enabling the client team to deliver faster. This depends on the organization's structure and culture. A good hint here is whether or not the client and server teams have a shared backlog and priorities.
- There is a need for an API federation layer. In a system where clients need to access data from several different APIs and data sources, a common architectural pattern is creating an API layer. It reduces the number of connections and interdependencies, making the system more robust. GraphQL is a good fit for this use case.
- The client needs to get notified when data changes on the server side. Heavily event-driven apps may be better off using more specialized protocols like gRPC. GraphQL is a good fit for clients that have mixed needs: both regular one-off queries and subscriptions (a.k.a. standing queries).
Summary
Building a GraphQL API backend can be more complex than building a REST API. Choosing REST is a good idea if the extra complexity of GraphQL is not needed or is not justifiable.
However, as the complexity of an application or the development organization increases, the benefits of GraphQL start to come into focus. For example, if the requirements for the API layer cannot be fulfilled with REST, then consider GraphQL.
We'd love to hear your feedback on this blog, on GraphQL, on REST, or on StepZen. Questions? Comments? Ping us on Twitter or join our Discord community to get in touch.
cover image credits: DALL·E [openai.com]