Node.js: Projeto API RESTful

Última atualização: 2026-08-26

1. Contexto do projeto

Alice e Bob receberam uma tarefa: desenvolver uma API de gerenciamento de livros para uma livraria online. Alice ficou responsável por projetar a arquitetura de roteamento e de middleware, enquanto Bob ficou encarregado de escrever a lógica de negócios. Os dois chegaram a um acordo sobre um formato padronizado de resposta e convenções de tratamento de erros e, em duas horas, concluíram todo o projeto — desde a configuração do projeto até a implementação completa da funcionalidade CRUD.

(1) Você aprenderá



2. Definição da estrutura de diretórios do projeto

Uma hierarquia de diretórios bem estruturada é a base da facilidade de manutenção de um projeto. Seguindo as melhores práticas da comunidade, Alice organiza o projeto em camadas com base nas responsabilidades:

TEXT 📖 Somente leitura
book-api/
├── app.js
├── routes/
│   └── books.js
├── controllers/
│   └── bookController.js
├── middleware/
│   ├── validate.js
│   └── errorHandler.js
└── models/
    └── bookModel.js
Diretório/Arquivo Responsabilidades Descrição
app.js Arquivo de entrada Criar uma instância do Express, associar rotas e middleware
routes/ Definição de rota Define os métodos HTTP e os caminhos que apontam para o controlador correspondente
controllers/ Lógica de negócios Processar a solicitação, chamar o modelo e retornar uma resposta
middleware/ Middleware Questões transversais, como validação de solicitações e tratamento de erros
models/ Modelos de dados Armazenamento de dados e encapsulamento de operações com dados


3. Projeto de pontos de extremidade da API

Com base nos requisitos de negócios, Bob elaborou uma lista de todos os endpoints da API:

Método HTTP Caminho Função Código de status de sucesso Código de status de falha
OBTER /api/books Obter todos os livros 200
GET /api/books/:id Recuperar um único livro 200 404
POST /api/books Novos livros 201 400
PUT /api/books/:id Atualizar livro 200 404 / 400
EXCLUIR /api/books/:id Excluir livro 200 404


4. Formato padronizado de resposta

Alice insiste em um formato de resposta padronizado para que a equipe de front-end não precise adivinhar os nomes dos campos:

▶ Exemplo: Resposta bem-sucedida

JAVASCRIPT
{
  "success": true,
  "data": { "id": 1, "title": "Node.js Guide", "author": "Alice" }
}
▶ Experimente

▶ Exemplo: Resposta de erro

JAVASCRIPT
{
  "success": false,
  "error": { "code": 404, "message": "Book not found" }
}
▶ Experimente
Campo Tipo Descrição
success booleano Se a solicitação foi bem-sucedida
data qualquer Dados retornados em caso de sucesso
error.code número Código de status de erro
error.message string Descrição do erro


5. Camada do modelo de dados

Bob usa matrizes no diretório models para simular um banco de dados e encapsular todas as operações com dados:

▶ Exemplo: bookModel.js

JAVASCRIPT
const books = [
  { id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }
];

let nextId = 2;

function findAll() {
  return books;
}

function findById(id) {
  return books.find(b => b.id === id);
}

function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}

function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}

function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}

module.exports = { findAll, findById, create, update, remove };
▶ Experimente

6. Middleware de validação de solicitações

Alice implementa a validação de dados na camada de middleware para garantir que solicitações inválidas não cheguem ao controlador:

▶ Exemplo: validate.js

JAVASCRIPT
function validateBook(req, res, next) {
  const { title, author, year } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") {
    errors.push("title is required and must be a string");
  }
  if (!author || typeof author !== "string") {
    errors.push("author is required and must be a string");
  }
  if (year !== undefined && (typeof year !== "number" || year < 0)) {
    errors.push("year must be a non-negative number");
  }
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}

module.exports = { validateBook };
▶ Experimente

7. Middleware para tratamento de erros

▶ Exemplo: errorHandler.js

JAVASCRIPT
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}

function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}

module.exports = { errorHandler, createError };
▶ Experimente

8. Camada do controlador

Bob lida com a lógica de negócios no controlador, chama o modelo e retorna uma resposta em um formato padronizado:

▶ Exemplo: bookController.js

JAVASCRIPT
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");

function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}

function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}

function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}

module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
▶ Experimente

9. Definições de rotas

O Alice separa o roteamento do controlador; o roteamento é responsável apenas pelo mapeamento:

▶ Exemplo: routes/books.js

JAVASCRIPT
const express = require("express");
const router = express.Router();
const controller = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");

router.get("/", controller.getAllBooks);
router.get("/:id", controller.getBookById);
router.post("/", validateBook, controller.createBook);
router.put("/:id", validateBook, controller.updateBook);
router.delete("/:id", controller.deleteBook);

module.exports = router;
▶ Experimente

10. Arquivo de entrada app.js

▶ Exemplo: app.js

JAVASCRIPT
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");

const app = express();
app.use(express.json());

app.use("/api/books", booksRoute);
app.use(errorHandler);

const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
▶ Experimente

11. Visão geral da arquitetura do projeto

Alice desenhou um diagrama de arquitetura que ilustra claramente o fluxo de solicitações:

