Engenharia de IA Aplicada à Programação Web
Aula 3 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 5.1 Introdução ao Node.js e NestJS
  2. 5.2 Módulos, controladores e provedores
  3. 5.3 REST, rotas e respostas HTTP
  4. 5.4 DTOs, pipes e validação
  5. 5.5 Erros, filtros e configuração
  6. 5.6 OpenAPI e documentação
  7. 5.7 Registros, interceptadores e testes
  8. 5.8 Compilação e projeto final

Módulo 5 · Aula 5.3

Aula 5.3 — REST, rotas e respostas HTTP

2 horas Teoria + laboratório Node.js, NestJS e APIs
Ver fonte Markdown

Identificação

  • Módulo: 5 — Node.js, NestJS e APIs
  • Duração: 2 horas
  • Tipo: teoria aplicada e laboratório
  • Entrega: API REST de tarefas com operações CRUD em memória
  • Laboratório: ../exemplos/aula-5.3/index.html

Introdução

Na aula anterior organizamos controller, service e repositório. Agora precisamos definir como outras aplicações conversam com esse módulo.

Uma API não é apenas “uma URL que devolve JSON”. Ela é um contrato. O cliente precisa saber qual endereço usar, qual ação pedir, quais dados enviar e como interpretar a resposta. HTTP já oferece um vocabulário para isso: métodos, caminhos, cabeçalhos, corpo e códigos de status.

Perguntas que responderemos:

  • REST é uma biblioteca do NestJS?
  • rota, endpoint e recurso significam exatamente a mesma coisa?
  • por que usar GET /tasks/42 em vez de /buscarTarefa?id=42?
  • POST, PUT e PATCH são intercambiáveis?
  • toda resposta bem-sucedida deve retornar 200?
  • o que significa uma operação ser idempotente?
  • o navegador pode acessar o MySQL diretamente depois de criarmos a API?

REST não é um pacote que instalamos. É um estilo arquitetural. Nesta aula aplicaremos uma parte prática desse estilo sobre HTTP.

Objetivos

Ao concluir a aula, você será capaz de:

  1. diferenciar API, recurso, representação, rota e endpoint;
  2. modelar URLs orientadas a recursos;
  3. usar GET, POST, PUT, PATCH e DELETE com intenção clara;
  4. extrair parâmetros com @Param(), filtros com @Query() e dados com @Body();
  5. devolver status HTTP coerentes;
  6. explicar operações seguras e idempotentes;
  7. implementar CRUD de tarefas em memória;
  8. devolver 404 quando um recurso não existir;
  9. evitar verbos de ação desnecessários nas URLs;
  10. inspecionar requisição e resposta completas.

Pré-requisitos

  • aulas 5.1 e 5.2 concluídas;
  • noção de requisição e resposta;
  • TasksModule, controller, service e repositório em memória;
  • conhecimentos iniciais de objetos e JSON.

1. O que é uma API?

API significa interface de programação de aplicações. É uma fronteira com regras que permite a comunicação entre sistemas.

Angular → API NestJS → regra de negócio → repositório → MySQL

O Angular conhece o contrato HTTP. Ele não deve receber senha do banco nem executar SQL diretamente.

2. O que é REST?

REST é um estilo arquitetural para sistemas distribuídos. Em APIs web, normalmente aplicamos ideias como:

  • recursos identificados por URLs;
  • interface uniforme baseada na semântica HTTP;
  • requisições autocontidas;
  • separação entre cliente e servidor;
  • representações transferidas, frequentemente em JSON;
  • respostas que permitem ao cliente entender o resultado.

Uma API que usa HTTP e JSON não se torna automaticamente RESTful. A intenção das rotas, métodos e respostas precisa ser coerente.

3. Recurso e representação

Um recurso é aquilo que queremos identificar ou manipular: tarefas, usuários ou documentos. Uma representação é uma forma de descrever o estado do recurso.

{
  "id": "task-1",
  "title": "Estudar rotas REST",
  "completed": false
}

O objeto JSON não é a tarefa física “dentro da internet”; é uma representação dela.

