This post is also available in: English

Modelar la arquitectura para una API Express Rest puede ser una tarea desalentadora. Cuanto más grande sea su aplicación, más difícil será mantener su base de código debido a todas las configuraciones posibles. Afortunadamente, hay un framework llamado NestJS que toma muchas de las convenciones de Angular, las coloca en la parte superior de Express JS y las usa para crear una arquitectura de aplicación más consistente. Muchos de los beneficios incluyen formas consistentes de manejar la documentación usando swagger, verificación de tipos e inyección de dependencia con Typescript y compatibilidad con la mayoría de todos los middleware Express. En este tutorial, vamos a repasar cómo construir una aplicación CRUD simple con documentación Swagger usando NestJS y Swagger. si usted está interesado en ver un video tutorial actualizado, por favor suscríbase aquí.

Primero, asegurémonos de que NestJS cli esté instalado y creemos una nueva aplicación. Esto hace que sea más fácil crear los diferentes componentes necesarios para NestJS.

npm i -g @nestjs/cli

nest new todo-api

Si está familiarizado con Angular, verá que hay un proyecto similar con modelos, servicios y controladores.

NestJs Project Structure
NestJs Project Structure

Agregar dependencias y configuración de base de datos

Si sabe qué dependencias va a usar, lo mejor es agregarlas al principio.

npm install -s @nestjs/mongoose mongoose @nestjs/swagger

npm install --save-dev @types/mongoose

Estos instalarán las dependencias necesarias para la funcionalidad mangosta y swagger.

Usando Mongo DB con Mongoose.js

A continuación, vamos a configurar la base de datos. Dentro de su app.module.ts, configure la conexión inicial.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest'),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

También queremos eliminar los archivos AppController y AppService porque no los vamos a utilizar.

Creando nuestro Dominio Todos

Queremos crear un área específica para nuestro dominio todos. Esto incluye el esquema de base de datos, nuestro controlador y DTO (objeto de transferencia de datos)

En la línea de comandos, ejecute

nest generate module todos 
nest generate controller todos
nest generate service todos

Cuando genere el servicio, estará en la carpeta principal, muévalo a la carpeta todos.

Esquema de base de datos de Mongoose

Ahora debemos crear nuestro esquema de base de datos de mangosta y registrarlo con nuestro módulo. Dentro de la carpeta todos, cree otra carpeta llamada esquemas y cree un archivo todo.schema.ts.

import * as mongoose from 'mongoose';

export const TodoSchema = new mongoose.Schema({
    text: String,
    complete: Boolean,
});

Cree una carpeta de interfaces con un archivo itodos.service.ts y todos.inteface.ts e index.ts El archivo idotos.service.ts especifica las llamadas a su almacén de datos que utilizará en su servicio. El uso de esta interfaz es bueno en caso de que cambie de una base de datos NoSQL a SQL o cambie su proveedor de base de datos. El archivo todos.interface.ts especifica el tipo de documento que son sus todos. En nuestro caso, hay documentos MongoDB creados usando Mongoose. Luego, en el archivo index.ts puede exportarlos ambos y usarlos más adelante con la destrucción.

import { Document } from 'mongoose';

export interface ITodo extends Document {
    readonly text: string;
    readonly complete: boolean;
}
import { ITodo } from './todos.interface';

export interface ITodosService {
    findAll(): Promise<ITodo[]>;
    findById(ID: number): Promise<ITodo | null>;
    findOne(options: object): Promise<ITodo | null>;
    create(todos: ITodo): Promise<ITodo>;
    update(ID: number, newValue: ITodo): Promise<ITodo | null>;
    delete(ID: number): Promise<string>;
}
export * from './itodos.service';
export * from './todos.interface';

También crearemos un CreateTodo.dto.ts en la carpeta dto. Importaremos apimodelProperty desde @nestjs .swagger para que cuando generemos nuestra documentación de Swagger, Swagger sepa cuáles son las diferentes propiedades de nuestro modelo.

import { ApiModelProperty } from '@nestjs/swagger';

export class CreateTodoDto {
    @ApiModelProperty()
    readonly _id: number;

    @ApiModelProperty()
    readonly text: string;

    @ApiModelProperty()
    readonly complete: boolean;
}

Creación de nuestro servicio de base de datos

Necesitaremos una forma de obtener acceso a los métodos ORM de Mangoose para nuestro Documento Todo de una manera que se ajuste a DRY (No repetirse a ti mismo). La mejor manera de hacerlo es crear un servicio que nos permita llamar a consultas de bases de datos reutilizables que se pueden pasar alrededor de nuestra aplicación. NestJS permite la inyección de dependencia usando el decorador inyectable. De esta manera, podemos tomar una clase y colocar una instancia de ella para ser utilizada dentro de otra clase. Dentro de fuera TodosService, importe mangosta así como sus modelos e interfaces de servicio. Además, asegúrese de que los módulos a que permiten la inyección de dependencia en su clase de servicio.

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ITodo, ITodosService } from './interfaces/index';
import { CreateTodoDto } from './dto/createTodo.dto';
import { debug } from 'console';

A continuación, haga que su clase de servicio sea inyectable con el decorador @Injectable, agregue la interfaz al servicio para asegurarse de que todas las consultas se ajusten a las funciones básicas crud e inyecte nuestra interfaz iToDo.

@Injectable()
export class TodosService implements ITodosService{
    constructor(@InjectModel('Todo') private readonly todoModel: Model<ITodo>) { }
}

A continuación, cree todas sus funciones básicas crud usando las consultas de base de datos MongoDB.

