Este documento descreve um pequeno código da estruturas Backend, Frontend e Docker utilizado no projeto Task-Manager. Abaixo está o índice para facilitar a navegação.
-
1.1 config
1.2 controllers
1.3 middlewares
1.4. models
1.5 routes
1.6. utils
1.7. app
-
2.1 api
2.2 componentss
2.3 contexts
2.4 pages
2.5 App
2.6 styles
-
3.1 docker
├── src/ │ ├── config/ # Configurações do banco de dados │ ├── controllers/ # Lógica dos endpoints │ ├── middlewares/ # Middlewares de autenticação │ ├── models/ # Modelos do Sequelize │ ├── routes/ # Definições de rotas │ ├── utils/ # Utilitários globais │ └── app.js # Configuração principal
const { Sequelize } = require('sequelize');
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;
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ where: { email } });
if (user && (await user.comparePassword(password))) {
res.json({
id: user.id,
username: user.username,
email: user.email,
token: generateToken(user.id),
});
} else {
res.status(401);
throw new Error('Invalid email or password');
}
});
const createTask = asyncHandler(async (req, res) => {
try {
const { title, description, taskDate } = req.body;
if (!title) {
return res.status(400).json({ message: "O título é obrigatório" });
}
// Verificação reforçada do usuário
if (!req.user?.id) {
return res.status(401).json({ message: 'Usuário não identificado' });
}
const task = await Task.create({
title,
description,
taskDate,
userId: req.user.id
});
res.status(201).json(task);
// Buscar a tarefa com dados completos
const createdTask = await Task.findByPk(task.id, {
include: [{ model: User, attributes: ['id', 'username'] }]
});
res.status(201).json(createdTask);
} catch (error) {
console.error('Erro no backend:', error);
res.status(500).json({
message: error.message || 'Erro interno no servidor'
});
}
});
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const protect = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1] ||
req.cookies?.token;
if (!token) {
return res.status(401).json({ message: 'Token não fornecido' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findByPk(decoded.id);
if (!req.user) {
return res.status(401).json({ message: 'Usuário não encontrado' });
}
next();
} catch (error) {
console.error('Erro na autenticação:', error);
res.status(401).json({ message: 'Token inválido' });
}
};
module.exports = { protect };const User = require('./User');
const Task = require('./Task');
module.exports = {
User,
Task
};const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/auth');
const {
loginUser,
registerUser,
getCurrentUser
} = require('../controllers/authController');
console.log({
loginUser,
registerUser,
getCurrentUser
});
router.get('/test', (req, res) => {
res.json({
message: 'Conexão com o backend estabelecida com sucesso!',
timestamp: new Date().toISOString()
});
});
// Rotas
router.post('/login', loginUser);
router.post('/register', registerUser);
router.get('/me', protect, getCurrentUser);
module.exports = router;const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/auth');
const {
getTasks,
createTask,
updateTask,
deleteTask
} = require('../controllers/taskController');
// Verifique se todas essas funções estão sendo importadas corretamente
console.log({ getTasks, createTask, updateTask, deleteTask });
router.route('/')
.get(protect, getTasks)
.post(protect, createTask);
router.route('/:id')
.put(protect, updateTask)
.delete(protect, deleteTask);
module.exports = router;const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
});
};
const notFound = (req, res, next) => {
const error = new Error(`Not found - ${req.originalUrl}`);
res.status(404);
next(error);
};
module.exports = { errorHandler, notFound };const express = require('express');
const cors = require('cors');
const { notFound, errorHandler } = require('./utils/errorHandler');
const authRoutes = require('./routes/authRoutes');
const taskRoutes = require('./routes/taskRoutes');
const app = express();
// Middleware
app.use(
cors({
origin: [
'http://localhost:5173',
'http://localhost:5180',
'http://backend:5173'
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
})
);
// Adicionar tratamento para requisições OPTIONS
app.options('*', cors());
app.use(express.json());
// Routes
app.get('/api', (req, res) => {
res.json({
message: 'API Task Manager está funcionando!',
endpoints: {
auth: '/api/auth',
tasks: '/api/tasks',
},
});
});
app.use('/api/auth', authRoutes);
app.use('/api/tasks', taskRoutes);
// Error Handling
app.use(notFound);
app.use(errorHandler);
module.exports = app;├── src/ │ ├── api/ # Configuração Axios e endpoints │ ├── assets/ # Arquivos estáticos │ ├── components/ # Componentes reutilizáveis │ ├── contexts/ # Contextos globais │ ├── pages/ # Páginas da aplicação └── styles/ # Estilos globais
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:5000/api',
timeout: 10000, // 10 segundos timeout
headers: {
'Content-Type': 'application/json'
}
});
// Interceptor para adicionar token
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config},
error => {
return Promise.reject(error);
}
);
// Interceptor de resposta
api.interceptors.response.use(
response => response,
error => {
if (error.code === 'ECONNABORTED') {
error.message = 'Timeout - Servidor demorou para responder';
}
return Promise.reject(error);
}
);
export default api;import { Outlet } from 'react-router-dom'
import Navbar from './Navbar'
const Layout = () => {
return (
<div className="relative min-h-screen">
<Navbar />
<div className="relative z-10 pt-16 min-h-[calc(100vh-64px)]">
<Outlet />
</div>
</div>
)
}
export default Layoutimport { Link } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
const Navbar = () => {
const { user, logout } = useAuth()
return (
<nav>
<div className="nav-brand">
<Link to="/" className="text-xl">
Task Manager <span>🏠</span>
</Link>
</div>
<div className="nav-links">
<Link to="/login" className="btn-primary">
Login
</Link>
<Link to="/register" className="btn-primary">
Registrar
</Link>
</div>
</nav>
)
}
export default Navbar
const AuthContext = createContext()
const login = async (credentials) => {
try {
const { data } = await api.post('/auth/login', {
email: credentials.email,
password: credentials.password
})
localStorage.setItem('token', data.token)
setUser({ username: data.username, email: data.email })
navigate('/dashboard')
return data
} catch (error) {
throw error.response?.data || { message: error.message || 'Login failed' }
}
}const TaskContext = createContext()
const fetchTasks = useCallback(async () => {
try {
setLoading(true);
const tasksData = await getTasks();
setTasks(tasksData.reverse()); // Ordenar do mais novo para mais antigo
} catch (error) {
console.error('Error:', error);
setTasks([]);
} finally {
setLoading(false);
}
}, [])
const Login = () => {
const [credentials, setCredentials] = useState({
email: '',
password: '',
});
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState({});
const { login } = useAuth();
const navigate = useNavigate();
const handleChange = (e) => {
setCredentials({
...credentials,
[e.target.name]: e.target.value,
});
// Limpa erros ao modificar o campo
setErrors({ ...errors, [e.target.name]: null });
};const Register = () => {
const [userData, setUserData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [loading, setLoading] = useState(false)
const [passwordError, setPasswordError] = useState('')
const [confirmPasswordError, setConfirmPasswordError] = useState('')
const { register } = useAuth()
const navigate = useNavigate()const Dashboard = () => {
const [showForm, setShowForm] = useState(false)
const { user } = useAuth()
return (
<div className="max-w-3xl mx-auto px-4 py-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4" >
<div>
<h1 className="text-2xl md:text-3xl font-bold text-white">
Olá, {user?.username} 👋
</h1>
<p className="text-gray-500 mt-1">Suas tarefas organizadas</p>
</div>
<button
type='button'
onClick={() => setShowForm(!showForm)}
className="btn-primary px-6 py-2.5 text-sm font-medium rounded-lg transition-all
hover:bg-primary/90 transform hover:scale-[1.02] shadow-sm w-full sm:w-auto"
>
{showForm ? (
<span>✖ Fechar Formulário</span>
) : (
<span>➕ Nova Tarefa</span>
)}
</button>
</div>
{showForm && (
<div className="max-w-3xl mx-auto px-4 py-8 h-[calc(100vh-160px)]">
<div className="animate-fade-in-up mb-8 task-form-scroll">
<TaskForm onCancel={() => setShowForm(false)} />
</div>
</div>
)}
<TaskList />
</div>
)
}
export default Dashboardfunction App() {
return (
<div className="min-h-screen bg-gray-800 !important">
<TaskProvider>
<AuthProvider>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="profile" element={<Profile />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
<ToastContainer
position="top-center"
autoClose={5000}
newestOnTop
closeOnClick
pauseOnFocusLoss
draggable
pauseOnHover
/>
</AuthProvider>
</TaskProvider>
</div>
)
}
export default App@tailwind base;
@tailwind components;
@tailwind utilities;
nav {
@apply fixed top-0 left-0 right-0 h-20 flex justify-between items-center;
background: linear-gradient(to bottom, #131727, #2b355a, #131727);
padding: 0 25px;
top: 4px;
z-index: 100;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.5);
}
.nav-brand {
display: flex;
align-items: center;
height: 100%;
gap: 15px
}
.nav-links {
display: flex;
align-items: center;
gap: 25px;
height: 100%;
}
.text-xl {
@apply text-white font-bold;
font-size: 2.25rem;
}
.text-xl:active {
color: #86efac;
}
.nav-brand span {
font-size: 1.65rem;
}
.btn-primary {
@apply bg-gradient-to-r from-blue-500 to-purple-600 text-white px-4 py-2 rounded-xl shadow-md hover:from-blue-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 active:scale-95 transform transition-all duration-200;
font-size: 1.25rem;
}
@media (max-width: 768px) {
.text-xl{
font-size: 1.5rem;
margin-top: 8px;
}
.nav-links{
gap: 10px;
}
.nav-brand {
font-size: 1.5rem;
}
.nav-brand span {
font-size: 1.2rem;
}
.btn-primary {
font-size: 1rem;
padding: 0.5rem 1rem;
}
}
├── docker/docker-compose.yml
services:
backend:
build:
context: ../backend
dockerfile: Dockerfile
environment:
NODE_ENV: development
DB_HOST: postgres
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: postgres
DB_NAME: taskmanager
JWT_SECRET: your_jwt_secret_key
JWT_EXPIRE: 30d
ports:
- '5000:5000'
depends_on:
postgres:
condition: service_healthy
networks:
- task-manager-network
frontend:
build:
context: ../frontend
dockerfile: Dockerfile
ports:
- "5173:5173"
volumes:
- ../frontend:/app
- /app/node_modules
environment:
- VITE_API_URL=http://localhost:5000/api
- VITE_API_URL=http://backend:5000/api
depends_on:
- backend
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: taskmanager
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- '5432:5432'
networks:
- task-manager-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
volumes:
postgres_data:
networks:
task-manager-network:
driver: bridge- O Task Manager desenvolvido neste projeto consolida uma arquitetura robusta e orientada à escalabilidade, exemplificando a integração harmoniosa de tecnologias fullstack por meio de uma estrutura organizacional clara e práticas de código sustentáveis. Ao aliar ferramentas modernas — como React para a interface dinâmica, Express para a camada de serviços, Sequelize para abstração do banco de dados e Docker para conteinerização — a padrões de desenvolvimento sólidos (modularidade, testes automatizados e separação de responsabilidades), a solução evidencia sua capacidade de adaptação a demandas crescentes sem comprometer a manutenibilidade.
- O resultado é uma aplicação coesa, testável em suas diferentes camadas e preparada para expansão funcional ou de infraestrutura. Além de validar a eficácia das escolhas tecnológicas adotadas, o projeto estabelece um paradigma prático para iniciativas fullstack, destacando-se como referência para o desenvolvimento de sistemas alinhados às exigências atuais de desempenho, organização e evolução contínua.