DEV Community

Cover image for Cursus NestJS - Les Controllers
WebeleonFR for Webeleon

Posted on

Cursus NestJS - Les Controllers


Bienvenue dans ce nouveaux cours du cursus NestJS par Webeleon.
Je m'appel Julien et je serais ton guide tout au long de cette aventure qui fera de toi un véritable expert Nest!
Alors abonne toi pour progresser rapidement!

Aujourd'hui on va parler controlleur!

Un Contrôlleurs mais qu'est ce que c'est?

Un controlleur c'est une classe exporté sur laquelle on applique le décorateur @Controller importé depuis @nestjs/common.

import { Controller } from "@nestjs/common";

@Controller('optional-path-prefix')
export class MyController {

}
Enter fullscreen mode Exit fullscreen mode

Qui est ensuite déclaré dans le module le plus proche dans la section controllers.

import { MyController } from './my-controller.controller.ts';

@Module({
  controllers: [ MyController ], 
})
export class MyModule {}
Enter fullscreen mode Exit fullscreen mode

A quoi ça sert?

Un controlleur vas être utilisé pour créer des routes pour notre API.
C'est ensuite au controlleurs de faire un premier tour de validation, je te montre ça dans la prochaine vidéo.
Et d'appeler les services et autres providers qui s'occuperons de la logique métier.
Pas de stress je te montre ça bientôt. ❤️
Il est aussi de bon gout que le controlleur soit en charge de la sérialisation des erreurs et des réponses qui seront envoyé au consommateur de l'api.

Créer un controller via la ligne de commande

nest generate controller <nom>
Enter fullscreen mode Exit fullscreen mode

Assez de théorie passons sur un cas pratique

Je te propose de créer rapidement une API REST pour gérer un collection de livre.

  • [GET] /book liste les livres dans notre collection.
  • [GET] /book/:id affiche les détails d'un livre. Une erreur 404 sera retourné si l'id du livre n'éxiste pas.
  • [POST] /book crée un livre dans notre collection
  • [PUT] /book/:id mise à jour d'un livre dans la collection. Une erreur 404 sera retourné si l'id du livre n'éxiste pas.
  • [DELETE] /book/:id suppression d'un livre dans la collection. Une erreur 404 sera retourné si l'id du livre n'éxiste pas.

Création du projet

Commence par créer un nouveau projet NestJS appelé book-api.

nest new book-api
cd book-api
Enter fullscreen mode Exit fullscreen mode

maintenant génére le controlleur via la ligne commande

nest generate controller book
Enter fullscreen mode Exit fullscreen mode

Si tout s'est bien passé tu es sensé avoir ces lignes dans ton terminal.

CREATE src/book/book.controller.spec.ts (478 bytes)
CREATE src/book/book.controller.ts (97 bytes)
UPDATE src/app.module.ts (322 bytes)
Enter fullscreen mode Exit fullscreen mode

[GET] liste de la collection de livre

Il est temps d'éditer le fichier book/book.controller.ts.

Créer une route, c'est créer une méthode dans classe BookController.
Sur laquelle tu applique une décorateur de routage (@Get, @post, @Put, @Delete, ...).

Commençons par la liste, pour cela il faut créer un tableau comme attribut de classe et la méthode liste sur laquelle il faut appliquer le décorateur @Get() qui viens de @nestjs/common.

import { Controller, Get } from '@nestjs/common';

@Controller('book')
export class BookController {
  books: string[] = [
    'Apprendre NestJS, c\'est facile',
  ];

  @Get()
  list() {
    return this.books;
  }
}
Enter fullscreen mode Exit fullscreen mode

Tu peux maintenant lancer ton projet nest (via la commande npm run start:dev).
Pour tester plusieurs solution s'offre à toi:

  • dans ton naivagateur à l'addresse http://localhost:3000/book (attention cela ne fonctionne que pour les routes en get)
  • Via la commande curl si tu l'as d'installer curl http://localhost:3000/book
  • via un outils comme postman

