The following table compares plain REST, JSON:API, OData (version 4.01), GraphQL and PartiQL from the point of view of what is included in the standard specs. It is not intended to be an ultimately complete comparison, but rather to show the technical differences at a glance. The features compared are those, I find important when building web API for business applications - see my previous article about common requirement for business web services.
NOTE
A checkmark means, that there is a specification for the respective feature.
On the other hand, no checkmark does not mean, the feature is not supported or not possible - it rather means, that you will need to find your own way to implement it!
Of course, comparing all these different approaches to Web APIs is not quite fair. Please keep in mind, that the following table is not inteded to determine a score-based winner technology: it should rather show how the different approaches affect typical tasks. So before we start, let's quickly summarize the differen paradigms:
- RESTful and REST-like APIs are the most common type of web service today. The key idea is having a URI for every resource and sending PUT/GET/POST/DELETE HTTP-requests for CRUD operations. The payload can be anything, but JSON and XML are most popular.
- RPC follows another idea: the client performs remote function calls as-if it were local functions. This reduces the overhead and is often prized as more peformant. On the other hand, calling function directly requires the client to have deep knowledge about the server logic and data structures. There are numerous RPC implementations using different transport layers - not only HTTP.
- GraphQL attempts to to take the best from both worlds: the client performs queries and mutations, that are essentially similar to RPCs, but the data is well structured and object-based. Introspection queries enable the client to explore data structures offered by the server. The main selling point is the possibility to tell the server exactly, what data is expected. However, while data structures are standardized, there is no specification for query parameters like filters, sorting, etc. GraphQL is theoretically agnostic to the transportation layer, but HTTP is most widely used.
- PartiQL is an SQL version, that allows to query unstructured resources too (not only tables, but also JSON, XML, documents, etc.). It's USP is being compatible to SQL and thus allowing very complex queries.
REST | JSON:API | OData v4 | GraphQL | PartiQL | SOAP | JSON-RPC | gRPC | |
---|---|---|---|---|---|---|---|---|
General | ||||||||
Origin | Roy Fielding (dissertation) | Community | Microsoft, OASIS | Amazon | ||||
Data Format | Any | JSON | JSON or XML | JSON | JSON | XML | JSON | Protocol Buffers |
Paradigm | REST | REST | REST | Proprietary | SQL | RPC | RPC | RPC |
Query language | URL parameters | URL parameters | URL parameters | Proprietary, JSON-like | SQL | Function arguments | Function arguments | Function arguments |
HTTP only | Yes | Yes | Yes | No | No | No | Yes | No |
Reading business objects | ||||||||
Filtering |
✔ | ✔ | ✔ | ✔ | ||||
Filtering with comparison operators |
✔ | ✔ | ||||||
Filtering with logical operators |
✔ | ✔ | ||||||
Filtering over related data |
✔ | ✔ | ✔ | |||||
Searching (google-like) | Via separate URL | ✔ | Via separate query | Via SQL | ||||
Pagination via offset | ✔ | ✔ | ✔ | ✔ | ||||
Pagination via cursor/index | ✔ | ✔ | ||||||
Pagination via URLs in response | ✔ | ✔ | ||||||
Sparse field sets (specify, which fields are needed in the result) |
✔ | ✔ | ✔ | ✔ | ||||
Nested data in response (e.g. positions for every order) |
✔ | ✔ | ✔ | ✔ | ||||
Inner joins (e.g. order data for every position) |
Via nested data | ✔ | Via nested data | ✔ | ||||
Cross joins (e.g. product data with prices from every price list) |
Via separate URL | ✔ | Via separate query | ✔ | ||||
Aggregation (e.g. sum of quantities per order) |
With extension | ✔ | ||||||
Writing business objects | ||||||||
Built-in create, update, delete | ✔ | ✔ | ✔ | Must create mutations for every operation | ||||
Nested data | ✔ | ✔ | ✔ | |||||
Partial upates | ✔ | ✔ | ✔ | |||||
Mass update (change a field for all items matching a filter) |
✔ | Via separate mutation | ||||||
Mass delete (delete all items matching a filter) |
✔ | |||||||
Optimistic locking | Via HTTP headers | Via HTTP headers | Via HTTP headers | |||||
Pessimistic locking | ||||||||
Function calls (not based on objects) | ||||||||
Function calls via HTTP message | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Function calls via proxy-objects in client-langauge | ✔ | ✔ | ||||||
Simple (scalar) parameters | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Complex (compound) parameters (e.g. business objects or other typed structures) |
✔ | ✔ | ✔ | ✔ | ||||
Other features | ||||||||
File upload | ✔ | Via third-pary addon spec | ||||||
File download | ✔ | |||||||
Advanced HTTP headers (e.g. If-Match, Retry-After, custom headers, etc.) |
✔ | |||||||
Batch requests (multiple independant operations in a single request) |
✔ | ✔ | Via streaming | |||||
Metadata and schemas | ||||||||
Schema document | via OpenAPI | via OpenAPI | XML | Introspection-queries | ✔ | via OpenAPI | ||
Objects | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Attributes | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Relations | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Built-in data types (number, string, etc.) | ✔ | ✔ | ✔ | ✔ | ✔ | |||
Custom data types | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ||
Functions and their parameters | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | |
Schema queries / introspection | ✔ | |||||||
Schema browser (GUI) | via OpenAPI | via OpenAPI | Via third-party apps | Via third-party apps | via OpenAPI | |||
Error handling | ||||||||
Error structure specification | ✔ | ✔ | Only query validation errors | ✔ | ✔ | ✔ | ||
Error codes | HTTP respnse status | HTTP respnse status | HTTP respnse status | ✔ | ✔ | ✔ | ||
Multiple errors per request/call | ✔ | ✔ | ✔ | |||||
Non-critical errors (error/warning messages sent along with response data) |
✔ |