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/42em vez de/buscarTarefa?id=42? POST,PUTePATCHsã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:
- diferenciar API, recurso, representação, rota e endpoint;
- modelar URLs orientadas a recursos;
- usar
GET,POST,PUT,PATCHeDELETEcom intenção clara; - extrair parâmetros com
@Param(), filtros com@Query()e dados com@Body(); - devolver status HTTP coerentes;
- explicar operações seguras e idempotentes;
- implementar CRUD de tarefas em memória;
- devolver
404quando um recurso não existir; - evitar verbos de ação desnecessários nas URLs;
- 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
- selecione
GET /api/tasks; - envie a requisição;
- identifique método, caminho, status e corpo.
Etapa 2 — Crie e consulte
- selecione
POST /api/tasks; - envie um título válido;
- copie o identificador criado;
- consulte
GET /api/tasks/:id.
Etapa 3 — Atualize parcialmente
- selecione
PATCH /api/tasks/:id; - marque a tarefa como concluída;
- observe que o título não foi removido.
Etapa 4 — Remova e repita
- envie
DELETEpara a tarefa; - observe
204sem corpo; - repita a remoção;
- explique por que o segundo status muda, mas a operação continua idempotente.
Etapa 5 — Explore erros
- busque um identificador inexistente;
- envie um título curto;
- use um filtro inválido;
- compare
400e404.
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:
- filtro
searchnoGET /tasks; - ordenação
sort=title; - envelope
{ items, total }; - cabeçalho
Locationna criação; - 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
- NestJS — Controllers
- NestJS — Exception filters
- RFC 9110 — HTTP Semantics
- RFC 5789 — PATCH Method for HTTP
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.