Why GraphQL? Implementation of a GraphQL API on the NestJS framework

Paul Harbinson
LibertyIT
Published in
8 min readMar 16, 2021

Introduction

GraphQL is an alternative to RESTful API’s. So what benefits or functionality does it provide that REST does not? If you consider over-fetching or under-fetching of data an issue then GraphQL could be the solution. A RESTful endpoint may return dozens of data fields of which you only require a few or you may have to call multiple endpoints to get all the data you need, again fetching even more unnecessary data that has to be stripped out on the client side.

A GraphQL Schema makes it possible to dig deeper into a single endpoint and fetch all of the data and only the data you require in a single http request. It is also easy to implement real-time updates to your data in the form of subscriptions that use websockets under the hood.

In the interest of transparency, most apps do not sustain the amount of traffic that would warrant using GraphQL solely to reduce the amount of http requests or the size of data packets. However, from a developer’s perspective I prefer the idea of using a query language on the front-end to select the data I want in one request rather than making multiple requests, mapping the data, and then collating it afterwards.

Prerequisites

The only prerequisites for this tutorial are an IDE, such as Visual Studio Code, and Node.js installed to host the server, which can be downloaded at https://nodejs.org/en/download/. All other download instructions are set out in the detailed steps. A little knowledge of NestJS and Typescript would be useful but not completely essential. GraphQL can be implemented in most languages and I only chose NestJS because I’m using it in my next project. There’s actually a little bit more boilerplate code in NestJS than some other implementations I’ve seen such as with AWS AppSync — A full-stack GraphQL backed application can be implemented very quickly using AWS Amplify, AppSync, and DynamoDB.

The Anatomy of GraphQL

GraphQL is mainly composed of the Schema and its Resolvers. The Schema describes the make-up of the data that can be fetched by the queries. That is, the types of objects, the relationships between types, and the actions that can executed on the types( e.g. like the CRUD actions in REST — GET, POST etc). GraphQL has 3 main actions; Query (like RESTful GET), Mutation (like POST, PUT, DELETE), and Subscription(Websockets) for real-time updates. The Resolvers are the methods that handle the queries and generate the responses.

NestJS

Nest (NestJS) is a framework for building Node.js server-side applications. It makes use of HTTP Server frameworks like Express providing a level of abstraction away from the Express API’s but still allowing the developer access to those API’s. If you are a front-end Angular developer you will recognise a lot of the patterns and annotations/decorators used in NestJS.

Repo

The finished code for the following tutorial can be found here: https://github.com/PaulHarbinson/nestjs-graphql

Getting Started

In the following set-up, to avoid confusion, I am going to assume the new project name is ‘nestjs-graphql-app’. Replace with your chosen app name.

npm i -g @nestjs/cli
nest new nestjs-graphql-app
cd nestjs-graphql-app
yarn add @nestjs/graphql graphql-tools graphql apollo-server-express class-validator uuid
yarn start:dev

The above commands install the NestJS Command Line Interface, creates a new Nest project, changes directory into that project, adds dependencies using yarn (or npm if you prefer), and starts the development server. Along with the required Nest and GraphQL dependencies I installed an Express server, a class validator for compile-time type checking, and the uuid package for generating unique ID’s to test the API with.

Coding the Project

Open the project in the IDE:-

code . //opens the project in Visual Studio Code

You can delete the following files(and references to them in app.module.ts) if you want since we won’t be using them here:-

app.controller.ts
app.controller.spec.ts
app.service.ts

NestJS support two approaches to generating a GraphQL Schema. In the code first approach, you use decorators and TypeScript classes to generate the corresponding GraphQL schema. In the schema first approach, a typePaths property indicates where the GraphQLModule should look for GraphQL SDL schema definition files which need to be written manually. I use the code first approach here. Below will be the completed app.module.ts file. It contains a reference to the UsersModule which we haven’t discussed yet. Everything else is generic:-

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersModule } from './users/users.module';
@Module({
imports: [
GraphQLModule.forRoot({autoSchemaFile: true}),
UsersModule,],
controllers: [],
providers: [],
})
export class AppModule {}

For each new entity or ‘type’, create a folder under /src and in it a module file, a service file, and a resolver file. There are command shortcuts to have Nest generate the code and test classes for you and also update the relevant Module file with references to the new Service and Resolver. For example, if you wanted to create a new Schema ‘type’ called Courses:-

nest g module courses
nest g service courses
nest g resolver courses

NestJS will prepend ‘Module’ ‘Service’ and ‘Resolver’ to the given name to form the name of the class, e.g. CoursesModule, CoursesService, and CoursesResolver.

Below are examples of these files for the Users type:-

//users.module.tsimport { Module } from '@nestjs/common';
import { UsersResolver } from './users.resolver';
import { UsersService } from './users.service';

