DEV Community

loading...
Cover image for Solving N + 1 problem with GraphQL, TypeScript, and PostgresQL

Solving N + 1 problem with GraphQL, TypeScript, and PostgresQL

wonder2210 profile image Wonder2210 ・2 min read

One of the common problems that most of the developers face with graphql, is the N + 1 problem, it means that the server executes multiple unnecessary round trips to datastores for nested data, resulting in slow response times and expensive requests

For Example


Enter fullscreen mode Exit fullscreen mode


graphql
query{
users{

id,
full_name,
pets{
name
}
}
}


Enter fullscreen mode Exit fullscreen mode

in the query above, will be executed 1 query to the database to the datastore get the users and for each user another extra query for each user to fetch its pets (N queries for N users) resulting in N + 1.

Now What to do?

to solve this problem the Graphql team in Facebook created Dataloader
wich batch our data, AKA: request all the data from the database, at the start of the server, and when we are going to request one item, all that we do is pass down the identifier of the item and execute a search inside the array of data, which is a lot faster than execute a query to the database every time is made a request because is retrieving data from memory not datastore

Once this is clarified let's puts our hands on

for this post I'll be using the API example I use for my last post after clone into your PC the repo and install everything
run:


Enter fullscreen mode Exit fullscreen mode


bash
yarn add dataloader

</code>  

create the following folder and file


Enter fullscreen mode Exit fullscreen mode
  ...
     src/
        utils/
           loaders.ts  
  ...
Enter fullscreen mode Exit fullscreen mode

and in uutils/loaders.ts

Enter fullscreen mode Exit fullscreen mode
import {BatchLoadFn} from 'dataloader';
import {Pet,User} from '../database/models';



export const Pets : BatchLoadFn<number,Array<Pet>>= async (ids)=>{
    const pets= await Pet.query();

    return ids.map(i=>pets.filter(item=> item.id===i))

};

export const Users : BatchLoadFn<number,Array<User>> = async (ids)=>{
    const users = await User.query();

    return ids.map(id=> users.filter(i=> i.id===id));
}
Enter fullscreen mode Exit fullscreen mode

the way it will be used in our resolvers is through the context :
src/index.ts


Enter fullscreen mode Exit fullscreen mode


typescript
...
import DataLoader from 'dataloader';
import {Pets,Users} from './utils/loaders';
...

...
const config : Config = {
schema:schema,
introspection: true,//these lines are required to use the gui
playground: true,// of playground
context:{
loaders:{
users: new DataLoader(Users),
pets: new DataLoader(Pets),
}
}

}
...


Enter fullscreen mode Exit fullscreen mode

now in your resolvers instead of using a query to get the data nested
you use the loaders passed down through the context and using the id to find the match in the loaders



And we can appreciate an increase of performance in the response

Performance

Thank's for read it , if you have any suggestion for me I would love to know it, leave it below in the comments box, if you liked it , please follow me here and also on twitter, Thanks! take care!

Discussion (0)

pic
Editor guide