Node.js: Projeto API (Parte 1)
Última atualização: 2026-08-26
1. Lançamento do projeto: O primeiro dia da Alice
Alice e sua equipe receberam uma nova solicitação: desenvolver uma API de gerenciamento de tarefas em equipe. No primeiro dia, decidiram começar configurando a estrutura do projeto e o módulo de autenticação de usuários. “A autenticação é a base de tudo”, disse Alice. “Sem autenticação, o restante do sistema de gerenciamento de tarefas não teria sentido.”
- A inicialização do projeto e o planejamento da estrutura de diretórios são as primeiras etapas da engenharia.
- O design de modelos do Mongoose constitui a base da camada de dados
- A combinação de JWT e bcrypt é a solução mais comum para autenticação no Node.js
- A API de Registro/Login é composta pelos dois endpoints principais do módulo de autenticação
- O middleware
authprotege as rotas que exigem autenticação
2. Inicialização do projeto e estrutura de diretórios
▶ Exemplo:(1) Inicializar um projeto Express
mkdir task-manager-api && cd task-manager-api
npm init -y
npm install express mongoose bcryptjs jsonwebtoken dotenv cors helmet
npm install --save-dev nodemon
▶ Exemplo:(2) Projeto da estrutura de diretórios
task-manager-api/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── middleware/
│ │ └── auth.js
│ ├── models/
│ │ ├── User.js
│ │ └── Task.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── tasks.js
│ ├── validators/
│ │ └── authValidator.js
│ └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
| Diretório/Arquivo | Descrição |
|---|---|
src/config/ |
Conexão com o banco de dados, variáveis de ambiente e outras configurações |
src/middleware/ |
Middleware para autenticação, tratamento de erros, etc. |
src/models/ |
Definições de esquema e modelo do Mongoose |
src/routes/ |
Módulos de roteamento, classificados por função |
src/validators/ |
Lógica de validação de parâmetros de solicitação |
src/app.js |
Arquivo principal do aplicativo Express |
server.js |
Arquivo de entrada, iniciar o servidor |
▶ Exemplo: arquivo de entrada server.js
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');
connectDB();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
▶ Exemplo: Configuração da conexão com o banco de dados
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
} catch (err) {
console.error('MongoDB connection error:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
3. Projeto de modelos no Mongoose
(1) Modelo do usuário
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
username |
String | Sim | Nome de usuário, índice exclusivo |
email |
String | Sim | E-mail, índice exclusivo |
password |
String | Sim | senha com hash bcrypt |
role |
String | Não | Função: user (padrão) / admin |
createdAt |
Data | Automático | Data de criação |
(2) Modelo de tarefa
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
title |
String | Sim | Título da tarefa |
description |
String | Não | Detalhes da tarefa |
status |
String | Não | pending (padrão) / in-progress / completed |
priority |
String | Não | low (padrão) / medium / high |
assignedTo |
ObjectId | Sim | Usuário designado, associado ao Usuário |
dueDate |
Data | Nº | Prazo |
createdAt |
Data | Automático | Data de criação |
updatedAt |
Data | Automático | Última atualização |
▶ Exemplo: Definição do modelo de usuário
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, minlength: 6 },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
userSchema.methods.comparePassword = function (candidate) {
return bcrypt.compare(candidate, this.password);
};
module.exports = mongoose.model('User', userSchema);
▶ Exemplo: Definição do modelo de tarefa
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: { type: String, required: true, trim: true },
description: { type: String, default: '' },
status: { type: String, enum: ['pending', 'in-progress', 'completed'], default: 'pending' },
priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
dueDate: { type: Date }
}, { timestamps: true });
module.exports = mongoose.model('Task', taskSchema);
4. Módulo de autenticação do usuário
▶ Exemplo:(1) Como funciona a autenticação JWT
graph TD
A[User Registration/Log In] --> B[Server Authentication Credentials]
B --> C[Generate JWT Token]
C --> D[Back Token To the client]
D --> E[Client-side Token Request]
E --> F[auth Middleware Validation Token]
F -->|Valid| G[Forward to the routing processor]
F -->|Invalid| H[Back 401 Error]
(2) Projeto dos pontos de extremidade da API de autenticação
| Método | Caminho | Descrição | Autenticação necessária |
|---|---|---|---|
| POST | /api/auth/register |
Cadastro de usuário | Não |
| POST | /api/auth/login |
Login do usuário | Não |
| GET | /api/auth/me |
Obter usuário atual | Sim |
▶ Exemplo: Rotas de cadastro e login
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });
router.post('/register', async (req, res, next) => {
try {
const { username, email, password } = req.body;
const user = await User.create({ username, email, password });
res.status(201).json({ token: generateToken(user._id), user: { id: user._id, username, email, role: user.role } });
} catch (err) {
next(err);
}
});
router.post('/login', async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
res.json({ token: generateToken(user._id), user: { id: user._id, username: user.username, email, role: user.role } });
} catch (err) {
next(err);
}
});
module.exports = router;
▶ Exemplo: middleware de autenticação
const jwt = require('jsonwebtoken');
const User = require('../models/User');
module.exports = async (req, res, next) => {
const header = req.headers.authorization;
if (!header || !header.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
if (!req.user) return res.status(401).json({ message: 'User not found' });
next();
} catch {
res.status(401).json({ message: 'Invalid token' });
}
};
▶ Exemplo: Obter a rota do usuário atual
const auth = require('../middleware/auth');
router.get('/me', auth, async (req, res) => {
res.json({ user: { id: req.user._id, username: req.user.username, email: req.user.email, role: req.user.role } });
});
5. O arquivo principal app.js e a configuração do arquivo .env
▶ Exemplo: Integrando o app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.statusCode || 500).json({ message: err.message || 'Server Error' });
});
module.exports = app;
▶ Exemplo: Variáveis de ambiente no arquivo .env
PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_super_secret_key_change_in_production
6. Exemplo abrangente: o processo completo de inicialização do projeto
O código a seguir integra todos os módulos principais mencionados acima. Com cerca de 80 linhas de código principal, é possível executar uma API básica de gerenciamento de tarefas com autenticação:
// server.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
app.use(helmet(), cors(), express.json());
// --- Models ---
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function () { if (this.isModified('password')) this.password = await bcrypt.hash(this.password, 10); });
userSchema.methods.comparePassword = function (pw) { return bcrypt.compare(pw, this.password); };
const User = mongoose.model('User', userSchema);
// --- Auth Middleware ---
const auth = async (req, res, next) => {
try {
const decoded = jwt.verify(req.headers.authorization?.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch { res.status(401).json({ message: 'Unauthorized' }); }
};
// --- Routes ---
app.post('/api/auth/register', async (req, res) => {
const user = await User.create(req.body);
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.status(201).json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.post('/api/auth/login', async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user || !(await user.comparePassword(req.body.password))) return res.status(401).json({ message: 'Invalid credentials' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.get('/api/auth/me', auth, (req, res) => res.json(req.user));
// --- Start ---
mongoose.connect(process.env.MONGO_URI).then(() => {
app.listen(process.env.PORT || 3000, () => console.log('Server running'));
});
❓ Perguntas Frequentes
P: Como a estrutura de diretórios do projeto deve ser projetada? R: Organize-a por funcionalidade: armazene os modelos de dados em
models/, as rotas emroutes/, o middleware emmiddleware/e as configurações emconfig/. Coloque o ponto de entrada,app.js, no diretório raiz.
P: Por que usar o Mongoose em vez do driver nativo? R: O Mongoose oferece validação de esquema, ganchos de middleware, dicas de tipo e um construtor de consultas, o que aumenta a eficiência do desenvolvimento; o driver nativo é mais leve e mais adequado para cenários simples.
P: O arquivo .env deve ser incluído no Git? R: De jeito nenhum. O arquivo .env contém informações confidenciais, como senhas de banco de dados e chaves; portanto, deve ser adicionado ao .gitignore e inserido por meio de variáveis de ambiente ou de CI durante a implantação.
P: Qual deve ser o comprimento do segredo JWT? R: Deve ser uma sequência aleatória de pelo menos 32 caracteres; para ambientes de produção, recomenda-se 64 caracteres ou mais. Você pode gerar um usando
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))".
P: Como faço para testar a API de registro? R: Use o Postman ou o curl para enviar uma solicitação POST para /api/auth/register, verifique o token retornado e, em seguida, use esse token para acessar uma rota protegida e verificar se a autenticação está funcionando.
- P: Por que precisamos fazer a autenticação primeiro? R: A autenticação é um pré-requisito para a lógica de negócios; sem uma identidade, é impossível estabelecer a propriedade dos dados e controlar as permissões de acesso, e todas as operações CRUD subsequentes dependem da autenticação.
- P: O armazenamento de senhas é seguro? R: O bcrypt utiliza hash adaptativo com um salt, o que é muito superior ao texto simples ou ao MD5/SHA. Com 10 rodadas, o custo é de aproximadamente 100 ms por operação, alcançando um equilíbrio entre segurança e desempenho.
- P: Como o segredo do JWT deve ser gerenciado? R: Use um arquivo .env no ambiente de desenvolvimento e variáveis de ambiente ou um serviço de gerenciamento de segredos (como o AWS Secrets Manager) no ambiente de produção; nunca o codifique diretamente no código.
- P: Como faço para testar o cadastro/login? R: Use o Postman ou o curl para enviar uma solicitação POST para
/api/auth/registere/api/auth/logine verifique o token retornado. - P: Como a estrutura do projeto deve ser organizada? R: Divida-a em componentes MVC: modelos (dados), rotas, middleware e configuração, para manter a separação de interesses.
- P: O que é melhor, o bcrypt ou o crypto? R: O bcrypt foi projetado especificamente para senhas e inclui valores de salt integrados e um custo adaptativo, o que o torna mais adequado para o hash de senhas do que a série SHA do pacote crypto.
📖 Resumo
- Lançamento do projeto: conceitos-chave e como usar o Alice desde o primeiro dia
- Conceitos básicos e uso da inicialização de projetos e das estruturas de diretórios
- Conceitos básicos e uso do design de modelos no Mongoose
- Conceitos básicos e uso do módulo de autenticação de usuários
- Conceitos básicos e uso do arquivo principal app.js e do arquivo de configuração .env
- Exemplo abrangente: conceitos fundamentais e aplicação de todo o processo de inicialização do projeto
📝 Exercícios
- Inicialize o projeto de acordo com a estrutura de diretórios, instale todas as dependências e certifique-se de que
npm run devconsiga iniciar o servidor. - Crie dois modelos Mongoose,
UsereTask, e use o Postman para testar as APIs de cadastro e login. - Escreva um middleware de autenticação e teste o endpoint
/api/auth/me: ele deve retornar um erro 401 se nenhum token for fornecido e retornar as informações do usuário se um token válido for fornecido. - Configure JWT_SECRET e MONGO_URI no arquivo
.enve ignore o arquivo.envno diretório.gitignore.