4. URL orientada a recursos

Prefira substantivos no plural:

/api/tasks
/api/tasks/task-1
/api/users/user-8/tasks

Evite repetir ações que o método HTTP já expressa:

/api/getTasks
/api/createTask
/api/deleteTask?id=task-1

Há exceções legítimas para operações que não se encaixam bem em CRUD, mas o ponto de partida deve ser o recurso.

5. Rota versus endpoint

Neste curso usaremos:

  • rota: padrão de caminho associado ao código, como /tasks/:id;
  • endpoint: combinação acessível de método e caminho, como GET /api/tasks/task-1;
  • URL: endereço completo, como http://localhost:3000/api/tasks/task-1.

Equipes podem usar termos de forma um pouco diferente. O importante é definir o vocabulário e evitar ambiguidade.

6. Anatomia de uma requisição

POST /api/tasks HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Accept: application/json

{
  "title": "Estudar métodos HTTP"
}
Parte Significado
POST intenção da operação
/api/tasks recurso alvo
cabeçalhos metadados da mensagem
corpo representação enviada pelo cliente

7. Anatomia de uma resposta

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/tasks/task-2

{
  "id": "task-2",
  "title": "Estudar métodos HTTP",
  "completed": false
}

O status comunica o resultado de forma padronizada. O corpo fornece detalhes ou uma representação quando necessário.

8. GET — consultar

GET recupera uma representação e não deve alterar o estado do recurso.

@Get()
list(@Query('completed') completed?: string): Task[] {
  return this.tasksService.list(completed);
}

@Get(':id')
findOne(@Param('id') id: string): Task {
  return this.tasksService.findOne(id);
}

Exemplos:

GET /api/tasks
GET /api/tasks?completed=true
GET /api/tasks/task-1

9. Parâmetro de rota

Em /tasks/:id, :id é um espaço dinâmico definido na rota. Em uma requisição real, ele recebe um valor:

padrão: /tasks/:id
URL:    /tasks/task-1
valor:  task-1
@Param('id') id: string

Rotas estáticas, como /tasks/summary, devem ser declaradas antes de rotas dinâmicas como /tasks/:id, evitando que summary seja interpretado como um identificador.

10. Cadeia de consulta

Cadeia de consulta costuma representar filtros, ordenação, busca e paginação:

/api/tasks?completed=false&search=nest
@Query('completed') completed?: string
@Query('search') search?: string

Tudo chega como dado externo. A string "false" é verdadeira em uma condição JavaScript se você apenas fizer Boolean(value). Converta e valide explicitamente.

11. POST — criar ou processar

Para criar uma tarefa na coleção:

@Post()
create(@Body() input: CreateTaskInput): Task {
  return this.tasksService.create(input.title);
}

Por padrão, manipuladores POST no NestJS respondem com 201 Created. É útil também informar onde o recurso foi criado por meio do cabeçalho Location.

Na Aula 5.4 substituiremos o tipo introdutório por uma classe DTO validada em ambiente de execução.

12. PUT — substituir a representação

PUT representa substituição completa do estado conhecido do recurso:

PUT /api/tasks/task-1
Content-Type: application/json

{
  "title": "Nova descrição completa",
  "completed": true
}

Se o contrato exige todos os campos editáveis, omitir um deles deve ser tratado de acordo com esse contrato, e não silenciosamente como atualização parcial.

13. PATCH — modificar parcialmente

PATCH aplica um conjunto de alterações:

PATCH /api/tasks/task-1
Content-Type: application/json

{
  "completed": true
}

O documento enviado não precisa conter a tarefa completa. O formato de patch deve ser definido pelo contrato da API.

14. DELETE — remover

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id') id: string): void {
  this.tasksService.remove(id);
}

204 No Content comunica sucesso sem corpo. Não devolva JSON junto com 204, pois a semântica desse status é justamente não possuir conteúdo.

15. Métodos seguros

Um método seguro é destinado à leitura, sem solicitar alteração do estado do servidor. GET é seguro. Isso não significa que absolutamente nada aconteça: registros e métricas podem ser produzidos, mas a intenção solicitada pelo cliente é leitura.

