[PT-BR] CRUD com NestJS e MongoDB
Tags: tutorial, beginners, mongodb, node
Published: 2020-09-27 13:19:50

---
title: [PT-BR] CRUD com NestJS e MongoDB
published: true
description: Desenvolvendo um CRUD com NestJS e MongoDB.
tags: tutorial, beginner, mongodb, node
cover_image: https://mir-s3-cdn-cf.behance.net/project_modules/1400/fecece74182713.5c254609b1588.png
---

Uma das tecnologias que curto bastante estudar em meu tempo livre é Node. E, recentemente eu comecei a estudar um dos frameworks para se trabalhar com Node: [NestJS](https://nestjs.com/).

Por já ter trabalhado com alguns frameworks tanto no frontend quanto no backend, o Nest me lembrou bastante conceitos que usamos quando trabalhamos com [Angular](https://angular.io/) e [Spring Boot](https://spring.io/projects/spring-boot), como por exemplo Módulos, Filtros e Decorators.

Por já ter estudado um pouco desse framework e gostado bastante, gostaria de explicar aqui como seria possível fazer uma API para um CRUD básico. Dessa forma, se você estiver querendo se familiarizar com a ferramenta ou como trabalhar com Node, você pode ter mais uma referência.

Em relação ao banco de dados, irei utilizar o [MongoDB](https://www.mongodb.com/).

**A referência que utilizarei para esse artigo será a [documentação do Nest](https://docs.nestjs.com/) e a [documentação do Mongoose](https://mongoosejs.com/).**


Vamos lá!


## Mão na massa

### Pré requisitos

Para começarmos a instalar o Nest e desenvolvermos a nossa aplicação, é necessário que tenhamos o Node instalado na nossa máquina. Para instalá-lo, podemos acessar esse [link](https://nodejs.org/).

Para o desenvolvimento desse artigo eu estou utilizando o Node na versão 12.18.4 LTS.

### Instalando CLI

Primeiro, vamos instalar o CLI do NestJS na nossa máquina. Em algum terminal de linha de comando, execute o seguinte comando:

```bash
## No Linux ou Mac
$ sudo npm install -g @nestjs/cli

## No Windows
$ npm install -g @nestjs/cli
```

### Criando projeto

Já com o CLI do Nest instalado na nossa máquina, temos que ir até o diretório que desejamos criar o projeto e executar o seguinte comando:

```bash
$ nest new <nome-do-projeto>
```

*Substitua o `<nome-do-projeto>` para o nome que desejar.*

O próprio CLI irá nos perguntar se nós desejamos instalar as dependências do nosso projeto com o NPM ou com Yarn. Fique a vontade para escolher qualquer um dos dois.

### Executando o projeto

Após ter criado o projeto e entrado na pasta do projeto, via algum terminal de linha de comando, execute o seguinte comando para executar o projeto:

```bash
$ npm start
```

Se desejar executar o projeto em modo *watch*, execute o seguinte comando:

```bash
$ npm run start:dev
```

### Configurando a conexão ao banco de dados

Fique à vontade para escolher como desejará criar o banco de dados: seja ele na nossa própria máquina ou em algum lugar remoto. A nossa aplicação precisará apenas da *string de conexão* ao banco de dados.

#### Instalando dependências para trabalhar com MongoDB

Para trabalharmos com MongoDB em um projeto com Nest, existem algumas bibliotecas obrigatórias que precisaremos instalar no nosso projeto:

```bash
## Para trabalharmos com MongoDB em um projeto Nest
$ npm install mongoose @nestjs/mongoose

## Para termos o suporte do Typescript ao trabalharmos com o Mongoose
$ npm install -D @types/mongoose
```

#### Configurando a string de conexão

Com as bibliotecas instaladas, precisaremos configurar nossa aplicação para que ela possa se conectar ao MongoDB. Faremos isso ao abrir o arquivo `src/app.module.ts` e incluirmos o módulo do Mongoose, definindo a nossa string de conexão do banco:

```ts
import { Module } from '@nestjs/commons';

// Importamos o módulo do Mongoose
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  // Suponhamos aqui que a nossa string de conexão seja 'mongodb://localhost/nest'
  imports: [MongooseModule.forRoot('mongodb://localhost/nest')]
})

export class AppModule()
```

> ### Dica
> Se precisarmos salvar esse código em algum repositório público, como Github, Gitlab e outros, sugiro a utilização da biblioteca [dotenv](https://www.npmjs.com/package/dotenv). Com ela, poderemos definir variáveis de ambiente para o nosso projeto e, por conta disso, poderemos salvar nossa string de conexão ao banco como uma variável de ambiente. Assim não precisaremos expor esse e algum outro dado sensível da nossa aplicação.

Ao fazer isso, teoricamente, deveremos ter acesso ao banco de dados através da nossa aplicação.

Tente executar a aplicação nesse momento. Se houver algum problema ao tentar se conectar ao banco, surgirá uma mensagem em vermelha no seu terminal (onde tivermos executado o comando para executar a aplicação) dizendo que não foi possível se conectar ao banco de dados:
![Erro ao tentar se conectar ao banco de dados](https://i.imgur.com/sgL4K8R.png)

### Nosso Model

Para quem não trabalhou com o Mongoose ainda, entenda que, basicamente, tudo que ele trabalha é derivado de um *Schema*. Os Schemas que ele trabalha irão mapear as nossas classes para um formato de Coleção e de seus respectivos Documentos no MongoDB. 

De uma forma geral, eu gosto de interpretar que os Schemas, no Mongoose, fazem analogia à forma que trabalhamos com as Models em outros ORMs tradicionais para banco de dados relacionais.

Utilizando do sugestivo tema do Nest e, também, para exemplificar melhor o entendimento da construção da nossa API, vamos trabalhar com Gato.

Antes de criarmos nosso Schema, vamos organizar o nosso contexto de API em um módulo. Ou seja, a API específica para Gato que iremos criar estará toda organizada em um módulo. E, para isso, vamos executar o seguinte comando no nosso terminal:

```bash
$ nest generate module gatos
```

Após a execução desse comando, repare que foi criado um subdiretório chamado `gatos` dentro do diretório `src` do nosso projeto. Dentro dele irá conter o arquivo `gatos.module.ts`. Repare também que o nosso GatoModule (nome do módulo que foi criado) já foi importado no nosso arquivo `AppModule`.

#### Criando o Schema Gato

Agora que já criamos o nosso módulo, vamos criar nosso Schema. Vamos gerar a nossa classe através do CLI do Nest - igual fizemos com o nosso GatoModule - e vamos transformá-la em um Schema.

Para criar a nossa classe Gato, vamos executar o seguinte comando:

```bash
$ nest generate class gatos/gato
```

Repare que arquivo `gato.ts` foi criado dentro do subdiretório gato e seu respectivo arquivo de teste também foi criado, o `gato.spec.ts`.

O conteúdo do arquivo `gato.ts`, no momento, é basicamente o `export` da classe. Para transformarmos ela em um Schema para que o Mongoose consiga mapeá-la no MongoDB, precisaremos fazer o seguinte: estender a classe `Document` do Mongoose. E, também, precisamos adicionar o Decorator `@Schema()` em cima da declação da nossa classe Gato.

As modificações deixarão o arquivo com o seguinte conteúdo:

```ts
import { Schema } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema()
export class Gato extends Document {}
```

Vamos adicionar algumas propriedades à nossa classe Gato, utilizando o Decorator `@Prop()` da biblioteca `@nestjs/mongoose`:

```ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema()
export class Gato extends Document {

  @Prop()
  nome: string;

  @Prop()
  idade: number;

  @Prop()
  raca: string;
}
```

O Decorator `@Schema()`, por si só, não irá criar de fato um Schema. O papel dele é apenas marcar a nossa classe como algo que poderá ser mapeado, no banco de dados, como uma Coleção. É válido dizer que da forma que definimos ele, sem passar nenhum parâmetro, ele irá mapear essa classe como uma Coleção e irá adicionar o `s` no final do nome da Coleção no banco de dados. Ou seja, ele irá entender que essa classe está relacionada à coleção `Gatos` no banco de dados.

Agora, para criarmos de fato o Schema baseado nessa classe e exportá-lo, precisamos adicionar a seguinte instrução no final do nosso arquivo `gato.ts`:

```ts
export const CatSchema = SchemaFactory.createForClass(Gato);
```

A classe `SchemaFactory` deve ser importada da biblioteca `@nestjs/mongoose`. Nosso arquivo final ficará da seguinte forma:

```ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema()
export class Gato extends Document {

  @Prop()
  nome: string;

  @Prop()
  idade: number;

  @Prop()
  raca: string;
}

export const GatoSchema = SchemaFactory.createForClass(Gato);
```

#### Registrando o Schema Gato

Agora que criamos a nossa classe e o nosso schema, precisamos registrá-los no nosso módulo Gato e no módulo do Mongoose. Dessa forma, o Mongoose entenderá que a nossa classe e o nosso schema estarão definidos nesse escopo somente.

Então, para fazermos esse registro, vamos definir o conteúdo do nosso `GatoModule` dessa forma:

```ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { Gato, GatoSchema } from './gato';

@Module({
  imports: [
    MongooseModule.forFeature([
      {
        name: Gato.name,
        schema: GatoSchema
      }
    ])
  ],
  exports: [],
  controllers: [],
  providers: []
})
export class GatosModule {}

```

Se caso quisermos utilizar a nossa classe e schema em algum outro módulo, precisamos apenas adicionarmos o `MongooseModule` dentro do array `exports` e, no módulo que formos utilizar a classe e/ou o schema, adicionarmos o `MongooseModule` no array `imports`.


### Criando o Service Gato

A classe responsável por "conversar" com o MongoDB através do Mongoose, será o nosso `GatosService`. Para criarmos esse Service, precisamos executar o seguinte comando:

```bash
$ nest generate service gatos
```

Ao executar esse comando, dois arquivos serão criados no subdiretório `src/gatos`: o `gatos.service.ts` e `gatos.service.spec.ts`. O comando também irá adicionar o `GatosService` como um provider no `GatosModule`.

Para o nosso CRUD, iremos definir os seguintes métodos e suas respectivas implementações:

```ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Gato } from './gato';

@Injectable()
export class GatosService {
  constructor(
    @InjectModel(Gato.name) private gatoModel: Model<Gato>
  ) {}

  async listarTodos(): Promise<Gato[]> {
    return this.gatoModel.find().exec();
  }

  async criar(gato: Gato): Promise<Gato> {
    const gatoCriado = new this.gatoModel(gato);

    return gatoCriado.save();
  }

  async buscarPorId(id: string): Promise<Gato> {
    return this.gatoModel.findById(id).exec();
  }

  async atualizar(id: string, gato: Gato): Promise<Gato> {
    return this.gatoModel.findByIdAndUpdate(id, gato).exec();
  }

  async remover(id: string) {
    const gatoApagado = this.gatoModel.findOneAndDelete({ _id: id }).exec();

    return (await gatoApagado).remove();
  }
}

```

#### Sobre o construtor do Service

A biblioteca `@nestjs/mongoose` nos fornece um meio para que possamos trabalhar com determinado Documento através da injeção de dependências com o Decorator `@InjectModel`. Para esse Decorator, precisamos apenas passar o nome da classe que foi marcada com o Decorator `@Schema()`.

#### Sobre os métodos de CRUD

As implementações dos métodos podem variar de acordo com os tratamentos que acreditamos que sejam necessários. Para fins de praticidade, eu os implementei da forma acima. Porém, para um CRUD mais elaborado, é válido que seja aplicado alguns tratamentos de segurança e prevenção de erros (por exemplo: verificar se o objeto existe no banco de dados antes de tentarmos alterá-lo).


### Criando o Controller Gato

A classe responsável por receber as requisições HTTP para trabalhar com o nosso CRUD de Gato será o nosso `GatosController`. Para criarmos esse Service, precisamos executar o seguinte comando:

```bash
$ nest generate controller gatos
```

Ao executar esse comando, o arquivo `gatos.controller.ts` será criado no subdiretório `src/gatos`. O comando também irá adicionar o `GatosController` como um controller no `GatosModule`.

A implementação do nosso GatosController será a seguinte:

```ts
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { Gato } from './gato';
import { GatosService } from './gatos.service';

@Controller('gatos')
export class GatosController {
  constructor(
    private readonly gatosService: GatosService
  ) {}

  @Get()
  async listarTodos(): Promise<Gato[]> {
    return this.gatosService.listarTodos();
  }

  @Post()
  async criar(@Body() gato: Gato): Promise<Gato> {
    return this.gatosService.criar(gato);
  }

  @Get(':id')
  async buscarPorId(@Param('id') id: string): Promise<Gato> {
    return this.gatosService.buscarPorId(id);
  }

  @Put(':id')
  async atualizar(@Param('id') id: string, @Body() gatoAtualizado: Gato): Promise<Gato> {
    return this.gatosService.atualizar(id, gatoAtualizado);
  }

  @Delete(':id')
  async remover(@Param('id') id: string): Promise<Gato> {
    return this.gatosService.remover(id);
  }

}

```

#### Sobre o construtor do Controller

Assim como no nosso `GatosService`, o construtor do `GatosController` utilizará da injeção de dependência para que possamos acessar os métodos do nosso `GatosService`.

#### Sobre os Decorators do Controller

O Nest nos provê diversos Decorators que devemos utilizar em nossos Controllers.

Primeiro, para que possamos marcar uma classe como Controller, precisamos adicionar o Decorator `@Controller()` em cima da declaração da classe. Como argumento opcional, podemos passar um prefixo de rota para que possamos agrupar as rotas dessa classe em um mesmo caminho. Ou seja, de acordo com a implementação acima, todas as rotas que implementarmos nessa classe terá o prefixo `/gatos`.

Também temos Decorators para os métodos HTTP das nossas rotas:
- Para requisições com o método GET precisamos definir o Decorator `@Get()`;
- Para requisições com o método POST precisamos definir o Decorator `@Post()`;
- Para requisições com o método PUT precisamos definir o Decorator `@Put()`;
- Para requisições com o método DELETE precisamos definir o Decorator `@Delete()`.

Cada um desses Decorators de métodos HTTP podem receber um parâmetro que definirão os parâmetros da rota. No nosso exemplo, definimos apenas o parâmetro `:id` em algumas das nossas rotas.

Quando definimos um parâmetro de rota, podemos ter acesso a ele através do Decorator `@Param()` como argumento de nosso método. Apenas precisamos passar o nome do parâmetro que queiramos ter acesso. No nosso exemplo, definimos o parâmetro `:id`.

Para as rotas que esperamos um conteúdo no corpo da nossa requisição, utilizamos o Decorator `@Body()` como argumento de nosso método. Dessa forma, teremos acesso ao objeto que estará contido no corpo da nossa requisição através do argumento relacionado ao Decorator `@Body()`.

### Acessando nossa aplicação

Após configurarmos a conexão ao MongoDB, criarmos nossa Model e Schema, criarmos nosso Service e nosso Controller, já conseguiremos fazer o uso dessa aplicação.

Para acessar as rotas do nosso CRUD, inicie a aplicação e faça as requisições nas rotas que foram criadas no nosso Controller.

> ### Dica
>Ao iniciar a aplicação com as rotas criadas, no mesmo terminal que você executou o comando de execução da aplicação serão listadas as rotas que a aplicação já estará esperando por requisições:
>
>![Log de rotas no terminal que a aplicação foi executada](https://i.imgur.com/Kg0Wkn3.png)

## Finalizando

A aplicação que foi criada nesse artigo pode ser encontrada nesse [link](https://github.com/LeonardoSSev/post-nest-crud). Em caso de dúvidas, estou sempre aberto a sugestões, críticas e ideias! o/