You Don't Need GraphQL
If you've heard of GraphQL, you've heard it's bold claim that it is better than other REST APIs. But perhaps it has met its open-source match.
Join the DZone community and get the full member experience.
Join For FreeGraphQL is a technology that seems to be getting a lot of attention in the developer community at the moment. Advocates describe it as "a better REST," claiming that it offers several advantages over traditional REST APIs:
- Single request with nested results vs. multiple separate requests
- Single endpoint for all requests vs. one endpoint per resource
- Single evolving version vs. multiple (presumably incompatible) versions
For example, the following GraphQL query might be used to retrieve an employee record from a hypothetical service based on the MySQL "employees" sample database. In addition to the employee number, first name, and last name, the query also requests the employee's title and salary history:
{
employee(id: 10004) {
employeeNumber
firstName
lastName
titles {
title
fromDate
toDate
}
salaries {
salary
fromDate
toDate
}
}
}
The response might look something like this, with some results omitted for brevity:
{
"employeeNumber": 10004,
"firstName": "Chirstian",
"lastName": "Koblick",
"titles": [
{
"title": "Senior Engineer",
"fromDate": 817794000000,
"toDate": 253370782800000
},
...
],
"salaries": [
{
"salary": 74057,
"fromDate": 1006837200000,
"toDate": 253370782800000
},
...
]
}
A RESTful Implementation
The data model for the sample database is shown below:
A typical REST API might provide access to employee, title, and salary resources as follows:
GET /employees/10004
{
"employeeNumber": 10004,
"firstName": "Chirstian",
"lastName": "Koblick"
}
GET /employees/10004/titles
[
{
"title": "Senior Engineer",
"fromDate": 817794000000,
"toDate": 253370782800000
},
...
]
GET /employees/10004/salaries
[
{
"salary": 74057,
"fromDate": 1006837200000,
"toDate": 253370782800000
},
...
]
This is indeed more verbose than the GraphQL version. However, there is nothing preventing a REST API from providing a similar interface.
For example, the following service method (implemented using the open-source HTTP-RPC framework) returns the same information as the GraphQL query. As with the GraphQL version, all of the data is obtained with a single request:
@RequestMethod("GET")
@ResourcePath("?:employeeNumber")
public void getEmployee(List<String> details) throws SQLException, IOException {
String employeeNumber = getKey("employeeNumber");
Parameters parameters = Parameters.parse("SELECT emp_no AS employeeNumber, "
+ "first_name AS firstName, "
+ "last_name AS lastName "
+ "FROM employees WHERE emp_no = :employeeNumber");
parameters.put("employeeNumber", employeeNumber);
try (Connection connection = DriverManager.getConnection(DB_URL);
PreparedStatement statement = connection.prepareStatement(parameters.getSQL())) {
parameters.apply(statement);
try (ResultSet resultSet = statement.executeQuery()) {
ResultSetAdapter resultSetAdapter = new ResultSetAdapter(resultSet);
for (String detail : details) {
switch (detail) {
case "titles": {
resultSetAdapter.attach("titles", "SELECT title, "
+ "from_date AS fromDate, "
+ "to_date as toDate "
+ "FROM titles WHERE emp_no = :employeeNumber");
break;
}
case "salaries": {
resultSetAdapter.attach("salaries", "SELECT salary, "
+ "from_date AS fromDate, "
+ "to_date as toDate "
+ "FROM salaries WHERE emp_no = :employeeNumber");
break;
}
}
}
getResponse().setContentType("application/json");
JSONEncoder jsonEncoder = new JSONEncoder();
jsonEncoder.writeValue(resultSetAdapter.next(), getResponse().getOutputStream());
}
} finally {
getResponse().flushBuffer();
}
}
The initial query retreives the employee's number, first name, and last name from the "employees" table. Subqueries to return the employee's salary and title history are optionally attached based on the values provided in the details
parameter. Column aliases are used in all of the queries to make the field names more JSON-friendly.
Callers can access the API via a standard HTTP GET
request, as shown below:
GET /employees/10004?details=titles&details=salaries
{
"employeeNumber": 10004,
"firstName": "Chirstian",
"lastName": "Koblick",
"titles": [
{
"title": "Senior Engineer",
"fromDate": 817794000000,
"toDate": 253370782800000
},
...
],
"salaries": [
{
"salary": 74057,
"fromDate": 1006837200000,
"toDate": 253370782800000
},
...
]
}
Additional Observations
GraphQL advocates tout its single-endpoint model as a major advantage over REST. This capability is not exclusive to GraphQL — it is certainly possible for REST APIs to be implemented using a single endpoint as well. However, such a service would probably become untenable very quickly. A collection of independent endpoints, each of which represents a specific resource or set of resources, will most likely be much more manageable in the long run.
Further, the concept of a single evolving version is not unique to GraphQL. Implementing a successful versioning strategy is difficult, and there are many ways of approaching it. However, there is nothing to preclude a REST service from providing backwards compatibility. It is simply one option among many.
Finally, adopting GraphQL requires services to be completely re-implemented using the GraphQL library. For any non-trivial application, this would most likely be a major undertaking. Additionally, it forces clients to use GraphQL as well, rather than standard HTTP operations such as GET
and POST
. This means that GraphQL APIs also can't be tested as easily in a web browser or using command-line utilties such as curl
.
So, while there are certainly a number of compelling reasons to consider GraphQL, you don't actually need to use GraphQL to take advantage of them.
For more information on HTTP-RPC, see the project README.
Opinions expressed by DZone contributors are their own.
Comments