16. Idempotência

Uma operação idempotente produz o mesmo efeito pretendido no servidor quando repetida uma ou várias vezes.

DELETE /tasks/task-1
DELETE /tasks/task-1

A primeira pode responder 204 e a segunda 404, mas repetir a intenção não remove duas tarefas diferentes. O estado final continua “task-1 ausente”.

Pela semântica HTTP, métodos seguros, PUT e DELETE são idempotentes. POST não é idempotente por definição. PATCH pode ser projetado de forma idempotente, mas não há essa garantia geral.

17. Status HTTP essenciais

Status Uso nesta API
200 OK consulta ou atualização com corpo
201 Created recurso criado
204 No Content remoção concluída sem corpo
400 Bad Request entrada inválida
404 Not Found tarefa inexistente
409 Conflict conflito com o estado atual
500 Internal Server Error falha não tratada no servidor

Status não é decoração. O cliente pode usá-lo para decidir qual interface apresentar.

18. Exceções no NestJS

findOne(id: string): Task {
  const task = this.repository.findById(id);
  if (!task) {
    throw new NotFoundException(`Tarefa ${id} não encontrada.`);
  }
  return task;
}

O framework converte a exceção HTTP em uma resposta coerente. Não exponha stack trace, detalhes internos ou SQL ao cliente.

19. Resposta padrão do NestJS

Na abordagem padrão recomendada, o manipulador retorna objeto ou array, e o NestJS serializa para JSON. Evite usar @Res() sem necessidade, porque isso acopla o código ao adaptador HTTP e transfere para você a responsabilidade de concluir a resposta.

@Get()
list(): Task[] {
  return this.tasksService.list();
}

20. Contratos consistentes

Há duas abordagens comuns para coleções:

[
  { "id": "task-1", "title": "Estudar REST", "completed": false }
]

ou:

{
  "items": [
    { "id": "task-1", "title": "Estudar REST", "completed": false }
  ],
  "total": 1
}

Não existe uma única forma universal. Escolha uma convenção, documente e mantenha-a. O envelope facilita adicionar paginação sem alterar a forma superior da resposta.

21. CRUD e REST não são sinônimos

CRUD descreve quatro operações de dados: criar, ler, atualizar e remover. REST é um estilo arquitetural mais amplo. CRUD ajuda a exercitar os métodos HTTP, mas não cobre cache, hipermídia, negociação de conteúdo e outras propriedades de sistemas REST.

22. Controller completo

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  @Get()
  list(@Query('completed') completed?: string): Task[] {
    return this.tasksService.list(completed);
  }

  @Get(':id')
  findOne(@Param('id') id: string): Task {
    return this.tasksService.findOne(id);
  }

  @Post()
  create(@Body() input: CreateTaskInput): Task {
    return this.tasksService.create(input.title);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() input: UpdateTaskInput): Task {
    return this.tasksService.update(id, input);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id') id: string): void {
    this.tasksService.remove(id);
  }
}

23. Service com busca e erro

findOne(id: string): Task {
  const task = this.repository.findById(id);
  if (!task) {
    throw new NotFoundException(`Tarefa ${id} não encontrada.`);
  }
  return task;
}

O service decide que uma tarefa precisa existir. O controller apenas conecta essa operação à rota HTTP.

24. Repositório em memória ampliado

O contrato agora precisa de:

export interface TasksRepository {
  findAll(): Task[];
  findById(id: string): Task | undefined;
  add(title: string): Task;
  update(id: string, changes: Partial<Omit<Task, 'id'>>): Task | undefined;
  remove(id: string): boolean;
}

Na integração com MySQL, a implementação muda e o contrato permanece como fronteira.

25. Estado HTTP é diferente de estado do banco

HTTP é stateless: cada requisição deve trazer as informações necessárias para ser interpretada. Isso não proíbe o servidor de persistir tarefas no MySQL. Significa que o protocolo não deve depender de uma conversa implícita e invisível entre requisições.

