REST vs GraphQL
With a few interviews under my belt, I can say with some certainty that I need to be prepared to describe the differences between a REST API and GraphQL! Throughout my time at Turing, I mostly worked with RESTful APIs, using a ton of different free and open APIs to develop vanilla javascript and React apps. I also got some experience with GraphQL as part of the final capstone project. The capstone project is a 3 week, full-stack group project. My team was comprised of three BE and two FE engineers (counting myself). Our final product, HiveMind (repo link), is a reddit-type social media application for beekeepers. GraphQL/Apollo Client was new technology for all parties involved, which added a lot of unknowns to our development process. Turns out it's hard to plan a JSON contract and data structure when no one knows what they don't know yet!
So let's get down to business, what is a REST API?
REST stands for Representational State Transfer and it is a set of architectural constraints for creating APIs. In order for an API to be considered RESTful:
- requests must be managed through HTTP or HTTPS
- the communication between the client and server must be stateless, which means that no client information is stored on the server between requests and each request takes place as if it were the first time -the API can return messages in a variety of formats: HTML, XML, plain text, and JSON -the response is cacheable, which means the client cache has the right to reuse the response data. In React, his might look like storing the fetched data in state.
The most important element of a REST API is a standard, uniform interface, a blueprint for what the API looks like, if you will. So what architectural constraints contribute to the uniform interface of a REST API?
Resources are accessed via URIs that are unique to the resource returned, for example: example.com/users might return a list of all users of an application whereas example.com/users/4 would return the user with an id of 4. In other words, specific endpoints point to specific resources. Not too many endpoints, and not too few. On the backend, REST architecture seems to be a balancing act between combining related information into larger resources, but not adding in too much data unnecessary data.
REST API are imperative, which essentially means that in order to interact with them you need to provide each and every step along the way to getting what you want. This might look like writing a fetch call, adding headers and parameters, checking the response for errors, parsing the body text with response.json()
and adding loading and error states to your React apps.
One of the biggest downsides of a REST API is that because of this endpoint set-up you may end up over- or under-fetching data on the front end. This is the problem that GraphQL set out to solve!
Okay, I'm interested, tell me more about GraphQL
In contrast to a REST API where resources are accessed via a unique URI, all GraphQL requests go to the same URI. You specify which resource(s) you would like on the FE, which gives you the power to request everything you need in only one request.
GraphQL uses a declarative programming approach. Declarative means that you just describe the data requirements but you don't really care what happens under the hood to get it to you. When you use Apollo Client on the front-end, a lot of the imperative steps from above are built into the useQuery hook.
While it sounds pretty magical, using Apollo Client in your React app for the first time is quite a shift from typical fetch calls. Luckily the documentation is really helpful! Once you have initialized a client in index.js, you wrap your entire React App in the ApolloProvider component similarly to how you would wrap it with the Context.Provider or BrowserRouter. This means that anywhere in your app, in any component, you can make requests to the client! Cool!
Still in index.js, you then define the queries and mutations you want to execute. For a user with id of 23, check out this syntax:
export const GET_USER = gql`
query getUser {
user(id: 23) {
id
username
region
biography
avatar
}
}
`;
Remember that if you wanted to you could query just some of this information:
export const GET_USER = gql`
query getUser {
user(id: 23) {
id
username
avatar
}
}
`;
Or add to it!
export const GET_USER = gql`
query getUser {
user(id: 23) {
id
username
region
biography
avatar
posts {
id
title
imageUrls
description
upvotes
downvotes
createdAt
}
}
}
`;
Querying and using your data is a simple as importing the useQuery hook into the relevant component and calling it as such:
const { loading, error, data } = useQuery(GET_USER)
POST requests are called Mutations in GraphQL and if you need to refetch data after creating a mutation, it couldn't be easier:
const [createPost] = useMutation(ADD_POST, {
refetchQueries: [GET_ALL_POSTS]
});
const addPost = (e)=> {
e.preventDefault();
if (!postTitle || !postDescription) {
validateForm();
} else {
createPost({
variables: {
input: {
title: postTitle,
description: postDescription,
imageIds: signedIds,
userId: Number(user.id)
}
}
});
closeModal(e);
clearState();
}
};
So which should you choose?
GraphQL/Apollo Client simplifies the process of making requests to an API, limits unnecessary requests to the server (potential for over- or under-fetching), adds built in error and loading states, and makes app architecture easy with the ApolloProvider wrapper.
One major downside that we ran into in the capstone project is that a GraphQL server always returns a 200 status code, which makes end-to-end testing with Cypress very challenging for 404 and 500 errors. The Cypress documentation on stubbing and intercepting GraphQL requests is truly abysmal, particularly if you need to stub or intercept a request with parameters, and the process (when you do eventually figure it out) is quite clunky. A major contributing factor to the difficulty of stubbing/intercepting these requests is that all requests go to the same URI so you can't rely on the simplicity of changing the endpoint in your tests.
REST is a tried-and-true option with built-in caching and authentication capabilities and is easy to use with software like Cypress. A good REST API will return different status codes and helpful error messages (this is not always the case, but that's the subject of a future blog). Having defined endpoints makes it easy to stub and intercept requests in Cypress. Since I'm not a BE engineer, I'll leave some of the security comparisons to the experts:
In terms of GraphQL vs. REST security, the credit seems to be leaning towards the latter’s side. REST provides several inherent ways to enforce the security of your APIs.
For example, you can ensure REST API security by implementing different API authentication methods, such as via HTTP authentication, where sensitive data is sent in HTTP headers, via JSON Web Tokens (JWT), where sensitive data is sent as JSON data structures, or via standard OAUth 2.0 mechanisms.
While GraphQL also provides some measures to ensure your APIs’ security, they are not as mature as those of REST. For example, although GraphQL assists in integrating data validation, users are left on their own to apply authentication and authorization measures on top. This often leads to unpredictable authorization checks, which may jeopardize the security of GraphQL-based apps.
Call me old fashioned, or maybe a control freak, but on the whole I would prefer to work with a REST API because I like a more declarative approach and easier testing. I have, however, worked on projects in the past where I had to make multiple requests in order to gather all of the data that I needed, or had to "clean out" large amounts of un-used data before setting it in state unnecessarily, so I do understand the benefits of GraphQL.
As with all things, I'm open to having my mind changed, so please leave a comment if you feel strongly one way or the other!