DEV Community

cbb-it-minds for IT Minds

Posted on • Updated on • Originally published at insights.it-minds.dk

How to architecture your JavaScript API using NestJS with a GraphQL API example part 1/2.

A traditional NodeJS and Express backend is quite simple to certain extent. You have a RouteHandler and some functions which returns some data upon request. While Node servers are simple, we are often afraid to open an old project during either maintenance or updating these projects.

Node does not give a concrete guidance as to how we are to structure our APIs, hence we see a bunch of Express routes that does all kinds of operations like: data-access, validation, logging, etc. While this is not always the case we do see them once in a while.

As well as these 'interesting' interpretations of how backend APIs should be structured and if logic should be separated or not. We do see structured APIs where data-access and business-logic is separated into different modules in a MVC-like architecture.

As much as I like the degrees of freedom Node and Express provides us. I believe that the problem inherited from this freedom is the main reason as to why we are so terrified, when we open a Node application.

In this series of blog posts we will take a look at how NestJS provides us guidance as to how we can structure our Node Express (Fastify and more...) applications and APIs with a GraphQL API example. Hopefully as we dig deeper into NestJS you will experience the same epiphany as I did the more I worked with NestJS.

In this first part of this series, we will setup the project and talk about what NestJS CLI gives us out-of-the-box. We will take a look at how we set up a simple MongoDB and structure a Mongoose Schema and Model, it is more fun playing with a real database than some JSON-objects.

In the second part we will start creating a GraphQL API in a Schema first approach and how NestJS can help structuring the application using dependency injection and decorators. We will look at some of the quirks and some of the cool stuff that NestJS introduces.

What is NestJS?

NestJS provides an abstraction on top of Node and makes use of Express (at least pr. default). NestJS provides an out-of-the-box application architecture that reminds a lot about the way you would structure an Angular application. With stuff like Modules, Services, Interceptors and Guards we clearly see from where they've been inspired. This architecture allows for scalability, easily maintainable- and testable applications as in Angular. Separating your validation- , business-logic as well as your data-access into different modules and services, provides a concise and testable application and API. But enough of the talking, lets dig into it and get started.

Let's get started

First we download the NestJS CLI and create a new project:

$ npm i -g @nestjs/cli
$ nest new my-nest

NestJS uses the power of TypeScript. If you are writing an application with TypeScript you will probably already know why this a great thing to have out-of-the-box.

Looking inside src/main.ts we see a function which acts like the entrypoint for our application. Just like in Express. We create an app and listen on some port. To run your application use:

$ npm run start:dev

Test the API by going to localhost:3000 in your browser or use your favorit API testing tool.

Project Structure

If we open src we see three different types of files: controller, module and service. The controller is where we define different endpoints. In the file we see that we can annotate functions as Get, Post, Put etc. Since we are going to use GraphQL, this is not relevant. We will later be creating resolvers, which will act as a controller.

Service: is an injectable component we use for fetching data to our controller.

MongoDB and Mongoose

To get going with our own database we use MongoDB and Mongoose:

$ npm install --save mongoose
$ npm install --save-dev @types/mongoose

I like to put my database configuration in a database folder (src/database). Create a module which we can use to inject in our model-classes. As in a classic Mongo setup this will make each class that depend on the connection wait till until a promise is resolved.

// products.module.ts

import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

Create the database providers. Here we can specify connection option as optional arguments, eg: {useNewUrlParser: true}.

// database.providers.ts

import * as mongoose from 'mongoose';
import { KEYS } from '@/config/config.constants';

export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';

export const databaseProviders = [
  {
    provide: DATABASE_CONNECTION,
    useFactory: (): Promise<typeof mongoose> =>
      mongoose.connect(KEYS.mongodb_connection_uri, { useNewUrlParser: true }),
  },
];

Now we are almost ready to connect to our database, all we need now is to define some model we would like to save in our database.

Creating a ProductModel

In the database-folder create a new schemas-folder. Inside the Schemas folder create a products-folder and create two files: products.schema.ts & products.interface.ts. The interface file are as follows:

// products.interface.ts

import { Document } from 'mongoose';

export interface Product {
  title: string;
  brand: string;
  currentPrice: number;
}

export interface ProductDocument extends Document, Product {}

With a Schema as follows:

// products.schema.ts

import { ObjectId } from 'mongodb';
import { Model, Schema } from 'mongoose';
import { ProductDocument } from './products.interface';

export const ProductSchema = new Schema({
  _id: { type: ObjectId, auto: true },
  title: String,
  brand: String,
  currentPrice: Number,
});

export type ProductModel = Model<ProductDocument>;

That it for our ProductModel for now. We will now create a module which we can define our types, queries and mutations. But first we need to create a ProductProvider:

// products.providers.ts

import { Connection } from 'mongoose';
import { ProductSchema } from './products.schema';
import { DATABASE_CONNECTION } from '@/database/database.providers';

export const PRODUCT_MODEL = 'PRODUCT_MODEL';

export const productsProviders = [
  {
    provide: PRODUCT_MODEL,
    useFactory: (connection: Connection) =>
      connection.model('Product', ProductSchema),
    inject: [DATABASE_CONNECTION],
  },
];

ProductModule

Create a new module using the NestJS CLI:

$ nest g module products

Add the productsProviders and DatabaseModule:

// products.module.ts

import { Module } from '@nestjs/common';
import { DatabaseModule } from '@/database/database.module';
import { productsProviders } from '@/database/schemas/products/products.providers';

@Module({
  imports: [DatabaseModule],
  providers: [...productsProviders],
})
export class ProductsModule {}

Import ProductModule into ApplicationModule and add it to imports. Now we can finally start creating our API! So stay tuned for the second part of this series, where we finally can start coding :)
This will be out next friday!

Top comments (0)