AluraChallenges #2 ( Semana 1)
Tags: typescript, nestjs, typeorm, node
Published: 2021-08-25 13:31:31

No post anterior, nós configuramos tudo e agora é hora de realmente começarmos a trabalhar na solução.

Bom, nosso objetivo é implementar uma api que retorne uma playlist de videos favoritos, então, vamos começar criando o recurso de videos.




### API com rotas implementadas segundo o padrão REST


O Nest tem um comando que já faz a criação de toda a estrutura de um CRUD em uma tacada só, vamos usar ele.
```bash
nest generate resource videos
```
Ele vai começar a fazer as perguntas para gerar nosso recurso:
nessa primeira, escolheremos "REST API" e na segunda "YES"
![Nest_CLI1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ga39g0s7w0ipz92l3mwv.png)
Pronto! ele criou todo o esqueleto do recurso que iremos implementar e atualizou app.module, inserindo o módulo de videos e já deixou todas as nossas rotas prontas para uso, precisando apenas implementar a lógica do serviço e modelar nossa entidade e DTO. Fantástico, não?!

Lá no [nosso trello](https://trello.com/b/6NmSXkr6/alura-challenge-backend-semana-1) temos nosso card de banco de dados, com as propriedades que um video tem e com elas, vamos até o arquivo create-video-dto.ts e deixaremos assim:
```typescript
// src/videos/dto/create-video.dto.ts

export class CreateVideoDto {
  id: number;
  titulo: string;
  descricao: string;
  url: string;
}
```

Após criar nosso DTO, vamos modelar nossa entidade, mas antes de chegar nela, precisaremos decidir o banco de dados e nosso ORM.




### Implementação de base de dados para persistência das informações


Utilizaremos o banco de dados [MySQL](https://www.mysql.com/) (que você deve instalar, caso não tenha) e o ORM [TypeORM](https://typeorm.io/#/), com isso, vamos instalar seus pacotes:
```bash
npm install --save @nestjs/typeorm typeorm mysql2
```
em seguida, criaremos na raiz do nosso projeto um arquivo .env, para deixarmos nossas configurações do banco.
```env
DB_HOST=localhost
DB_USER=seu_username_criado_no_mysql
DB_PASS=sua_senha_criada_no_mysql
DB_NAME=alura_challenges_2
```
_*adicione .env no seu arquivo .gitignore, para que suas informações não sejam enviadas no commit_

Só que temos um porém. 
Para utilizarmos o .env, precisaremos instalar o pacote config do Nest e configurar ele...
```bash
npm i --save @nestjs/config
```
vamos no nosso arquivo app.module.ts e deixaremos dessa forma:
```typescript
// src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { VideosModule } from './videos/videos.module';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRootAsync({
      useFactory: () => ({
        type: 'mysql',
        host: process.env.DB_HOST,
        port: 3306,
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME,
        synchronize: true,
        autoLoadEntities: true,
        keepConnectionAlive: true,
      }),
    }),
    VideosModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```




### Validações feitas conforme as regras de negócio


para nossas validações, utilizaremos o class-validator:
```bash
npm i --save class-validator class-transformer
```

agora vamos lá no nosso arquivo video.entity.ts e deixaremos assim:

```typescript
// src/videos/entities/video.entity.ts

import { PrimaryGeneratedColumn, Column } from 'typeorm';
import { IsNotEmpty, IsString, IsUrl } from 'class-validator';

@Entity()
export class Video {
  @PrimaryGeneratedColumn()
  id: number;

  @IsNotEmpty()
  @IsString()
  @Column()
  titulo: string;

  @IsNotEmpty()
  @IsString()
  @Column()
  descricao: string;

  @IsNotEmpty()
  @IsUrl()
  @Column()
  url: string;
}

```
e no arquivo main, adicionaremos um Pipe do Nest, deixando assim:

```typescript
// src/main.ts

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
      forbidNonWhitelisted: true,
    }),
  );
  await app.listen(3000);
}
bootstrap();
```

_* Não abordei muito o assunto da criação do banco de dados, mas você precisa ter o banco criado conforme informado no .env
por exemplo:_
Vá no terminal e acesse o mysql:
```bash
mysql -u gabriel -p
```
Depois crie o banco de dados:
```mysql
create database alura_challenges_2;
```


Agora podemos subir a aplicação e ver se tudo está rodando sem erros.
```bash
npm run start:dev
```

Você terá uma saída semelhante a isso:
![npm_run_start:dev](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74kr57qq5425qdzu5aus.png)

E ao acessarmos o endereço http://localhost:3000/videos veremos a seguinte mensagem:
![first_page_message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/379mdjz362lpdlwyeg2k.png)

Isso acontece porque no nosso videos.controller está configurado para quando receber uma requisição do tipo GET para o endereço "/videos", ele deve executar a função findAll() do nosso videos.services, que por sua vez está com essa função retornando a mensagem que vimos na página "This action returns all videos", pois não implementamos a camada de serviços ainda. 

Por enquanto, nosso quadro está assim...
![trello_1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fy4kdqz21nv8uax9843m.png)
Concluímos a parte de banco de dados e com o class-validator, já matamos a regra de negócio que pedia para que todos os campos fossem validados.


Agora vamos para implementação da camada de serviços para fecharmos essa primeira semana (que com o Nest, faremos em 1 dia)

injetaremos nosso repositório de videos e utilizaremos o tipo genérico Repository passando nossa entidade Video, para que tenhamos todos os métodos de criação, alteração, etc...
Nosso arquivo ficará assim:

```typescript
// src/videos/videos.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateVideoDto } from './dto/create-video.dto';
import { UpdateVideoDto } from './dto/update-video.dto';
import { Video } from './entities/video.entity';

@Injectable()
export class VideosService {
  @InjectRepository(Video)
  private videoRepository: Repository<Video>;

  create(createVideoDto: CreateVideoDto) {
    return this.videoRepository.save(createVideoDto);
  }

  findAll() {
    return this.videoRepository.find();
  }

  findOne(id: number) {
    return this.videoRepository.findOne(id);
  }

  update(id: number, updateVideoDto: UpdateVideoDto) {
    return this.videoRepository.update(id, updateVideoDto);
  }

  async remove(id: number) {
    const video = await this.findOne(id);
    return this.videoRepository.remove(video);
  }
}

```
Feito isso, precisaremos alterar nosso videos.module, deixando assim:
```typescript
// src/videos/videos.module.ts

import { Module } from '@nestjs/common';
import { VideosService } from './videos.service';
import { VideosController } from './videos.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Video } from './entities/video.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Video])],
  controllers: [VideosController],
  providers: [VideosService],
  exports: [VideosService],
})
export class VideosModule {}

```

Maravilha!
Para testar tudo isso, vou utilizar o Insonmia e fazer as requisições para as rotas definidas no controller, testando se tudo está funcionando.




### Teste das rotas GET, POST, PATCH e DELETE


Começaremos enviando uma requisição do tipo POST para http://localhost:3000/videos com o seguinte body:
```json
{
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video"
}
```

Nosso retorno deverá ser um 201 (created) com o body:
```json
{
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video",
  "id": 1
}
```
_obs.: Você pode criar mais alguns exemplos, para visualizar melhor a lista de videos depois..._

para listar nossos videos criados, faremos uma requisição do tipo GET para http://localhost:3000/videos e a resposta deve ser uma lista dos videos que criou anteriormente e o status code 200 (OK), no meu caso:
```json
{
  "id": 1,
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video"
}
```

seguindo, vamos agora testar a rota que deve mostrar um vídeo que buscaremos pelo id.
Faremos uma requisição do tipo GET também para o endereço http://localhost:3000/videos/1 e o resultado deve ser um status code 200 (OK) e o body:
```json
{
  "id": 1,
  "titulo": "video_qualquer",
  "descricao": "video qualquer",
  "url": "http://meu-site.com/video"
}
```

Para testar a atualização de um vídeo, utilizaremos o tipo PATCH, para não termos a necessidade de enviar todos os dados do vídeo, somente o que queremos atualizar. Então, vamos fazer uma requisição PATCH para o endereço http://localhost:3000/videos/1 com o body:
```json
{
  "descricao":"video qualquer atualizado"
}
```
Ops! parece que não deu certo, recebemos um status code 400 (Bad Request), dizendo que não informamos alguns campos. Isso é graças ao nosso class-validator que não deixa passar uma requisição faltando campos obrigatórios. 
Mas então como faremos para contornar essa situação? 
Utilizaremos um recurso super interessante do Typescript, que transforma todos os atributos de uma classe em opcional, nosso Partial<T> (ou seja, não precisaremos ter no corpo todos os atributos do video). Vamos usar ele no nosso tipo de dado recebido no videos.controller, deixando assim:
```typescript
// src/videos/videos.controller.ts
...

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateVideoDto: Partial<UpdateVideoDto>) {
    return this.videosService.update(+id, updateVideoDto);
  }

...
```
Agora vamos tentar enviar novamente a requisição que fizemos e verificar o resultado. Ao enviar, receberemos o status code 200 (OK) e o body:
```json
{
  "generatedMaps": [],
  "raw": [],
  "affected": 1
}
```
E para finalizar esses primeiros testes, vamos enviar uma requisição para deletar um vídeo. Faremos uma requisição do tipo DELETE para http://localhost:3000/videos/1 e teremos como resposta o status code 200 (OK).

Com isso, fechamos todos os testes manuais das nossas rotas e podemos concluir todos os cards da primeira semana, deixando assim:
![trello_concl_s1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c05x3c4pkzxitl3pq69i.png) 

Uhuuuuullll, tudo concluído e de forma rápida e fácil!


Até a próxima semana com os novos desafios!

Abraços!!!