Pour éviter de refaire ce même test manuellement, l'ideal est d'ajouter un test end to end pour automatiser.
Histoire de rester simple, ajoute une cas de test dans le fichier test/app.e2e-spec.ts.

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {

  // ... bootstrap et test par défaut

  it('/book [GET]', () => {
    return request(app.getHttpServer())
      .get('/book')
      .expect(200)
      .expect([]);
  });
});

Enter fullscreen mode Exit fullscreen mode

Super! tu sais afficher la liste par défaut!

[GET] Récupérer un livre par son index

Tu vas pouvoir ajouter la méthode pour afficher un livre par son index dans le tableau this.books.
C'est presque la même chose, tu cré une méthode getBookByIndex sur laquelle tu applique le décorateur @Get('/:index').
En passant une chaine de caractére pour définir la suite de la route cela donnera au final /book/:index.
:index signifie qu'il y a une variable dans l'url que l'on peu récupérer via le décorateur @Param('index') dans la signature de la méthode getBookById(@Param('index') index: string).

L'implémentation de la méthode est assez simple ici:

  • on regarde si l'index existe dans le tableau, si ce n'est pas le cas on envoie une erreur 404 en utilisant l'exception NotFoundException
  • on envoie ce qu'il y a dans le tableau à l'index demandé
import { Controller, Get, NotFoundException, Param } from '@nestjs/common';

@Controller('book')
export class BookController {

  // code dans l'exemple précédent!

  @Get('/:index')
  getBookByIndex(
    @Param('index') index: string,
  ) {
    if (!this.books[index]) {
      throw new NotFoundException(`Book with index ${index} does not exist!`);
    }
    return this.books[index];
  }
}
Enter fullscreen mode Exit fullscreen mode

Pour tester, même techniques qu'avant mais sur les routes

  • http://localhost:3000/book/0
  • http://localhost:3000/book/1

Ajoute ces deux cas de test fonctionnel dans le fichier test/app.e2e-spec.ts pour automatiser le test.

// import dans l'exemple précédent

describe('AppController (e2e)', () => {

  // cas de test de l'exemple précédent

  it('/book/:index [GET][200]', () => {
    return request(app.getHttpServer())
      .get('/book/0')
      .expect(200)
      .expect('le secret de ji');
  });

  it('/book/:index [GET][404]', () => {
    return request(app.getHttpServer()).get('/book/1').expect(404);
  });
});

Enter fullscreen mode Exit fullscreen mode

Top! On liste la collection et on affiche un élément, c'est un bon début mais pas encore suffisant!

[POST] Ajouter un livre

Il est temps maintenant de créer la méthode pour ajouter un livre dans notre collection.

Tu vas devoir créer une méthode addBook sur laquelle tu applique le décorateur @Post() qui viens encore et toujours de @nestjs/common.
Pour récupérer un élément du body de la requéte il faut utiliser le décorateur @Body(), il carrément possible de récupérer un attribut spécifique en le passant en paramétre.

import { Body, Controller, Get, NotFoundException, Param, Post } from '@nestjs/common';

@Controller('book')
export class BookController {

  // voir exemples précédent

  @Post()
  addBook(
    @Body('title') book: string
  ) {
    this.books.push(book)
    return book
  }
}
Enter fullscreen mode Exit fullscreen mode

On test, cette fois le navigateur n'est pas une option car il envoie des requétes GET et il nous faut un POST en utilisant postman ou CURL.

curl --location --request POST 'http://localhost:3000/book' \
--header 'Content-Type: application/json' \
--data-raw '{
    "title": "test"
}'
Enter fullscreen mode Exit fullscreen mode
// import dans les exemples précédents

