Overview

We recently started out with a project where we are using Apollo GraphQL with TypeGraphQL, Typegoose as mongoose wrapper, and Express.js to create APIs. While individually, documentation of each of the components is top-notch, when trying to use them together there are several hurdles. We have created a boilerplate GraphQL API with Node.js, Apollo, TypeGraphQL, TypeScript, Nx, MongoDB, Typegoose.

What is GraphQL?

GraphQL is a query language for APIs. It provides a complete and understandable description of data in your API, gives clients the power to ask for exactly what they need, which makes it easier to evolve APIs over time, and enables powerful tooling for the developers.

Schema:

GraphQL server uses a schema to describe the shape of requests/graphs. This schema basically defines a hierarchy of types with fields that are populated from back-end data stores.

type User {

username: String
email: String

}

Resolvers:

The server needs to know how to populate data for every field in your schema so that it can respond to requests for that data. To accomplish this, it uses resolvers. Resolvers can be of three types-

I. Queries

{
user
{
username email
}
}

II. Mutations

mutation {

createUser(username: “test”, email: “test@test.com”)

{

username email

}

}

III. Subscriptions

subscription {
users
}

Why choose GraphQL over REST?

I. Challenges with REST

We have been using REST APIs for a long time now. However, there are some problems with REST – for instance fetching data from REST API comes with a lot of unnecessary data being pulled. Also, we have to remember a lot of API endpoints.

With GraphQL we describe to the client which data we want to have instead of just asking for all the data.

II. GraphQL solves the issue

In the backend, we need to define our data types which will form our schemas, and the resolvers to resolve the request coming frontend.

In GraphQL we only need to maintain a single endpoint where we would request only the data that we need on the frontend. GraphQL basically reduces the network call by enabling us to fetch all the data that we need in a single query from a single endpoint.

Using Apollo with TypeGraphQL

The most popularly used library for GraphQL is Apollo. The Apollo Server comes with a lot of features like caching, an external data loader, and many more.

The Apollo Client is a tool that helps you use GraphQL in the frontend. It provides different features like in-memory caching, state management, etc. It can be integrated with multiple JavaScript frameworks like React, React Native, Vue, Angular and for iOS and Android, there are also possibilities to use the Apollo Client.

TypeGraphQL

TypeGraphQL is a modern framework for creating GraphQL APIs. Apollo is really great and solves many problems that we have.

Creating a sample resolver:

@Resolver() class UserResolver {

private userCollection: User[] = [];

async users() {

return await this.userCollection;

}

}

Using TypeGraphQL with Typegoose

Typegoose is basically a TypeScript wrapper around Mongoose. It is easy to use in a typescript environment and provides a lot of features.

A challenge we encountered while Integrating Typegoose with TypeGraphQL is that we had to define multiple interfaces – one for Typegoose, then one for TypeGraphQL schema, resulting in a lot of redundancy.

The solution we found was simply to use TypeGraphQL decorators on top of Typegoose methods.

@ObjectType()

export class User {

@Field()

readonly _id: string;

@Field() @prop()

public username: string;

@Field()

@IsEmail()

@prop()

public email: string;

}

Here we define our MongoDB models with prop() decorator from Typegoose and along with we simultaneously define our GraphQL schemas with ObjectType() and Field() decorators from TypeGraphQL. This basically removes all the redundant interfaces we had earlier.

The second challenge we encountered is that during the initial phase we were writing all core logic directly into resolver methods which eventually created a lot of problems in maintaining the codebase.

The solution was to refactor all codebases into different folder structures. We started using typescript decorator features for services and used dependency Injector to inject all Database models and Services directly into Resolvers.

import { Service, Inject } from “typedi”;

@Service()

export class UserService {

constructor(

@Inject(“userModel”) private readonly UserModel

) {}

async getAll() {

return this.UserModel.find();

}

}

Create UserService which injects userModel.

Once we have our service running we can inject this directly into our Resolvers as :

@Resolver()

export class UserResolver {

constructor(

private readonly userService: UserService,

) { }

@Query(() => [User])

async users() {

return this.userService.getAll();

}

}

Most common challenges we may encounter

While using GraphQL we have a lot of advantages. However, it comes with its own set of challenges. The most common problem you would face is the server making a lot of multiple requests to the database than expected.

Suppose you have a list of posts, in which each post has a user document associated with it. Now you may want to fetch all of these posts with user data.  While using REST this would be similar to having two database calls. One for posts and one for users corresponding to these posts.

But what about GraphQL? We now have an extra call to fetch each user data per resolver. Now let’s say we have 10 posts and each post also has 5 comments, each of which has a user document. So the number of calls we will have is one for the list of posts, 10 for the post authors, 10 for each sub-list of 5 comments, and 50 for the comment users which sums up to around 71 database calls to fetch a set of data!

Nobody would want this to happen – waiting for 15 secs to load a set of posts.

To solve this problem we have a Dataloader library.

Dataloader basically lets you combine or batch multiple similar requests and cache database calls. The data loader detects that posts having similar id and batch them together and will reuse the user document which it already has in memory instead of making a new database call.

We hope you enjoyed reading this blog. We will keep posting technical experiences while building software. You can read our other blogs here!