@Module({
providers: [UsersResolver, UsersService]
})
export class UsersModule {}
//users.service.tsimport { Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import { GetUserArgs } from './dto/args/get-user.args';
import { CreateUserInput } from './dto/input/create-user.input';
import { User } from './models/user';

@Injectable()
export class UsersService {
private users: User[] = [];

public createUser(createUserData: CreateUserInput): User {
const user: User = {
userId: uuidv4(),
...createUserData
}

this.users.push(user);

return user;
}

public getUser(getUserArgs: GetUserArgs): User {
return this.users.find(user => user.userId === getUserArgs.userId);
}
}

In the above UsersService only the createUser and getUser actions are displayed.

This is not representative of a real service in that we are only saving new users to an array. However, it is easy to see how we could be saving to and fetching from a MongoDB database here, for example.

We can also see how a certain amount of boilerplate code comes into play here. In a folder called ‘models’ we have out User class which contains all the fields that go to make up a User, and we also have a ‘dto’ folder which contains the Data Transfer Objects for decoupling the persistence layer and the API layer. This pattern is commonly used in REST API so is not unique to GraphQL.

//user.tsimport { Field, Int, ObjectType } from "@nestjs/graphql";

@ObjectType()
export class User {
@Field()
userId: string;

@Field()
email: string;

@Field(() => Int)
age: number;

@Field({ nullable: true })
isSubscribed?: boolean;
}
//create-user.input.tsimport { Field, InputType } from '@nestjs/graphql';
import { IsEmail, IsNotEmpty } from 'class-validator';

@InputType()
export class CreateUserInput {
@Field()
@IsNotEmpty()
@IsEmail()
email: string;

@Field()
@IsNotEmpty()
age: number;
}
//get-user.args.tsimport { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, isNotEmpty } from 'class-validator';

@ArgsType()
export class GetUserArgs {
@Field()
@IsNotEmpty()
userId: string;
}
//users.resolver.tsimport { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
import { GetUserArgs } from './dto/args/get-user.args';
import { CreateUserInput } from './dto/input/create-user.input';
import { User } from './models/user';
import { UsersService } from './users.service';

@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}

@Query(() => User, { name: 'user', nullable: true })
getUser(@Args() getUserArgs: GetUserArgs): User {
return this.usersService.getUser(getUserArgs);
}

@Mutation(() => User)
createUser(@Args('createUserData') createUserData: CreateUserInput): User {
return this.usersService.createUser(createUserData);
}
}

Again, only the getUser and createUser resolvers are displayed here for brevity. We can see here how the resolver is the link between the GraphQL Schema and the service which connects to the database.

Speaking of the GraphQL Schema, we haven’t seen what it looks like because the ‘code first’ approach in NestJS generates is on the fly using the decorators and TypeScript classes that we created above. If you want to see the GraphQL Schema file then provide a path to it in the autoSchemaFile property in the GraphQLModule options object in the app.module.ts file.

//app.module.tsimport { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { UsersModule } from './users/users.module';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
UsersModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
//schema.gql# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------
type User {
userId: String!
email: String!
age: Int!
isSubscribed: Boolean
}
type Query {
user(userId: String!): User
}
type Mutation {
createUser(createUserData: CreateUserInput!): User!
}
input CreateUserInput {
email: String!
age: Float!
}

As before, I have removed references to ‘updateUser’, deleteUser’ and the query to fetch all users; full code to be found in the GitHub repo. It mostly just contains the types of objects being worked with and their input fields. The question mark, or bang symbol, represents a non-nullable field.

Every GraphQL service has a query type and may or may not have a mutation type. These types are the same as a regular object type, but they are special because they define the entry point of every GraphQL query. That brings us to how a front-end application interacts with a GraphQL API. NestJS provides a graphical editor tool called Playground that lets you interact with the API. The query language is quite similar to a json object. You state the type of query(e.g. query, mutation, subscription), the name of the query, any arguments required, and then within the second set of curly braces you state which specific fields you want returned i.e. no over-fetching. If there are relationships between types, e.g. a user is enrolled in many courses, then you have nested entry points e.g. getCourses would be nested within getUser i.e no under-fetching. The screenshots below show the query for creating a user and fetching a user. Finally, another benefit of GraphQL is that you can ask a GraphQL schema for information about what queries it supports. So an editor like Playground can provide detailed documentation on the composition of your schema.

Conclusion

Will GraphQL replace REST? No. Will it gain popularity in apps where the data required is driven by the front-end? Probably. Personally, I have always disliked having to make a REST call to an endpoint that brings back a huge amount of json with dozens of fields when I am only interested in a few of those fields. I look forward to finding out how practical it is for an app that is dependent on multiple other microservices in the cloud, which, in AWS, would involve calling GraphQL from lambda functions.

Any thoughts or comments let me know below, and see current vacancies in Liberty IT here!

--

--