describe('AppController (e2e)', () => {

  // mise en place et test dans les exemples précédents

  it('/book [POST][201]', async () => {
    await request(app.getHttpServer())
      .post('/book')
      .send({
        title: 'Super book',
      })
      .expect(201);

    await request(app.getHttpServer())
      .get('/book')
      .expect(200)
      .expect(['le secret de ji', 'Super book']);
  });
});
Enter fullscreen mode Exit fullscreen mode

[PUT] mise à jour d'un livre par index

Et c'est partie pour la mise à jour d'un livre!

On retrouve une fois encore la logique de précédente.
Tu crée une méthode updateBook sur laquelle tu applique le décorateur @Put('/:index').
Il faut récupérer le title dans le body et le l'index du livre dans les paramétres de route.
Comme dans le getByIndex on vérifie l'existence du livre, on fait la mise à jour ou on renvoi un erreur 404.

import { Body, Controller, Get, NotFoundException, Param, Post, Put } from '@nestjs/common';

@Controller('book')
export class BookController {

  // exemples précédent camarade!

  @Put('/:index')
  updateBook(
    @Param('index') index: string,
    @Body('title') book: string,
  ) {
    if (!this.books[index]) {
      throw new NotFoundException(`Book with index ${index} does not exist!`)
    }
    this.books[index] = book;
    return book;
  }
}
Enter fullscreen mode Exit fullscreen mode

Encore un petit curl pour toi mon ami!

curl --location --request PUT 'http://localhost:3000/book/0' \
--header 'Content-Type: application/json' \
--data-raw '{
    "title": "Nest c'\''est facile v2"
}'
Enter fullscreen mode Exit fullscreen mode

Et voici le test end 2 end

// ...

describe('AppController (e2e)', () => {

  // ...

  it('/book/index [PUT][200]', async () => {
    await request(app.getHttpServer())
      .put('/book/0')
      .send({
        title: 'nouveau titre',
      })
      .expect(200);

    await request(app.getHttpServer())
      .get('/book/0')
      .expect(200)
      .expect('nouveau titre');
  });

  it('/book/index [PUT][404]', () => {
    return request(app.getHttpServer()).put('/book/1').expect(404);
  });
});
Enter fullscreen mode Exit fullscreen mode

[DELETE] supprimer un livre

On à presque finis, il ne reste plus qu'à faire la suppression.
Donc, pour la suppression, une méthode deleteBook sur laquelle tu applique le décorateur @Delete('/:id').
Tu vérifie que le livre existe, si c'est le cas il faut le supprimer du tableau.

import { Body, Controller, Delete, Get, NotFoundException, Param, Post, Put } from '@nestjs/common';

@Controller('book')
export class BookController {

  // voir plus haut ;)

  @Delete('/:index')
  deleteBook(
    @Param('index') index: string
  ) {
    if (!this.books[index]) {
      throw new NotFoundException(`Book with index ${index} does not exist!`)
    }

    delete this.books[index];

    return 'deleted';
  }
}
Enter fullscreen mode Exit fullscreen mode

Un curl en cadeau pour tester:

curl --location --request DELETE 'http://localhost:3000/book/0'
Enter fullscreen mode Exit fullscreen mode

Et le test fonctionnel!

// ...

describe('AppController (e2e)', () => {

  // ...

  it('/book/index [DELETE][200]', async () => {
    await request(app.getHttpServer()).delete('/book/0').expect(200);
    await request(app.getHttpServer()).get('/book/0').expect(404);
  });

  it('/book/index [DELETE][404]', () => {
    return request(app.getHttpServer()).delete('/book/1').expect(404);
  });
});
Enter fullscreen mode Exit fullscreen mode

Et voila! on a terminé ce petit controlleur REST.

Conclusion

Laisse-moi tes questions en commentaire, je me ferai un plaisir d'y répondre.

Un exemple complet du TP est disponible sur github https://github.com/Webeleon/Cursus-NestJS-les-controllers

Ou viens directement les poser sur le serveur discord webeleon!

La prochaine fois nous traiterons de la validation des entrées alors abonne-toi pour progresser rapidement sur NestJS!

Discussion (0)