GraphQL offers performance benefits for most applications. By reducing round-trips when fetching data, lower the amount of data we are sending back, and make it easier to batch data lookups. Since GraphQL is often built as a stateless request-response pattern, scaling our app horizontally becomes much easier. In this section, we will dive into some benefits that Apollo Server brings to our app, and some patterns for speeding up our service.
Rest endpoints often return all of the fields for whatever data they are returning. As applications grow, their data needs grow as well, which leads to a lot of unnecessary data being downloaded by our client applications. With GraphQL this isn't a problem because Apollo Server will only return the data that we ask for when making a request! Take for example a screen which shows an avatar of the currently logged in user. In a rest app we may make a request to `/api/v1/currentUser` which would return a response like this:
// and so on for every field on this model that our client **could** use
}
```
Contrast that to the request a client would send to Apollo Server and the response they would receive:
```graphql
query GetAvatar {
currentUser {
avatar
}
}
```
```json
{
"data": {
"currentUser": {
"avatar": "/photos/profile.jpg"
}
}
}
```
No matter how much our data grows, this query will always only return the smallest bit of data that the client application actually needs! This will make our app faster and our end users data plan much happier!
Applications typically need to fetch multiple resources to load any given screen for a user. When building an app on top of a REST API, screens need to fetch the first round of data, then using that information, make another request to load related information. A common example of this would be to load a user, then load their friends:
The above code would make at minimum two requests, one for the logged in user and one for a single friend. With more friends, the number of requests jumps up quite a lot! To get around this, custom endpoints are added into a RESTful API. In this example, a `/api/v1/friends/:userId` may be added to make fetching friends a single request per user instead of one per friend.
If we take the above query we may think GraphQL simply moves the waterfall of requests from the client to the server. Even if this was true, application speeds would still be improved. However, Apollo Server makes it possible to make applications even faster by batching data requests.
The most common way to batch requests is by using Facebook's [`dataloader`](https://github.com/facebook/dataloader) library. Let's explore a few options for request batching the previous operation:
The simplest (and often easiest) way to speed up a GraphQL service is to create resolvers that optimistically fetch the needed data. Often times the best thing to do is to write the simplest resolver possible to look up data, profile it with a tool like Apollo Engine, then improve slow resolvers with logic tuned for the way our schema is used. Take the above query, for example:
The above resolver will make a database lookup for the initial user and then one lookup for every friend that our user has. This would quickly turn into an expensive resolver to call so lets look at how we could speed it up! First, lets take a simple, but proven technique:
Instead of fetching each user independently, we could fetch all users at once in a single lookup. This would be analogous to `SELECT * FROM users WHERE id IN (1,2,3,4)` vs the previous query would have been multiple versions of `SELECT * FROM users WHERE id = 1`.
Often times, custom resolvers are enough to speed up our server to the levels we want. However, there may be times where we want to be even more efficient when batching data. Lets say we expanded our operation to include more information:
Assuming that `family` returns more `User` types, we now are making at minimum three database calls: 1) the user, 2) the batch of friends, and 3) the batch of family members. If we expand the query deeper:
We are now looking at any number of database calls! The more friends and families that are connected in our app, the more expensive this query gets. Using a library like `dataloader`, we can reduce this operation to a maximum of three database lookups. Let's take a look at how to implement it to understand what is happening:
After the first data request returns with our current user's information, we execute the resolvers for `friends` and `family` within the same "tick" of the event loop, which is technical talk for "pretty much at the same time". DataLoader will delay making a data request (in this case the `UserModel.findByIds` call) long enough for it to capture the request to look up both friends and families at once! It will combine the two arrays of ids into one so our `SELECT * FROM users WHERE id IN ...` request will contain the ids of both friends **and** families!
The friends and families request will return at the same time so when we select friends and families for all of previously returned users, the same batching can occur across all of the new users requests! So instead of potentially hundreds of data lookups, we can only perform 3 for a query like this!
Horizontal scaling is a fantastic way to increase the amount of load that our servers can handle without having to purchase more expensive computing resources to handling it. Apollo Server can scale extremely well like this as long as a couple of concerns are handled:
- Every request should ensure it has access to the required data source. If we are building on top of a HTTP endpoint this isn't a problem, but when using a database it is a good practice to verify our connection on each request. This helps to make our app more fault tolerant and easily scale up a new service which will connect as soon as requests start!
- Any state should be saved into a shared stateful datastore like redis. By sharing state, we can easily add more and more servers into our infrastructure without fear of losing any kind of state between scale up and scale down.