26. Segurança inicial

  • trate parâmetros, query e corpo como não confiáveis;
  • limite tamanho de textos e paginação;
  • não exponha mensagens internas;
  • não permita atualização de campos que o cliente não controla;
  • nunca construa SQL concatenando entrada do usuário;
  • não confie apenas nos tipos TypeScript para validar rede.

A validação completa será implementada na Aula 5.4.

27. Roteiro de implementação

Etapa Tempo sugerido
API, REST e recursos 15 min
Métodos e URLs 20 min
Parâmetros, query e corpo 20 min
Status e erros 20 min
Controller, service e repositório 25 min
Laboratório 15 min
Revisão 5 min

28. Testando manualmente

curl http://localhost:3000/api/tasks
curl -X POST http://localhost:3000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Estudar REST"}'
curl -X PATCH http://localhost:3000/api/tasks/task-1 \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

No PowerShell, você também pode usar Invoke-RestMethod.

29. Laboratório guiado

Abra o Console REST da Knowledge API.

Etapa 1 — Consulte a coleção

  1. selecione GET /api/tasks;
  2. envie a requisição;
  3. identifique método, caminho, status e corpo.

Etapa 2 — Crie e consulte

  1. selecione POST /api/tasks;
  2. envie um título válido;
  3. copie o identificador criado;
  4. consulte GET /api/tasks/:id.

Etapa 3 — Atualize parcialmente

  1. selecione PATCH /api/tasks/:id;
  2. marque a tarefa como concluída;
  3. observe que o título não foi removido.

Etapa 4 — Remova e repita

  1. envie DELETE para a tarefa;
  2. observe 204 sem corpo;
  3. repita a remoção;
  4. explique por que o segundo status muda, mas a operação continua idempotente.

Etapa 5 — Explore erros

  1. busque um identificador inexistente;
  2. envie um título curto;
  3. use um filtro inválido;
  4. compare 400 e 404.

30. Erros comuns

Usar GET para alterar dados

Pode causar efeitos inesperados por cache, pré-carregamento e robôs.

Retornar sempre 200

Obriga o cliente a adivinhar o resultado lendo textos no corpo.

Colocar verbos em todas as URLs

Duplica a intenção já comunicada pelo método HTTP.

Tratar PUT como PATCH sem documentar

Cria ambiguidade sobre campos omitidos.

Devolver corpo com 204

Contraria a semântica de No Content.

Acreditar que TypeScript validou a requisição

O JSON veio da rede e os tipos foram apagados em ambiente de execução.

31. Exercício de fixação

Implemente o recurso documents com:

  • GET /api/documents;
  • GET /api/documents/:id;
  • POST /api/documents;
  • PATCH /api/documents/:id;
  • DELETE /api/documents/:id;
  • status coerentes e erro 404.

32. Desafio individual

Acrescente ao laboratório ou ao exemplo:

  1. filtro search no GET /tasks;
  2. ordenação sort=title;
  3. envelope { items, total };
  4. cabeçalho Location na criação;
  5. uma tabela documentando todos os contratos.

33. Lista de verificação de conclusão

  • Sei explicar API, REST, recurso e representação.
  • Diferencio rota, endpoint e URL.
  • Uso substantivos nas URLs.
  • Diferencio parâmetro, cadeia de consulta e corpo.
  • Sei quando usar GET, POST, PUT, PATCH e DELETE.
  • Entendo método seguro e idempotente.
  • Uso 200, 201, 204, 400 e 404 corretamente.
  • Não devolvo corpo em uma resposta 204.
  • Mantenho regras no service.
  • Continuo sem expor MySQL ao navegador.

34. Rubrica da entrega

Critério Pontos
Modelagem dos recursos e URLs 15
Uso dos métodos HTTP 20
Parâmetros, query e corpo 15
Status e erros 20
Separação controller/service/repositório 15
Contratos consistentes 10
Explicação de idempotência 5
Total 100

35. Fontes oficiais

Próxima aula

Na Aula 5.4, criaremos DTOs como classes e aplicaremos pipes de transformação e validação para impedir que dados inválidos cheguem às regras da aplicação.