Building GraphQL API with NestJS

I have developed web applications for more than a decade. I'd known the good times when `Web` was built around `PHP`, the dawn of new masters of the industry. In those days I was impressed by the pace of development for new frameworks and their features. But, mostly, I was disappointed by existing approaches of building large, reliable, and what's really important, scalable applications and APIs. Frameworks keep appearing at a fast pace. But most of them, aim at totally different principles, copying attitudes of existing analogs. Most of them have too raw responsibility rules, which, basically, allowed people to `express` themselves in a way and a manner that sometimes can't be or, even, should be, predicted by colleagues. I was seeking to find a framework, which will be flexible enough in a place, but offers predictable and straightforward responsibility rules, so scaling of the application won't raise maintenance effort geometrically.

At the moment when I decided to switch to `Node.js`, to pursue modern technology stack, performance reasons, SPAa development, better tools to work with instant messaging and etc, I was looking for the solution that can replace old frameworks in terms of reliability and scalability, but bring modern development technologies in my life. That's how I found `NestJs`, which is, basically, is higher abstraction level framework on `Express`, which, is turn, built on top of `Node.js`.

Introduction

NestJs is a rising star among `Node.js` frameworks, growing popularity it owes to modular, scalable and robust architecture. Initially inspired by `Angular`, it combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming)within full support of `Typescript`. With usage of `Express` (or `Fastify`) under the hood, which makes it compatible with the majority of `Express` middleware.

Why NestJs?

Pros

  1. `Typescript` is a key-feature in modern `web` development, we always prefer well-typed codebase if possible.

    • Use decorators to observe, modify, validate or replace your classes, while preserving DRY principles and being concise.

    • Stay predictable and easy-to-understand. Design fully typed business-logic for your application, for rest - ~~MasterCard~~ SRP (single-responsibility principle).

    • You should definitely enjoy `NestJs` if you familiar with `Angular` . Stick to the well-known dependency injection principle, guards, interceptors, modular structure and etc..

  2. One of the `NestJs` advantages - modular approach, we always want to keep our applications robust and scalable.

    • Well-planed modular project structure includes a best practice for lion's share of operations you would want to perform, allows you colleagues to predict which code goes where, which kind of class should be used for this type of operation, etc.

    • Unlike most of popular frameworks, in NestJs you won't encounter a question how to validate your DTOs. Because of thoughtful structure your incoming data will be typed by DTOs, and validated with best NestJs practices by `class-validator` package.

    • You can easily scale your application with `NestJs`. Nest natively supports the microservice architectural style of development. Has a widely covered documentation and different approaches available.

  3. As I mentioned in previous para, most of necessary packages already has an integration with NestJs via `@nestjs/packagename` wrapper designed to keep the project structure still predictable even with third-party libraries.

  4. NestJs is growing very quickly, already passed 7th version and goes on. High community support encourages devs to keep the framework up to modern standards, at the moment more than 28.8k stars proving.After each release you can always find updated, well-structured comprehensive documentation, with step-by-step best practices designs for most of popular cases.

  5. Use modern approaches on building `GraphQL` APIs, wrapped by `@nestjs/graphql` package.

  6. Simple and straightforward request lifecycle makes it easier to understand how service all parts of the request - validation, data interception, transformation, response part and etc.

  7. Scaffold your project with style. Use convenient CLI to start a new project, generate module, build, install or update packages libs.

Cons

  1. Due to single-responsibility principle (SRP), `NestJs` requires you to clearly understand which class should be responsible for the action you want to perform, so at the begging it make take some time to recognise / read according docs which class you should use.

  2. Some features or integrations are built on top of certain libs, to follow basic responsibility pattern you should get used to this libs, accept their style and approaches.

Prerequisites

Step 1 - Installing NestJs CLI

To get started with NestJs, you will need to globally install the Nest CLI. It is a command-line tool created to scaffold fresh NestJs app.

$ npm install -g @nestjs/cli