async findAll(): Promise<ITodo[]> {
        return await this.todoModel.find().exec();
    }

    async findOne(options: object): Promise<ITodo> {
        return await this.todoModel.findOne(options).exec();
    }

    async findById(ID: number): Promise<ITodo> {
        return await this.todoModel.findById(ID).exec();
    }
    async create(createTodoDto: CreateTodoDto): Promise<ITodo> {
        const createdTodo = new this.todoModel(createTodoDto);
        return await createdTodo.save();
    }

    async update(ID: number, newValue: ITodo): Promise<ITodo> {
        const todo = await this.todoModel.findById(ID).exec();

        if (!todo._id) {
            debug('todo not found');
        }

        await this.todoModel.findByIdAndUpdate(ID, newValue).exec();
        return await this.todoModel.findById(ID).exec();
    }
    async delete(ID: number): Promise<string> {
        try {
            await this.todoModel.findByIdAndRemove(ID).exec();
            return 'The todo has been deleted';
        }
        catch (err){
            debug(err);
            return 'The todo could not be deleted';
        }
    }

Construyendo el controlador NestJS con documentación de Swagger

A continuación, vamos a querer construir nuestro controlador a cabo.

Necesitaremos importar tipos de solicitud, nuestro TodosService y nuestro DTO, así como nuestros decoradores para nuestra documentación de Swagger

import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete } from '@nestjs/common';
import { TodosService } from './todos.service';
import { CreateTodoDto} from './dto/createTodo.dto';
import { ApiUseTags, ApiResponse } from '@nestjs/swagger';

.

A continuación, etiquetaremos nuestro controlador con nuestro BaseURL ‘todos’, así como nuestra etiqueta para swagger

@ApiUseTags('todos')
@Controller('todos')
export class TodosController {
}

Para acceder a nuestro almacén de datos MongoDB, tendremos que inyectar nuestro TodosService.

constructor(private readonly todosService: TodosService) {}

Ahora podemos crear nuestra ruta que se asigna a nuestro TodosService, cada decorador anota qué tipo de solicitud se está haciendo y la ruta URL.

@Get()
    public async getTodos(@Response() res) {
        const todos = await this.todosService.findAll();
        return res.status(HttpStatus.OK).json(todos);
    }

    @Get('find')
    public async findTodo(@Response() res, @Body() body) {
        const queryCondition = body;
        const todos = await this.todosService.findOne(queryCondition);
        return res.status(HttpStatus.OK).json(todos);
    }

    @Get('/:id')
    public async getTodo(@Response() res, @Param() param){
        const todos = await this.todosService.findById(param.id);
        return res.status(HttpStatus.OK).json(todos);
    }

    @Post()
    @ApiResponse({ status: 201, description: 'The record has been successfully created.' })
    @ApiResponse({ status: 403, description: 'Forbidden.' })
    public async createTodo(@Response() res, @Body() createTodoDTO: CreateTodoDto) {

        const todo = await this.todosService.create(createTodoDTO);
        return res.status(HttpStatus.OK).json(todo);
    }

    @Patch('/:id')
    public async updateTodo(@Param() param, @Response() res, @Body() body) {

        const todo = await this.todosService.update(param.id, body);
        return res.status(HttpStatus.OK).json(todo);
    }

    @Delete('/:id')
    public async deleteTodo(@Param() param, @Response() res) {

        const todo = await this.todosService.delete(param.id);
        return res.status(HttpStatus.OK).json(todo);
    }

Registrar componentes en nuestro módulo NestJS

Ahora que hemos creado todos nuestros componentes, tendremos que registrar todo con NestJS para que funcionen. A pesar de que podemos registrar todo dentro de nuestro AppModule, lo mejor es registrarlos dentro de allí respectiva clase de módulo específico del dominio.

Dentro de su TodosModule, importe y agregue esquema, controlador y servicio.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { TodosController } from './todos.controller';
import { TodosService } from './todos.service';
import { TodoSchema } from './schemas/todo.schema';
@Module({
    imports: [MongooseModule.forFeature([{ name: 'Todo', schema: TodoSchema }])],
    controllers: [TodosController],
    providers: [TodosService],
})
export class TodosModule {}

Notará que estamos importando el esquema MongoDB junto con darle un nombre ‘Todo’

Este nombre también se usó dentro de su TodosService

@InjectModel('Todo')

Esto solo le dice a NestJS que haga ese modelo al esquema MongoDB

Ahora podemos volver a nuestro módulo de aplicaciones y empezar a conectar todo.

import { Module, NestModule } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { TodosModule } from './todos/todos.module';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest'),
    TodosModule,
],
  controllers: [],
  providers: [],
})
export class AppModule {}

Esto registra todos nuestros controladores y servicios dentro de nuestro TodosModule con nuestro AppModule principal.

Ahora vaya a su archivo main.ts y registre Swagger.

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Todos example')
    .setDescription('The Todos API description')
    .setVersion('1.0')
    .addTag('todos')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

Esto configurará swagger con la url /api. Una vez que todo haya terminado, ahora puede ejecutar su aplicación con

npm start
Todo Swagger Api
Todo Swagger Api

Conclusión

NestJS ofrece varias ventajas sobre la implementación estándar de ExpressJS. Ofrece una estructura consistente, la comprobación de tipos más fácil integración con middleware expreso. sigue un conjunto de convenciones que hacen que la aplicación sea fácil de seguir. En el siguiente tutorial, vamos a repasar la adición de autenticación JWT usando Passport. Puede ver el repositorio de GitHub aquí y los cursos de video aquí si está interesado en obtener más información.

Codebrains Newsletter

Get weekly dev news and tutorials.

Powered by ConvertKit

This post is also available in: English