loading...

Get started with NestJS and create a todo "notes" app.

codeliezel profile image Funmilayo E. Olaiya Updated on ・6 min read

Recently, I took part in a challenge to work on a mini NestJS app and I fell in love with the framework.

  • The architecture?
  • Easy to set-up CLI?
  • or the fact that it fully supports the use of typescript?

The three points mentioned above made the experience wonderful and I'll be using NestJs for my future projects.

According to the official docs, it is deemed a progressive Node.js framework for building efficient, reliable and scalable server-side applications.
Check out the docs: https://docs.nestjs.com/

I use NodeJS and the express framework for back-end web development. Express is known and good for setting up quick, reliable and fast APIs but it does not enforce any architecture or solid principles of OOP, and this is exactly where Nestjs comes in.


Few interesting facts I got from using NestJs:

  • It enforces an architecture that ensures the developed/developing app is modular.
  • It is very easy to document APIs using nestJs/swagger, as it can easily be incorporated when the API routes are being designed.
  • It contains all the features you need at the beginning of the project - this happens when you create a new app with the CLI.
  • It helps to consistently demand the best logic and practice when developing new APIs - the reason being that you can not easily do a manipulation.
  • It fits so perfectly with the use of the mongoose ODM and typescript - it helps the more if you are experienced in using mongoose with express.
  • Who is happy with me? Yes, we can finally do away with babel.

A quick overview of how our app architecture will be structured:

Alt Text


Let us get started with creating to understand better.

Install nestjs and create an app using the following commands:

npm i -g @nestjs/CLI

After the installation is successful run,

npm install

Then create a new nest project,

nest new project-name

Since we are going to be using mongoose with NestJs, we have to install some packages.

npm install mongoose -S
npm install @nestjs/mongoose -S
npm install dotenv -S
npm install @types/dotenv -S

Create a database connection using the atlas mongodb connection string.

I am assuming you can set up a new cluster on mongodb called notes-todo.
If you have set up the database and gotten the connection string, good!

In your src folder, create folders called schemas, interfaces, dtos, services and controllers.


Creating a schema.

  • A schema will determine how the data should be stored in the database.

Create a file in the schemas folder called note.schema.ts

Add the following code:

import * as mongoose from "mongoose";
const { Schema } = mongoose;

export const NoteSchema = new Schema({
    name: String,
    description: String,
    tags: {
        type: String,
        enum: ["Personal", "Travel", "Life", "Work"],
    },
});

Create a file in the interfaces folder called note.interface.ts

  • An interface defines the kind of values(type-checking) the application must adhere to/receive.
  • The readonly keyword depicts that the values can be accessed outside of the class but cannot be modified.

Add the following code:

import { Document } from "mongoose";

export interface Note extends Document {
    readonly name: string;
    readonly description: string;
    readonly tags: string;
    readonly createdAt: Date;
}

Create a file in the dtos folder called note.dto.ts

  • A DTO(data transfer object) depicts what the expected request should look like.

Add the following code:

export class CreateNoteDTO {
    name: string;
    description: string;
    tags: string;
    createdAt: Date;
}

Create the service provider class and methods for all the routes:

  • In the services folder, create a file named note.service.ts

Add the following code:

import { Injectable } from "@nestjs/common";
import { Model } from "mongoose";
import { InjectModel } from "@nestjs/mongoose";
import { Note } from "../interfaces/note.interface";
import { CreateNoteDTO } from "../dtos/note.dto";

@Injectable()
export class NoteService {
    constructor(@InjectModel("Note") private readonly noteModel: Model<Note>) { }
    async createANote(createNoteDTO: CreateNoteDTO): Promise<Note> {
        const newNote = await this.noteModel(createNoteDTO);
        return newNote.save();
    }

    async getAllNotes(): Promise<Note[]> {
        const notes = await this.noteModel.find().exec();
        return notes;
    }

    async getANote(noteId): Promise<Note> {
        const note = await this.noteModel.findById(noteId).exec();
        return note;
    }

    async updateANote(_id, createNoteDTO: CreateNoteDTO): Promise<Note> {
        const note = await this.noteModel.findByIdAndUpdate(_id, createNoteDTO, { new: true });
        return note;
    }

    async deleteANote(_id): Promise<any> {
        const note = await this.noteModel.findByIdAndRemove(_id);
        return note;
    }
}

Note:

  • @Injectable is a decorator that allows classes to be made available and be a provider.
  • A NoteService function is created and the note interface is injected through the class constructor using the decorator @InjectModel from nestjs/mongoose.
  • The NoteService class takes in five methods to help design the API routes.
  • The main use of these methods is to abstract the logic away.

Creating the controller class and methods for all the routes:

In the controllers folder, create a file named note.controller.ts

Add the following code:

import { Controller, Res, HttpStatus, Post, Get, Param, Body, Patch, Delete } from "@nestjs/common";
import { NoteService } from "../services/note.service";
import { CreateNoteDTO } from "../dtos/note.dto";

@Controller('note')
export class NoteController {
    constructor(private noteService: NoteService) { }

    @Post('/add')
    async createANote(@Res() res, @Body() createNoteDTO: CreateNoteDTO) {
        const note = await this.noteService.createANote(createNoteDTO);
        return res.status(HttpStatus.CREATED).json({
            status: 201,
            message: "Successful!",
            data: note
        })
    }

    @Get('/all')
    async getAllNotes(@Res() res) {
        const notes = await this.noteService.getAllNotes();
        return res.status(HttpStatus.OK).json({
            status: 200,
            data: notes
        })
    }

    @Get("/:noteId")
    async getANote(@Res() res, @Param("noteId") _id: string) {
        const note = await this.noteService.getANote(_id);
        if (!note)
            return res
                .status(HttpStatus.NOT_FOUND)
                .json({ status: 404, error: "Not found!" });
        return res.status(HttpStatus.OK).json({ status: 200, data: note });
    }

    @Patch('/update/:noteId')
    async updateCustomer(@Res() res, @Body() createNoteDTO: CreateNoteDTO, @Param("noteId") _id: string) {
        const note = await this.noteService.updateANote(_id, createNoteDTO);
        if (!note)
            return res
                .status(HttpStatus.NOT_FOUND)
                .json({ status: 404, error: "Not found!" });
        return res.status(HttpStatus.OK).json({
            status: 200,
            message: 'Successful!',
            note
        });
    }

    @Delete('/delete/:noteId')
    async deleteCustomer(@Res() res, @Param('noteId') _id) {
        const note = await this.noteService.deleteANote(_id);
        if (!note)
            return res
                .status(HttpStatus.NOT_FOUND)
                .json({ status: 404, error: "Not found!" });
        return res.status(HttpStatus.OK).json({
            status: 200,
            message: 'Successful!',
        })
    }

}

Note:

  • A class called NoteController is created and the provider - NoteService is injected through the class constructor.
  • The five methods created within the class controller are only created to handle the incoming requests. Remember that all the logic have been abstracted away with the providers.

Create a feature module for the provider and the controller:

  • The purpose of a feature module is to simply organize code and establish boundaries and this principle makes more sense if the application is bound to keep growing, it is used with the @Module decorator.

In the modules folder, create a file named note.module.ts

Add the following code:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { NoteController } from "../controllers/note.controller";
import { NoteService } from "../services/note.service";
import { NoteSchema } from "../schemas/note.schema";

@Module({
    imports: [
        MongooseModule.forFeature([{ name: 'Note', schema: NoteSchema }])
    ],
    controllers: [NoteController],
    providers: [NoteService]
})
export class NoteModule { }

The root module needs to be modified:

The module is the start point of the application graph and it encapsulates providers by default, but since we already have a defined feature module, all we need to do is import that feature module and schema into this root module.

In the app.module.ts

Modify it by adding the following code:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
import { NoteModule } from './modules/note.module';
import "dotenv/config";

@Module({
  imports: [
    MongooseModule.forRoot(process.env.DATABASE_URI,
      {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        useCreateIndex: true,
        useFindAndModify: false
      }),
    NoteModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

Finally:

In order to add an API version, we are going to use nestjs setGlobalPrefix

In the main.ts

Modify it by adding the following code:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix("api/v1");
  await app.listen(3000);
}
bootstrap();

Add the connection string to your .env file

example: DATABASE_URI = mongodb+srv://<username>:<pasword>@cluster0-xydxj.mongodb.net/notes_db

Run npm run start:dev


These API routes should be able to function:

localhost:3000/api/v1/notes/add
localhost:3000/api/v1/notes/all
localhost:3000/api/v1/notes/:noteId
localhost:3000/api/v1/notes/update/:noteId
localhost:3000/api/v1/notes/delete/:noteId

KeyNote: Read up more on Dependencies, Decorators, Modules, Providers and Controllers on the official docs: docs.nestjs.com

For part 2: https://dev.to/funmi5/get-started-with-nestjs-and-create-a-todo-notes-app-creating-e2e-tests-part-2-5pl

For part 3: https://dev.to/funmi5/get-started-with-nestjs-and-create-a-todo-notes-app-documenting-the-api-endpoints-with-nestjs-swagger-part-3-67

For the code - https://github.com/funmi5/nestjs-notes-todo

Posted on by:

codeliezel profile

Funmilayo E. Olaiya

@codeliezel

Believer in the future of all things!

Discussion

pic
Editor guide
 

It's looking good but I would make this one with just using Mongoose, Express, dotenv and no extra dependencies but it does look good! Check my little API:

github.com/SalesmanUnknown/jokes-api

 

Great! Whatever you want to make of it..