This will give you access to the`nest`command for project installation and other project specific commands.

Next, run the following command to scaffold a new project named`todoapp-nest`within your development folder:

$ nest new todoapp-nest

You will be asked which package manager you would like to use, just follow the prompt and respond accordingly. Next, once the installation is complete, switch your working directory into the newly created project:

$ cd todoapp-nest

Start the application with:

npm run start

You can also run the followingcommand in order to use Nodemon for the project:

// start the application using --watch option
npm run start:dev

Navigate to`http://localhost:3000`in your browser and you will see the Hello World! message as shown in the following image:

Step 2 - Adding GraphQL

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API, gives clients more flexibility and customization tools for developer needs.

While REST API is an architectural concept for network-based software. GraphQL, on the other hand, is a query language, a specification, and a set of tools that operates over a single endpoint using HTTP.

The advantage of GraphQL - you can ask data you need in particular, sustain security and increase performance.

NestJs provider two ways of building GraphQL APIs:

We prefer `Code First` approach, because we allowed to use decorators and Typescript classes to generate the corresponding `GraphqQL` schema. Advantages of this approach - we can avoid context switching between different languages, use TS support while writing our schemas, benefit from `class-validator` and `class-transformer` packages, validating our data, use `GraphQL CLI`, to generate our modules, which is accessible only for `code first` approach.

In order to get started with GraphQL, you need to install packages:

$ npm i @nestjs/graphql graphql-tools graphql

Depending on what underlying platform you use (`Express` or `Fastify`), you must also install either `apollo-server-express` or `apollo-server-fastify`.

Next, we should register GraphQL module in `app.module.ts`:

import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: true,
    }),
    TasksModule
  ],
...

To maintain GraphQL hints add following code to your nest-cli.json:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/graphql/plugin"]
  }
}

With the packages installed, let's create task module.

Step 3 - Creating a Module

In order to generate module, we need to run the following command:

$ nest generate module tasks

This creates a new folder named`tasks`within the`src`folder. Within the`books`folder you will find a`tasks.module.ts`file:

import { Module } from '@nestjs/common';
@Module({})
export class TasksModule {}

This was generated by the command and the module has also been added to the`app.module.ts`which happens to be the root module of the application.

Next, we have to create resolver.

Step 4 - Creating a Resolver

To generate resolver paste this in command prompt:

$ nest generate resolver tasks

The command above will create resolver `tasks.resolver.ts` file inside `tasks` folder and update `tasks.module.ts` listing new provider.

import { Resolver } from '@nestjs/graphql';

@Resolver('Tasks')
export class TasksResolver {}

Step 5 - Add a Model

To describe an object we want to work with, we should create a model for `Task` instance. To do this create `models` folder and add `task.model.ts` inside, than add this code:

import { Field, ID, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Task {
  @Field(() => ID)
  id: string;

  @Field()
  title: string;

  @Field()
  description: string;

  @Field()
  completed: boolean;
}

To simplify things we won't be connecting to the database now, so let's create a sample mock data for our list. Create `mocks` folder in the `src` folder, than add file named `tasks.mock.ts` with following code:

export const TASKS = [
  { id: '1', title: 'Task #1', description: "This is the description for the task #1", completed: false },
  { id: '2', title: 'Task #2', description: "This is the description for the task #2", completed: false },
  { id: '3', title: 'Task #3', description: "This is the description for the task #3", completed: true },
  { id: '4', title: 'Task #4', description: "This is the description for the task #4", completed: false },
  { id: '5', title: 'Task #5', description: "This is the description for the task #5", completed: true },
  { id: '6', title: 'Task #6', description: "This is the description for the task #6", completed: false },
  { id: '7', title: 'Task #7', description: "This is the description for the task #7", completed: false },
];

Next, we should create a service to implement all the logic for TodoApp.

Step 6 - Generating a Service

$ nest generate service tasks

This command will create a new file named `tasks.service.ts` within ./src/tasks folder and update `tasks.module.ts` with a new provider.

First let's import our mocked data, so we can serve our queries with data.

import { Injectable } from '@nestjs/common';

import { TASKS } from '../mocks/tasks.mock';

@Injectable()
export class TasksService {
  tasks = TASKS;
}

Than we should add 2 methods, `getTasks` and `getTask`, to receive all tasks or certain one respectively.

...
tasks = TASKS;

getTasks() {
  return this.tasks;
}

getTask(id: number) {
  return this.tasks.find(task => task.id === id);
}
...

`getTask` method accepts `id` as a parameter, of type `number`, which we will ensure via validation in next steps.

So it's time to add typical `mutation` method to create entity of `Task`, let's name it `addTask`:

async addTask(input: AddTaskInput): Promise<Task[]> {
    const lastTask = this.tasks.slice(-1).pop();
    const task: Task = {
      id: lastTask.id + 1,
      title: input.title,
      description: input.description,
      completed: false,
    };

    this.tasks.push(task);
    return this.tasks;
  }

You can always take advantage of `async/await` syntax, for asynchronous operations, since `NestJs` supports latest `Typescript`.

In the same manner adding update/delete methods:

  deleteTask(id: string): Task[] {
    const taskIndex = this.tasks.findIndex(item => item.id === id);
    if (taskIndex === -1) {
      throw new HttpException('Task not found', 404);
    }

    this.tasks.splice(taskIndex, 1);
    return this.tasks;
  }

Step 7 - Add a DTO

DTO is a data transfer object, a TypeScript class created for type-checking and to define the structures of what an object looks like upon Task creation.

import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';

@InputType()
export class AddTaskInput {
  @Field()
  @IsNotEmpty()
  title: string;

  @Field()
  description: string;
}

Since we will generate a new `ID` for task, we don't want to include it into

Step 8 - Injecting a Service into Resolver

Since `NestJS` is build around `Dependency Inject` concept. we can benefit in performance by injecting instance of `TasksService` into `TasksResolver`. To achieve this we need add respective code to class constructor:

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { TasksService } from './tasks.service';
import { AddTaskInput } from './dto/add-task.input';
import { UpdateTaskInput } from './dto/update-task.input';
import { Task } from './models/tasks.model';

@Resolver('Tasks')
export class TasksResolver {
  constructor(
    private readonly taskService: TasksService
  ) {}

  @Query(type => [Task])
  async getTasks() {
    return this.taskService.getTasks();
  }

  @Query(type => Task)
  async getTask(
    @Args('id') id: string,
  ) {
    return this.taskService.getTask(id);
  }

  @Mutation(type => [Task])
  async addTask(
    @Args('input') input: AddTaskInput,
  ) {
    return this.taskService.addTask(input);
  }

  @Mutation(type => Task)
  async updateTask(
    @Args('input') input: UpdateTaskInput,
  ) {
    return this.taskService.updateTask(input);
  }

  @Mutation(type => [Task])
  async deleteTask(
    @Args('id') id: string,
  ) {
    return this.taskService.deleteTask(id);
  }
}

Note that `Query` decorator should be imported from `@nestjs/graphql`.

Next, we injected `TaskService` via constructor and created set of `Queries/Mutations`

  • getTasks() - returns all tasks;
  • getTask(id) - returns task by id;
  • addTask(input) - creates task;
  • updateTask(input) - updates task state;
  • deleteTask(input) - delete task;

For each `Query/Mutation` we set return type for GraphQL according to the data returned from service methods.

Step 9 - Running the App!

When all setup finished, it's time to test out API. For testing we recommend using `GraphQL Playground`, which is available by default.

By default server starts on `3000` port. To open `playground` visit `http://localhost:3000/graphql`

Get all tasks:

Add task:

Update task:

Conclusion

In this article we examined basic fundamentals and practices of NestJs, slightly discussed upsides and downsides of GraphQL, reviewed few approaches of building a GraphQL application.

You will find the complete source code of this tutorial here on GitHub.