100%
graph TD
    A[app.js Entrance] --> B[routes/books.js]
    B --> C{Needs verification?}
    C -->|POST/PUT| D[middleware/validate.js]
    C -->|GET/DELETE| E[controllers/bookController.js]
    D --> E
    E --> F[models/bookModel.js]
    F --> E
    E --> G[Standardized Response Format]
    G --> H[Client]
    E -->|Exception| I[middleware/errorHandler.js]
    I --> H


12. Testes de API

Bob usa curl para verificar cada ponto final, um por um:

comando curl Descrição
curl localhost:3000/api/books Ver todos os livros
curl localhost:3000/api/books/1 Encontrar um livro específico
curl -X POST -H "Content-Type: application/json" -d '{"title":"New Book","author":"Bob","year":2025}' localhost:3000/api/books Novos livros
curl -X PUT -H "Content-Type: application/json" -d '{"year":2026}' localhost:3000/api/books/1 Atualizar livros
curl -X DELETE localhost:3000/api/books/1 Excluir livro

▶ Exemplo: Testando operações de inserção e consulta

BASH
# Add a book
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Express in Action","author":"Evan","year":2024}' \
  localhost:3000/api/books
TEXT 📖 Somente leitura
{"success":true,"data":{"id":2,"タイトル":"Express in Action","author":"Evan","year":2024}}
BASH
# Search All Books
curl localhost:3000/api/books
TEXT 📖 Somente leitura
{"success":true,"data":[{"id":1,"title":"Node.js Guide","author":"Alice","year":2024},{"id":2,"title":"Express in Action","author":"Evan","year":2024}]}


13. Exemplo abrangente: API completa de gerenciamento de livros

Combinando todos os módulos anteriores, eis o fluxo principal do código para o projeto completo:

JAVASCRIPT
// models/bookModel.js
const books = [{ id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }];
let nextId = 2;
function findAll() { return books; }
function findById(id) { return books.find(b => b.id === id); }
function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}
function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}
function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}
module.exports = { findAll, findById, create, update, remove };
JAVASCRIPT
// middleware/validate.js
function validateBook(req, res, next) {
  const { title, author } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") errors.push("title is required");
  if (!author || typeof author !== "string") errors.push("author is required");
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}
module.exports = { validateBook };
JAVASCRIPT
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}
function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}
module.exports = { errorHandler, createError };
JAVASCRIPT
// controllers/bookController.js
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");
function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}
function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}
function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}
module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
JAVASCRIPT
// routes/books.js
const express = require("express");
const router = express.Router();
const ctrl = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");
router.get("/", ctrl.getAllBooks);
router.get("/:id", ctrl.getBookById);
router.post("/", validateBook, ctrl.createBook);
router.put("/:id", validateBook, ctrl.updateBook);
router.delete("/:id", ctrl.deleteBook);
module.exports = router;
JAVASCRIPT
// app.js
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");
const app = express();
app.use(express.json());
app.use("/api/books", booksRoute);
app.use(errorHandler);
app.listen(3000, () => console.log("Server running on port 3000"));
BASH
# Start and Test
node app.js
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Clean Code","author":"Robert","year":2008}' \
  localhost:3000/api/books
curl localhost:3000/api/books/1
curl -X DELETE localhost:3000/api/books/1

❓ Perguntas Frequentes

P: Por que os controladores e as rotas são separados? R: Separação de interesses — as rotas definem os mapeamentos de caminho, enquanto os controladores se concentram na lógica de negócios. Ao dissociar os dois, eles podem ser modificados e testados de forma independente.

P: Como faço para testar uma API? R: Use o curl na linha de comando, o Postman ou o plug-in Thunder Client para o VS Code, se preferir uma interface gráfica, e a biblioteca supertest para testes automatizados.

P: Em qual camada a validação de dados deve ocorrer? R: Ela deve ser realizada na camada de middleware para interceptar dados inválidos antes que a solicitação chegue ao controlador, garantindo que a lógica do controlador permaneça limpa.

P: Como devem ser tratados os recursos que não podem ser encontrados? R: Retorne um código de status 404 juntamente com o formato de erro padronizado { success: false, error: { code: 404, message: "Book not found" } }.

P: Devo usar TypeScript no meu projeto? R: O JavaScript é suficiente para pequenos projetos de prática; para grandes projetos de produção, recomenda-se o uso do TypeScript, pois ele oferece segurança de tipos e melhor suporte em IDEs.

P: Os dados armazenados na memória serão perdidos após uma reinicialização? R: Sim, os arrays são armazenados na memória do processo; portanto, os dados são apagados quando o serviço é reiniciado. Em um ambiente de produção, você deve usar um banco de dados (MongoDB, PostgreSQL etc.).

P: Qual é a diferença entre PUT e PATCH? R: O PUT exige o envio do recurso inteiro para uma substituição completa, enquanto o PATCH envia apenas os campos que precisam ser modificados para uma atualização parcial. Nesta lição, usaremos o PUT por uma questão de simplicidade.


📖 Resumo


📝 Exercícios

  1. Conclua todos os exemplos de código desta lição e certifique-se de que cada um deles seja executado corretamente.
  2. Modifique o exemplo completo e adicione suas próprias extensões
  3. Analise a documentação oficial, identifique 1 ou 2 APIs que não foram abordadas nesta aula e escreva um código de teste para elas.
  4. Reflexão: Como você aplicaria o que aprendeu nesta aula a um projeto do mundo real?
  5. Tente combinar o que você aprendeu nesta aula com o conteúdo das aulas anteriores para criar um pequeno projeto.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%