DEV Community

Gabriel de Luca
Gabriel de Luca

Posted on • Edited on

AluraChallenges #2 ( Semana 1)

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.

nest generate resource videos
Enter fullscreen mode Exit fullscreen mode

Ele vai começar a fazer as perguntas para gerar nosso recurso:
nessa primeira, escolheremos "REST API" e na segunda "YES"
Nest_CLI1
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 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:

// src/videos/dto/create-video.dto.ts

export class CreateVideoDto {
  id: number;
  titulo: string;
  descricao: string;
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

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 (que você deve instalar, caso não tenha) e o ORM TypeORM, com isso, vamos instalar seus pacotes:

npm install --save @nestjs/typeorm typeorm mysql2
Enter fullscreen mode Exit fullscreen mode

em seguida, criaremos na raiz do nosso projeto um arquivo .env, para deixarmos nossas configurações do banco.

DB_HOST=localhost
DB_USER=seu_username_criado_no_mysql
DB_PASS=sua_senha_criada_no_mysql
DB_NAME=alura_challenges_2
Enter fullscreen mode Exit fullscreen mode

*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...

npm i --save @nestjs/config
Enter fullscreen mode Exit fullscreen mode

vamos no nosso arquivo app.module.ts e deixaremos dessa forma:

// 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 {}
Enter fullscreen mode Exit fullscreen mode

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

para nossas validações, utilizaremos o class-validator:

npm i --save class-validator class-transformer
Enter fullscreen mode Exit fullscreen mode

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

// 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;
}

Enter fullscreen mode Exit fullscreen mode

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

// 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();
Enter fullscreen mode Exit fullscreen mode

* 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:

mysql -u gabriel -p
Enter fullscreen mode Exit fullscreen mode

Depois crie o banco de dados:

create database alura_challenges_2;
Enter fullscreen mode Exit fullscreen mode

Agora podemos subir a aplicação e ver se tudo está rodando sem erros.

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Você terá uma saída semelhante a isso:
npm_run_start:dev

E ao acessarmos o endereço http://localhost:3000/videos veremos a seguinte mensagem:
first_page_message

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
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:

// 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);
  }
}

Enter fullscreen mode Exit fullscreen mode

Feito isso, precisaremos alterar nosso videos.module, deixando assim:

// 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 {}

Enter fullscreen mode Exit fullscreen mode

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:

{
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video"
}
Enter fullscreen mode Exit fullscreen mode

Nosso retorno deverá ser um 201 (created) com o body:

{
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video",
  "id": 1
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "id": 1,
  "titulo":"video_qualquer",
  "descricao":"video qualquer",
  "url":"http://meu-site.com/video"
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "id": 1,
  "titulo": "video_qualquer",
  "descricao": "video qualquer",
  "url": "http://meu-site.com/video"
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "descricao":"video qualquer atualizado"
}
Enter fullscreen mode Exit fullscreen mode

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 (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:

// src/videos/videos.controller.ts
...

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

...
Enter fullscreen mode Exit fullscreen mode

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:

{
  "generatedMaps": [],
  "raw": [],
  "affected": 1
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

Abraços!!!

Top comments (0)