Tutoriais

Como Criar API REST com Node.js e Express: Tutorial Completo

Tutorial passo-a-passo completo para construir APIs REST profissionais usando Node.js e Express. Do setup inicial ao deploy em produção, incluindo authentication, middleware, error handling e best practices de segurança.

1 de jun. de 2025
19 min de leitura
Equipe Revitorial
Como Criar API REST com Node.js e Express: Tutorial Completo

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

  1. Adicionar Testes: Unit tests e integration tests
  2. Documentation: OpenAPI/Swagger para documentação automática
  3. Real-time Features: WebSockets com Socket.io
  4. File Upload: Multer para upload de imagens
  5. Email Service: Nodemailer para notifications
  6. 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
min6 de jun. de 2025

Cookies

Usamos cookies apenas para Analytics (Google Analytics) e AdSense para melhorar sua experiência.

Política