CRUD MongoDB avec Nest.js et Mongoose

Pour le contexte de ce tutoriel, j’ai réutilisé mon API d’un clone Trello

Qu’est ce que Nest.js ?

Nest.js est un framework Node.js qui permets d’avoir une surcouche Typescript pour un code de meilleure qualité grâce aux typages.

Nest.js permet de séparer le code en plusieurs fichier pour les controllers et les services afin d’avoir un code mieux organisé.

Package requis

Mettre en place Mongoose

Ajout de Mongoose au module racine

En première étape, il faut définir l’url de MongoDB dans le fichier app.controller.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
import { DashboardsModule } from './dashboards/dashboard.module';
import { ColumnsModule } from './dashboardsColumn/columns.module';
import { CardsModule } from './dashboardsCard/cards.module';

@Module({
  imports: [
    //Mettre l'url ici
    MongooseModule.forRoot('mongodb://localhost:27017/votre-url'),
    DashboardsModule,
    ColumnsModule,
    CardsModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Création de votre model

Pour le contexte, c’est un dashboard Trello avec un nom, une description et des colonnes.

Ajoutez un fichier nom_model.model.ts et mettez le model de votre choix.

import * as mongoose from 'mongoose';

export const DashboardSchema = new mongoose.Schema({
  name: { type: String, required: true },
  description: { type: String, required: false },
  columns: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Column',
    },
  ],
});

export interface Dashboard extends mongoose.Document {
  id: string;
  name: string;
  description: string;
  columns: Array<object>;
}

Ajout du schéma dans le module avec les routes utilisées

Pour cette étape, nous allons en quelques sortes dire aux routes pour lesquelles nous souhaitons utiliser notre model, que nous pouvons utiliser ce model.

Ceci ce fait via la méthode forFeature du package @nestjs/mongoose, nous définissons le nom du model ainsi que son schéma importé.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { DashboardSchema } from './dashboard.model';
import { DashboardsController } from './dashboards.controller';
import { DashboardsService } from './dashboards.service';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'Dashboard', schema: DashboardSchema }]),
  ],
  controllers: [DashboardsController],
  providers: [DashboardsService],
})
export class DashboardsModule {}

Une fois cette étape faite, votre controller pourra utiliser ce schéma pour faire des requêtes à votre base de données MongoDB.

Injecter le model MongoDB dans votre service

Dernière étape nécessaire, il faut injecter le model MongoDB dans le constructeur de votre service.

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Dashboard } from './dashboard.model';
import { Model } from 'mongoose';

@Injectable()
export class DashboardsService {
  private dashboards: Dashboard[] = [];

  constructor(
    @InjectModel('Dashboard') private readonly dashboardModel: Model<Dashboard>,
  ) {}
}

Grâce à ca le model sera disponible une fois que vous utiliserez la super variable ‘this.’.

Tout est prêt pour utiliser Mongoose, il ne reste plus qu’a faire les routes et fonction de services qui permettront de faire les opérations de base de données.

Mise en place des routes de l’API

Routes dans le controller Nest.js

Une route Nest.js nécessite un décorateur (Par exemple @Get()) pour définir le verbe d’appel pour chaque route. Ils sont tous dans l’import de @nestjs/common.

Dans le constructeur il est également nécessaire d’avoir l’injection du service qui sera appelé.

import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Patch,
  Delete,
} from '@nestjs/common';
import { DashboardsService } from './dashboards.service';

@Controller('dashboard')
export class DashboardsController {
  constructor(private readonly dashboardService: DashboardsService) {}

  /**
   * Route de récupérations des dashboard
   * @returns Un tableau de dashboard
   */
  @Get()
  getDashboards() {
    const dashboards = this.dashboardService.getDashboards();
    return dashboards;
  }

  /**
   * Route qui permet de récupérer un dashboard via son id
   * @param dashboardId Id Mongodb du dashboard
   * @returns string
   */
  @Get(':id')
  getDashboard(@Param('id') dashboardId: string) {
    const dashboard = this.dashboardService.getSingleDashboard(dashboardId);
    return dashboard;
  }

  /**
   * Route d'ajout d'un dashboard
   * @param dashboardName Le nom du dashboard
   * @param dashboardDescription La description du dashboard
   * @returns L'id du dashboard crée
   */
  @Post()
  async createDashboard(
    @Body('name') dashboardName: string,
    @Body('description') dashboardDescription: string,
  ): Promise<string> {
    const generateId = await this.dashboardService.createDashboard(
      dashboardName,
      dashboardDescription,
    );
    return generateId;
  }

  /**
   * Route de modification du dashboard
   * @param dashboardId Id Mongodb du dashboard
   * @param dashboardName Nom du dashboard
   * @param dashboardDescription Description du dashboard
   * @returns null
   */
  @Patch()
  async updateDashboard(
    @Body('id') dashboardId: string,
    @Body('name') dashboardName: string,
    @Body('description') dashboardDescription: string,
  ) {
    this.dashboardService.updateDashboard(
      dashboardId,
      dashboardName,
      dashboardDescription,
    );

    return null;
  }

  /**
   * Route qui permet de supprimer un dashboard via son id
   * @param dashboardId Id Mongodb du dashboard
   * @returns string
   */
  @Delete(':id')
  deleteDashboard(@Param('id') dashboardId: string) {
    this.dashboardService.removeDashboard(dashboardId);
    return null;
  }
}

Utilisation du model dans le service

Voici toutes les opérations de CRUD pour utiliser Mongoose dans votre service.

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Dashboard } from './dashboard.model';
import { Model } from 'mongoose';

@Injectable()
export class DashboardsService {
  private dashboards: Dashboard[] = [];

  constructor(
    @InjectModel('Dashboard') private readonly dashboardModel: Model<Dashboard>,
  ) {}

  async getDashboards() {
    const dashboards = await this.dashboardModel.find().exec();
    return dashboards.map((dashboard) => ({
      id: dashboard.id,
      name: dashboard.name,
      description: dashboard.description,
    }));
  }

  getSingleDashboard(dashboardId: string) {
    const dashboard = this.findDashboard(dashboardId);
    return dashboard;
  }

  private async findDashboard(id: string): Promise<Dashboard> {
    const dashboard = await this.dashboardModel.findById(id).populate({
      path: 'columns',
      populate: { path: 'cards' },
    });
    if (!dashboard) {
      throw new NotFoundException('Could not find dashboard');
    }
    return dashboard;
  }

  async createDashboard(name: string, description: string): Promise<string> {
    const dashboard = new this.dashboardModel({
      name: name,
      description: description,
    });
    const result = await dashboard.save();
    this.dashboards.push(dashboard);
    return result.id;
  }

  async updateDashboard(
    id: string,
    name: string,
    description: string,
  ): Promise<Dashboard> {
    const result = await this.dashboardModel
      .findByIdAndUpdate(id, { name, description })
      .exec();
    console.log(result);
    return result;
  }

  async removeDashboard(id: string): Promise<void> {
    await this.dashboardModel.findByIdAndRemove(id);
  }
}

Lien du code et liens utiles

Annexe : Nest.js tutoriels officiel Mongoose

Si vous souhaitez avoir accès au code, le voici.

Autres tutoriels Node.js.