Resumo Executivo
O Que Vamos Construir: API REST completa para um sistema de blog com authentication JWT, CRUD operations, middleware de segurança, validation e deploy production-ready.
Tecnologias: Node.js 18+, Express.js 4.x, MongoDB/Mongoose, JWT, bcrypt, helmet, express-rate-limit e express-validator.
Tempo Total: 3-4 horas para implementação completa, 30 minutos para versão básica funcional.
Node.js com Express.js domina o desenvolvimento de APIs REST modernas, oferecendo performance excepcional, ecossistema rico e JavaScript full-stack. Em 2025, 67% das APIs novas usam Node.js.
Este tutorial ensina criação de API REST profissional seguindo best practices de segurança, arquitetura e performance, resultando em código production-ready que escala.
Pré-requisitos e Setup Inicial
Ferramentas Necessárias
- Node.js 18+: Runtime JavaScript no servidor
- npm/yarn: Gerenciador de pacotes
- VS Code: Editor recomendado com extensões Node.js
- Postman/Insomnia: Para testar endpoints
- MongoDB: Database (local ou MongoDB Atlas)
Verificar Instalação
# Verificar versões instaladas
node --version # Deve ser 18+
npm --version # Deve ser 8+
# Instalar dependências globais úteis
npm install -g nodemon # Auto-restart durante desenvolvimento
Passo 1: Configuração do Projeto
1.1 Inicializar Projeto
# Criar diretório e inicializar projeto
mkdir blog-api
cd blog-api
npm init -y
# Instalar dependências principais
npm install express mongoose dotenv bcryptjs jsonwebtoken
npm install express-validator helmet cors express-rate-limit
# Dependências de desenvolvimento
npm install -D nodemon jest supertest
1.2 Estrutura de Pastas
blog-api/
├── src/
│ ├── controllers/
│ │ ├── authController.js
│ │ └── postController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ └── errorHandler.js
│ ├── models/
│ │ ├── User.js
│ │ └── Post.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── posts.js
│ ├── config/
│ │ └── database.js
│ └── app.js
├── tests/
├── .env
├── .gitignore
├── package.json
└── server.js
1.3 Configurar package.json Scripts
{
"name": "blog-api",
"version": "1.0.0",
"description": "API REST completa com Node.js e Express",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"test:watch": "jest --watch"
},
"keywords": ["nodejs", "express", "api", "rest", "mongodb"],
"author": "Seu Nome",
"license": "MIT"
}
Passo 2: Configurar Servidor Express
2.1 Arquivo Principal (server.js)
// server.js
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/database');
const PORT = process.env.PORT || 3000;
// Conectar ao banco de dados
connectDB();
// Iniciar servidor
app.listen(PORT, () => {
console.log(`🚀 Servidor rodando na porta ${PORT}`);
console.log(`📖 Documentação: http://localhost:${PORT}/api/docs`);
});
2.2 Configuração do Express (src/app.js)
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
// Importar routes
const authRoutes = require('./routes/auth');
const postRoutes = require('./routes/posts');
// Importar middleware
const errorHandler = require('./middleware/errorHandler');
const app = express();
// Middleware de segurança
app.use(helmet()); // Headers de segurança
app.use(cors()); // Cross-Origin Resource Sharing
// Rate limiting - previne spam/ataques
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // máximo 100 requests por IP
message: 'Muitas tentativas. Tente novamente em 15 minutos.'
});
app.use('/api/', limiter);
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Logging middleware (desenvolvimento)
if (process.env.NODE_ENV === 'development') {
app.use((req, res, next) => {
console.log(`${req.method} ${req.originalUrl} - ${new Date().toISOString()}`);
next();
});
}
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/posts', postRoutes);
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'Endpoint não encontrado'
});
});
// Error handling middleware (deve ser o último)
app.use(errorHandler);
module.exports = app;
2.3 Configuração do Banco (src/config/database.js)
// src/config/database.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log(`📦 MongoDB conectado: ${conn.connection.host}`);
} catch (error) {
console.error('❌ Erro ao conectar MongoDB:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
Passo 3: Criar Models (Mongoose)
3.1 Model User (src/models/User.js)
// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Nome é obrigatório'],
trim: true,
maxlength: [50, 'Nome não pode ter mais de 50 caracteres']
},
email: {
type: String,
required: [true, 'Email é obrigatório'],
unique: true,
lowercase: true,
match: [
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
'Please enter a valid email'
]
},
password: {
type: String,
required: [true, 'Senha é obrigatória'],
minlength: [6, 'Senha deve ter pelo menos 6 caracteres'],
select: false // Não incluir senha em queries por padrão
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
avatar: {
type: String,
default: 'default-avatar.png'
},
isActive: {
type: Boolean,
default: true
}
}, {
timestamps: true // Adiciona createdAt e updatedAt automaticamente
});
// Middleware para hash da senha antes de salvar
userSchema.pre('save', async function(next) {
// Só fazer hash se senha foi modificada
if (!this.isModified('password')) return next();
// Hash da senha com salt de 12
this.password = await bcrypt.hash(this.password, 12);
next();
});
// Método para comparar senhas
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// Método para obter dados públicos do usuário
userSchema.methods.getPublicData = function() {
const user = this.toObject();
delete user.password;
return user;
};
module.exports = mongoose.model('User', userSchema);
3.2 Model Post (src/models/Post.js)
// src/models/Post.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Título é obrigatório'],
trim: true,
maxlength: [100, 'Título não pode ter mais de 100 caracteres']
},
content: {
type: String,
required: [true, 'Conteúdo é obrigatório'],
minlength: [10, 'Conteúdo deve ter pelo menos 10 caracteres']
},
summary: {
type: String,
maxlength: [200, 'Resumo não pode ter mais de 200 caracteres']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
tags: [{
type: String,
trim: true,
lowercase: true
}],
status: {
type: String,
enum: ['draft', 'published', 'archived'],
default: 'draft'
},
featured: {
type: Boolean,
default: false
},
views: {
type: Number,
default: 0
},
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
publishedAt: {
type: Date
}
}, {
timestamps: true
});
// Index para melhor performance em buscas
postSchema.index({ title: 'text', content: 'text' });
postSchema.index({ author: 1, createdAt: -1 });
postSchema.index({ status: 1, publishedAt: -1 });
// Virtual para contagem de likes
postSchema.virtual('likesCount').get(function() {
return this.likes.length;
});
// Middleware para definir publishedAt quando status muda para published
postSchema.pre('save', function(next) {
if (this.isModified('status') && this.status === 'published' && !this.publishedAt) {
this.publishedAt = new Date();
}
next();
});
// Populate autor automaticamente
postSchema.pre(/^find/, function(next) {
this.populate({
path: 'author',
select: 'name email avatar'
});
next();
});
module.exports = mongoose.model('Post', postSchema);
Passo 4: Implementar Authentication
4.1 JWT Middleware (src/middleware/auth.js)
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
// Middleware para verificar JWT token
exports.protect = async (req, res, next) => {
try {
// 1) Verificar se token existe
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({
success: false,
message: 'Acesso negado. Token não fornecido.'
});
}
// 2) Verificar token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 3) Verificar se usuário ainda existe
const user = await User.findById(decoded.id);
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Token inválido. Usuário não encontrado.'
});
}
// 4) Adicionar usuário ao request
req.user = user;
next();
} catch (error) {
return res.status(401).json({
success: false,
message: 'Token inválido.'
});
}
};
// Middleware para verificar roles específicos
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: 'Acesso negado. Permissões insuficientes.'
});
}
next();
};
};
// Middleware para verificar ownership (usuário pode editar apenas seus próprios posts)
exports.checkOwnership = (Model) => {
return async (req, res, next) => {
try {
const resource = await Model.findById(req.params.id);
if (!resource) {
return res.status(404).json({
success: false,
message: 'Recurso não encontrado'
});
}
// Admin pode editar tudo, user apenas seus próprios recursos
if (req.user.role !== 'admin' && resource.author.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
message: 'Acesso negado. Você só pode editar seus próprios recursos.'
});
}
req.resource = resource;
next();
} catch (error) {
return res.status(500).json({
success: false,
message: 'Erro interno do servidor'
});
}
};
};
4.2 Auth Controller (src/controllers/authController.js)
// src/controllers/authController.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
// Função para gerar JWT token
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE || '7d'
});
};
// Register novo usuário
exports.register = async (req, res) => {
try {
const { name, email, password } = req.body;
// Verificar se usuário já existe
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
success: false,
message: 'Usuário já existe com este email'
});
}
// Criar usuário
const user = await User.create({
name,
email,
password
});
// Gerar token
const token = generateToken(user._id);
res.status(201).json({
success: true,
message: 'Usuário criado com sucesso',
data: {
user: user.getPublicData(),
token
}
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
};
// Login usuário
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// Validar input
if (!email || !password) {
return res.status(400).json({
success: false,
message: 'Email e senha são obrigatórios'
});
}
// Buscar usuário (incluindo senha)
const user = await User.findOne({ email }).select('+password');
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Credenciais inválidas'
});
}
// Verificar senha
const isPasswordValid = await user.comparePassword(password);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: 'Credenciais inválidas'
});
}
// Gerar token
const token = generateToken(user._id);
res.json({
success: true,
message: 'Login realizado com sucesso',
data: {
user: user.getPublicData(),
token
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro interno do servidor'
});
}
};
// Obter perfil do usuário logado
exports.getProfile = async (req, res) => {
try {
res.json({
success: true,
data: {
user: req.user.getPublicData()
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro interno do servidor'
});
}
};
// Atualizar perfil
exports.updateProfile = async (req, res) => {
try {
const { name, avatar } = req.body;
const user = await User.findByIdAndUpdate(
req.user._id,
{ name, avatar },
{ new: true, runValidators: true }
);
res.json({
success: true,
message: 'Perfil atualizado com sucesso',
data: {
user: user.getPublicData()
}
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
};
Passo 5: Implementar CRUD Operations
5.1 Post Controller (src/controllers/postController.js)
// src/controllers/postController.js
const Post = require('../models/Post');
// GET /api/posts - Listar posts (com paginação, filtros, busca)
exports.getPosts = async (req, res) => {
try {
// Query building
let query = {};
// Filtros
if (req.query.status) query.status = req.query.status;
if (req.query.author) query.author = req.query.author;
if (req.query.featured) query.featured = req.query.featured === 'true';
// Busca por texto
if (req.query.search) {
query.$text = { $search: req.query.search };
}
// Tags
if (req.query.tags) {
query.tags = { $in: req.query.tags.split(',') };
}
// Paginação
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
// Sorting
const sortBy = req.query.sortBy || '-createdAt';
// Execute query
const posts = await Post.find(query)
.sort(sortBy)
.skip(skip)
.limit(limit);
// Total count para paginação
const total = await Post.countDocuments(query);
res.json({
success: true,
data: {
posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro ao buscar posts'
});
}
};
// GET /api/posts/:id - Obter post específico
exports.getPost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({
success: false,
message: 'Post não encontrado'
});
}
// Incrementar views
post.views += 1;
await post.save();
res.json({
success: true,
data: { post }
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro ao buscar post'
});
}
};
// POST /api/posts - Criar novo post
exports.createPost = async (req, res) => {
try {
const { title, content, summary, tags, status } = req.body;
const post = await Post.create({
title,
content,
summary,
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
status: status || 'draft',
author: req.user._id
});
res.status(201).json({
success: true,
message: 'Post criado com sucesso',
data: { post }
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
};
// PUT /api/posts/:id - Atualizar post
exports.updatePost = async (req, res) => {
try {
const { title, content, summary, tags, status, featured } = req.body;
const updateData = {};
if (title) updateData.title = title;
if (content) updateData.content = content;
if (summary) updateData.summary = summary;
if (tags) updateData.tags = tags.split(',').map(tag => tag.trim());
if (status) updateData.status = status;
if (featured !== undefined) updateData.featured = featured;
const post = await Post.findByIdAndUpdate(
req.params.id,
updateData,
{ new: true, runValidators: true }
);
res.json({
success: true,
message: 'Post atualizado com sucesso',
data: { post }
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
};
// DELETE /api/posts/:id - Deletar post
exports.deletePost = async (req, res) => {
try {
await Post.findByIdAndDelete(req.params.id);
res.json({
success: true,
message: 'Post deletado com sucesso'
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro ao deletar post'
});
}
};
// POST /api/posts/:id/like - Curtir/descurtir post
exports.likePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({
success: false,
message: 'Post não encontrado'
});
}
const userId = req.user._id;
const likeIndex = post.likes.indexOf(userId);
if (likeIndex === -1) {
// User hasn't liked the post, so add like
post.likes.push(userId);
} else {
// User has liked the post, so remove like
post.likes.splice(likeIndex, 1);
}
await post.save();
res.json({
success: true,
message: likeIndex === -1 ? 'Post curtido' : 'Curtida removida',
data: {
liked: likeIndex === -1,
likesCount: post.likes.length
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Erro ao processar curtida'
});
}
};
Passo 6: Configurar Routes
6.1 Auth Routes (src/routes/auth.js)
// src/routes/auth.js
const express = require('express');
const { body } = require('express-validator');
const {
register,
login,
getProfile,
updateProfile
} = require('../controllers/authController');
const { protect } = require('../middleware/auth');
const { validateRequest } = require('../middleware/validation');
const router = express.Router();
// Validações
const registerValidation = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('Nome deve ter entre 2 e 50 caracteres'),
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Email inválido'),
body('password')
.isLength({ min: 6 })
.withMessage('Senha deve ter pelo menos 6 caracteres')
];
const loginValidation = [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Email inválido'),
body('password')
.notEmpty()
.withMessage('Senha é obrigatória')
];
// Routes
router.post('/register', registerValidation, validateRequest, register);
router.post('/login', loginValidation, validateRequest, login);
router.get('/profile', protect, getProfile);
router.put('/profile', protect, updateProfile);
module.exports = router;
6.2 Posts Routes (src/routes/posts.js)
// src/routes/posts.js
const express = require('express');
const { body, param, query } = require('express-validator');
const {
getPosts,
getPost,
createPost,
updatePost,
deletePost,
likePost
} = require('../controllers/postController');
const { protect, authorize, checkOwnership } = require('../middleware/auth');
const { validateRequest } = require('../middleware/validation');
const Post = require('../models/Post');
const router = express.Router();
// Validações
const createPostValidation = [
body('title')
.trim()
.isLength({ min: 5, max: 100 })
.withMessage('Título deve ter entre 5 e 100 caracteres'),
body('content')
.trim()
.isLength({ min: 10 })
.withMessage('Conteúdo deve ter pelo menos 10 caracteres'),
body('summary')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('Resumo não pode ter mais de 200 caracteres'),
body('status')
.optional()
.isIn(['draft', 'published', 'archived'])
.withMessage('Status deve ser: draft, published ou archived')
];
const updatePostValidation = [
body('title')
.optional()
.trim()
.isLength({ min: 5, max: 100 })
.withMessage('Título deve ter entre 5 e 100 caracteres'),
body('content')
.optional()
.trim()
.isLength({ min: 10 })
.withMessage('Conteúdo deve ter pelo menos 10 caracteres'),
body('summary')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('Resumo não pode ter mais de 200 caracteres'),
body('status')
.optional()
.isIn(['draft', 'published', 'archived'])
.withMessage('Status deve ser: draft, published ou archived')
];
const paramValidation = [
param('id')
.isMongoId()
.withMessage('ID inválido')
];
// Public routes
router.get('/', getPosts);
router.get('/:id', paramValidation, validateRequest, getPost);
// Protected routes
router.use(protect); // Todas as rotas abaixo precisam de autenticação
router.post('/', createPostValidation, validateRequest, createPost);
router.post('/:id/like', paramValidation, validateRequest, likePost);
// Routes que precisam de ownership check
router.put('/:id',
paramValidation,
updatePostValidation,
validateRequest,
checkOwnership(Post),
updatePost
);
router.delete('/:id',
paramValidation,
validateRequest,
checkOwnership(Post),
deletePost
);
module.exports = router;
Passo 7: Middleware de Validação e Error Handling
7.1 Validation Middleware (src/middleware/validation.js)
// src/middleware/validation.js
const { validationResult } = require('express-validator');
exports.validateRequest = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Dados inválidos',
errors: errors.array().map(error => ({
field: error.path,
message: error.msg,
value: error.value
}))
});
}
next();
};
7.2 Error Handler (src/middleware/errorHandler.js)
// src/middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
console.error(err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Recurso não encontrado';
error = { message, statusCode: 404 };
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Recurso duplicado';
error = { message, statusCode: 400 };
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message);
error = { message, statusCode: 400 };
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const message = 'Token inválido';
error = { message, statusCode: 401 };
}
if (err.name === 'TokenExpiredError') {
const message = 'Token expirado';
error = { message, statusCode: 401 };
}
res.status(error.statusCode || 500).json({
success: false,
message: error.message || 'Erro interno do servidor',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = errorHandler;
Passo 8: Configurar Variáveis de Ambiente
8.1 Arquivo .env
# .env
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/blog-api
# Para MongoDB Atlas:
# MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/blog-api
# JWT
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRE=7d
# CORS
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
8.2 Arquivo .gitignore
# .gitignore
node_modules/
.env
.env.local
.env.production
.env.test
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
logs/
*.log
coverage/
.nyc_output/
dist/
build/
Passo 9: Testar a API
9.1 Iniciar Servidor
# Iniciar em modo desenvolvimento
npm run dev
# Servidor deve estar rodando em http://localhost:3000
9.2 Testes com Postman/cURL
Registrar Usuário
POST http://localhost:3000/api/auth/register
Content-Type: application/json
{
"name": "João Silva",
"email": "joao@email.com",
"password": "123456"
}
Login
POST http://localhost:3000/api/auth/login
Content-Type: application/json
{
"email": "joao@email.com",
"password": "123456"
}
Criar Post (precisa do token)
POST http://localhost:3000/api/posts
Authorization: Bearer SEU_TOKEN_AQUI
Content-Type: application/json
{
"title": "Meu Primeiro Post",
"content": "Este é o conteúdo do meu primeiro post na API.",
"summary": "Resumo do primeiro post",
"tags": "nodejs, express, api",
"status": "published"
}
Listar Posts
GET http://localhost:3000/api/posts?page=1&limit=10&status=published
Passo 10: Deploy para Produção
10.1 Otimizações para Produção
// Adicionar ao package.json
{
"scripts": {
"start": "NODE_ENV=production node server.js",
"build": "echo 'No build step required'",
"lint": "eslint src/",
"test": "jest --coverage"
}
}
10.2 Deploy no Heroku
# Instalar Heroku CLI e fazer login
heroku login
# Criar app
heroku create nome-da-sua-api
# Configurar variáveis de ambiente
heroku config:set NODE_ENV=production
heroku config:set JWT_SECRET=production-jwt-secret-super-secure
heroku config:set MONGODB_URI=sua-string-mongodb-atlas
# Deploy
git add .
git commit -m "Deploy inicial"
git push heroku main
# Verificar logs
heroku logs --tail
10.3 Deploy no Vercel
# Instalar Vercel CLI
npm i -g vercel
# Deploy
vercel
# Configurar environment variables no dashboard da Vercel:
# NODE_ENV=production
# JWT_SECRET=production-jwt-secret
# MONGODB_URI=sua-string-mongodb-atlas
Best Practices e Segurança
Checklist de Segurança
Implementadas neste Tutorial
- ✅ Helmet.js para headers de segurança
- ✅ Rate limiting para prevenir spam
- ✅ Input validation com express-validator
- ✅ Password hashing com bcrypt
- ✅ JWT authentication
- ✅ CORS configurado
- ✅ Error handling sem exposição de dados
- ✅ Environment variables para secrets
Próximos Passos (Produção)
- 🔄 HTTPS obrigatório
- 🔄 API versioning (/api/v1/)
- 🔄 Logging com Winston
- 🔄 Monitoring com New Relic
- 🔄 Database backup automático
- 🔄 Documentation com Swagger
- 🔄 Load testing
- 🔄 Health checks avançados
Performance Tips
- Database Indexing: Criar indexes para queries frequentes
- Caching: Redis para cache de queries caras
- Compression: Usar compression middleware
- Connection Pooling: Configurar pool do MongoDB
- Pagination: Sempre limitar resultados de queries
Nossa Recomendação Final
Esta API REST serve como foundation sólida para aplicações modernas. O código é production-ready e segue best practices de segurança, performance e maintainability.
Próximos Passos
- Adicionar Testes: Unit tests e integration tests
- Documentation: OpenAPI/Swagger para documentação automática
- Real-time Features: WebSockets com Socket.io
- File Upload: Multer para upload de imagens
- Email Service: Nodemailer para notifications
- Advanced Auth: OAuth, 2FA, refresh tokens
Node.js + Express continua sendo escolha dominante para APIs REST devido à performance, ecossistema e facilidade de desenvolvimento. Dominar essas tecnologias é skill essential para backend developers em 2025.
Continue Aprendendo Backend Development
Esta API é foundation. Explore tópicos avançados como microservices, GraphQL, testing strategies e DevOps para se tornar backend developer completo.
Ver Mais Tutoriais Backend →