Construa do zero um projeto full‑stack com Node.js e React

Construa do zero um projeto full‑stack com Node.js e React
Objetivo: Demonstrar, de forma hands‑on, como iniciar, desenvolver e publicar um aplicativo completo que combina um backend em Node.js, um banco PostgreSQL, um frontend React e automação de entrega contínua usando Docker e GitHub Actions.
Introdução
A criação de um produto digital costuma envolver várias camadas: servidor, banco de dados, interface de usuário e processos de publicação. Embora existam inúmeras ferramentas, montar tudo “na unha” ainda é a melhor maneira de entender as interdependências e garantir que cada parte esteja configurada de forma otimizada.
Neste tutorial, você vai:
Ao final, você terá um repositório pronto para ser clonado, executado localmente e enviado para produção com apenas um clique.
1. Preparando o ambiente
Antes de escrever código, certifique‑se de que as seguintes ferramentas estejam instaladas:
| Ferramenta | Versão mínima | Como instalar |
|---|---|---|
| Node.js | 18.x | |
| Docker Desktop | 24.x | |
| Git | 2.30+ | |
| VS Code (ou outro editor) | — |
Dica: Use o
nvm(Node Version Manager) para alternar entre versões do Node sem conflitos.
# Verificando versões
node -v
docker --version
git --version
2. Estrutura de pastas e configuração do backend
Crie a pasta raiz do projeto e inicie o repositório Git:
mkdir fullstack-node-react
cd fullstack-node-react
git init
2.1. Backend
mkdir backend
cd backend
npm init -y
npm install express sequelize pg pg-hstore dotenv cors
npm install --save-dev nodemon
2.1.1. Arquivo de configuração .env
# backend/.env
DB_HOST=postgres
DB_PORT=5432
DB_NAME=fullstack_db
DB_USER=admin
DB_PASSWORD=secret
PORT=4000
2.1.2. Estrutura de diretórios
backend/
│ app.js
│ server.js
│ .env
│ package.json
│
├─ config/
│ database.js
│
├─ models/
│ User.js
│
└─ routes/
users.js
2.1.3. Conexão com o banco (config/database.js)
// backend/config/database.js
const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false,
}
);
module.exports = sequelize;
2.1.4. Modelo de usuário (models/User.js)
// backend/models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const User = sequelize.define('User', {
// id será criado automaticamente
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: { isEmail: true },
},
});
module.exports = User;
2.1.5. Rotas (routes/users.js)
// backend/routes/users.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
// CREATE – cadastrar novo usuário
router.post('/', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// READ – listar todos
router.get('/', async (req, res) => {
const users = await User.findAll();
res.json(users);
});
// READ – buscar por id
router.get('/:id', async (req, res) => {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'Não encontrado' });
res.json(user);
});
// UPDATE – alterar dados
router.put('/:id', async (req, res) => {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'Não encontrado' });
await user.update(req.body);
res.json(user);
});
// DELETE – remover registro
router.delete('/:id', async (req, res) => {
const rows = await User.destroy({ where: { id: req.params.id } });
if (!rows) return res.status(404).json({ error: 'Não encontrado' });
res.status(204).send();
});
module.exports = router;
2.1.6. Aplicação principal (app.js)
// backend/app.js
const express = require('express');
const cors = require('cors');
const sequelize = require('./config/database');
const userRoutes = require('./routes/users');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.json());
// Rotas
app.use('/api/users', userRoutes);
// Sincronizar modelo e iniciar servidor
(async () => {
try {
await sequelize.authenticate();
await sequelize.sync({ alter: true }); // cria/atualiza tabelas
console.log('Conexão com o banco estabelecida.');
} catch (err) {
console.error('Falha ao conectar ao banco:', err);
process.exit(1);
}
})();
module.exports = app;
2.1.7. Servidor (server.js)
// backend/server.js
const http = require('http');
const app = require('./app');
require('dotenv').config();
const PORT = process.env.PORT || 4000;
const server = http.createServer(app);
server.listen(PORT, () => {
console.log(🚀 Servidor rodando em http://localhost:${PORT});
});
2.1.8. Scripts de desenvolvimento (package.json)
// backend/package.json (trecho)
{
"scripts": {
"dev": "nodemon server.js",
"start": "node server.js"
}
}
Com npm run dev o backend será recarregado automaticamente a cada alteração.
3. Frontend com React e Vite
Volte à raiz do projeto e crie a camada de cliente:
cd ..
npm create vite@latest frontend -- --template react
cd frontend
npm install
npm install axios


