Semana 3: Express.js Avançado

Rotas Dinâmicas, Middleware Customizado, Estrutura de Projetos e APIs REST

🎯 Objetivo: Dominar Express.js avançado, organizar projetos profissionalmente e criar APIs REST completas

📊 Seu Progresso

0% concluído

1

Nível Básico

Express.js Avançado: Rotas Dinâmicas e Middleware Customizado

🛣️ Rotas Dinâmicas e Parâmetros

Aprenda a criar rotas flexíveis que capturam parâmetros da URL e query strings!

📝 Exemplo Prático:

// Simulação de Express.js para demonstração
const express = {
    routes: [],
    
    get(path, handler) {
        this.routes.push({ method: 'GET', path, handler });
        console.log(`✅ Rota GET ${path} registrada`);
    },
    
    post(path, handler) {
        this.routes.push({ method: 'POST', path, handler });
        console.log(`✅ Rota POST ${path} registrada`);
    },
    
    put(path, handler) {
        this.routes.push({ method: 'PUT', path, handler });
        console.log(`✅ Rota PUT ${path} registrada`);
    },
    
    delete(path, handler) {
        this.routes.push({ method: 'DELETE', path, handler });
        console.log(`✅ Rota DELETE ${path} registrada`);
    },
    
    // Simular requisição
    simulateRequest(method, url, body = null) {
        console.log(`\n🌐 ${method} ${url}`);
        
        // Encontrar rota correspondente
        const route = this.routes.find(r => {
            if (r.method !== method) return false;
            
            // Converter rota Express para regex
            const routeRegex = r.path
                .replace(/:[^/]+/g, '([^/]+)')
                .replace(/\//g, '\\/');
            
            return new RegExp(`^${routeRegex}$`).test(url.split('?')[0]);
        });
        
        if (route) {
            // Extrair parâmetros
            const params = this.extractParams(route.path, url);
            const query = this.extractQuery(url);
            
            const req = { params, query, body };
            const res = {
                json: (data) => console.log('📤 Resposta JSON:', JSON.stringify(data, null, 2)),
                send: (data) => console.log('📤 Resposta:', data),
                status: (code) => ({ 
                    json: (data) => console.log(`📤 Status ${code}:`, JSON.stringify(data, null, 2)),
                    send: (data) => console.log(`📤 Status ${code}:`, data)
                })
            };
            
            route.handler(req, res);
        } else {
            console.log('❌ Rota não encontrada - 404');
        }
    },
    
    extractParams(routePath, url) {
        const routeParts = routePath.split('/');
        const urlParts = url.split('?')[0].split('/');
        const params = {};
        
        routeParts.forEach((part, index) => {
            if (part.startsWith(':')) {
                const paramName = part.slice(1);
                params[paramName] = urlParts[index];
            }
        });
        
        return params;
    },
    
    extractQuery(url) {
        const queryString = url.split('?')[1];
        if (!queryString) return {};
        
        const query = {};
        queryString.split('&').forEach(param => {
            const [key, value] = param.split('=');
            query[key] = decodeURIComponent(value || '');
        });
        
        return query;
    }
};

// Banco de dados simulado
const usuarios = [
    { id: 1, nome: 'Ana Silva', email: 'ana@email.com', idade: 28 },
    { id: 2, nome: 'João Santos', email: 'joao@email.com', idade: 35 },
    { id: 3, nome: 'Maria Costa', email: 'maria@email.com', idade: 42 }
];

const posts = [
    { id: 1, titulo: 'Primeiro Post', conteudo: 'Conteúdo do primeiro post', autorId: 1 },
    { id: 2, titulo: 'Segundo Post', conteudo: 'Conteúdo do segundo post', autorId: 2 },
    { id: 3, titulo: 'Terceiro Post', conteudo: 'Conteúdo do terceiro post', autorId: 1 }
];

console.log('=== CONFIGURANDO ROTAS DINÂMICAS ===');

// 1. Rotas básicas
express.get('/', (req, res) => {
    res.json({ message: 'API funcionando!', timestamp: new Date().toISOString() });
});

// 2. Listar todos os usuários com filtros
express.get('/usuarios', (req, res) => {
    let resultado = [...usuarios];
    
    // Filtrar por idade mínima
    if (req.query.idadeMin) {
        const idadeMin = parseInt(req.query.idadeMin);
        resultado = resultado.filter(u => u.idade >= idadeMin);
    }
    
    // Filtrar por nome
    if (req.query.nome) {
        resultado = resultado.filter(u => 
            u.nome.toLowerCase().includes(req.query.nome.toLowerCase())
        );
    }
    
    res.json({
        total: resultado.length,
        filtros: req.query,
        usuarios: resultado
    });
});

// 3. Buscar usuário específico
express.get('/usuarios/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const usuario = usuarios.find(u => u.id === id);
    
    if (usuario) {
        res.json(usuario);
    } else {
        res.status(404).json({ erro: 'Usuário não encontrado' });
    }
});

// 4. Posts de um usuário específico
express.get('/usuarios/:id/posts', (req, res) => {
    const autorId = parseInt(req.params.id);
    const postsDoUsuario = posts.filter(p => p.autorId === autorId);
    
    res.json({
        autorId,
        total: postsDoUsuario.length,
        posts: postsDoUsuario
    });
});

// 5. Buscar posts com filtros
express.get('/posts', (req, res) => {
    let resultado = [...posts];
    
    // Filtrar por autor
    if (req.query.autor) {
        const autorId = parseInt(req.query.autor);
        resultado = resultado.filter(p => p.autorId === autorId);
    }
    
    // Buscar no título
    if (req.query.busca) {
        resultado = resultado.filter(p => 
            p.titulo.toLowerCase().includes(req.query.busca.toLowerCase())
        );
    }
    
    res.json({
        total: resultado.length,
        filtros: req.query,
        posts: resultado
    });
});

// 6. Post específico
express.get('/posts/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const post = posts.find(p => p.id === id);
    
    if (post) {
        // Incluir informações do autor
        const autor = usuarios.find(u => u.id === post.autorId);
        res.json({
            ...post,
            autor: autor ? { nome: autor.nome, email: autor.email } : null
        });
    } else {
        res.status(404).json({ erro: 'Post não encontrado' });
    }
});

console.log('\n=== TESTANDO ROTAS ===');

// Testando as rotas
express.simulateRequest('GET', '/');
express.simulateRequest('GET', '/usuarios');
express.simulateRequest('GET', '/usuarios?idadeMin=30');
express.simulateRequest('GET', '/usuarios?nome=ana');
express.simulateRequest('GET', '/usuarios/1');
express.simulateRequest('GET', '/usuarios/999');
express.simulateRequest('GET', '/usuarios/1/posts');
express.simulateRequest('GET', '/posts?autor=1');
express.simulateRequest('GET', '/posts?busca=primeiro');
express.simulateRequest('GET', '/posts/1');

📊 Tipos de Parâmetros:

Route Params (:id)

Parâmetros obrigatórios na URL

Query Strings (?nome=valor)

Parâmetros opcionais para filtros

Body (POST/PUT)

Dados enviados no corpo da requisição

Headers

Metadados da requisição


                    

⚙️ Middleware Customizado

Middleware são funções que executam durante o ciclo de vida da requisição. Vamos criar nossos próprios!

📝 Exemplo Prático:

// Sistema de middleware customizado
class ExpressSimulator {
    constructor() {
        this.middlewares = [];
        this.routes = [];
    }
    
    // Registrar middleware global
    use(middleware) {
        this.middlewares.push(middleware);
        console.log('🔧 Middleware registrado');
    }
    
    // Registrar rota com middleware específico
    get(path, ...handlers) {
        const middleware = handlers.slice(0, -1);
        const handler = handlers[handlers.length - 1];
        
        this.routes.push({
            method: 'GET',
            path,
            middleware,
            handler
        });
        
        console.log(`✅ Rota GET ${path} registrada com ${middleware.length} middleware(s)`);
    }
    
    // Simular requisição
    async simulateRequest(method, url, headers = {}, body = null) {
        console.log(`\n🌐 ${method} ${url}`);
        console.log('📋 Headers:', headers);
        
        const req = {
            method,
            url,
            headers,
            body,
            params: {},
            query: {},
            user: null,
            startTime: Date.now()
        };
        
        const res = {
            statusCode: 200,
            headers: {},
            
            status(code) {
                this.statusCode = code;
                return this;
            },
            
            json(data) {
                console.log(`📤 Status ${this.statusCode}:`, JSON.stringify(data, null, 2));
                return this;
            },
            
            send(data) {
                console.log(`📤 Status ${this.statusCode}:`, data);
                return this;
            },
            
            setHeader(name, value) {
                this.headers[name] = value;
            }
        };
        
        let index = 0;
        
        // Função next para passar para próximo middleware
        const next = (error) => {
            if (error) {
                console.log('❌ Erro no middleware:', error.message);
                return res.status(500).json({ erro: error.message });
            }
            
            // Executar middlewares globais
            if (index < this.middlewares.length) {
                const middleware = this.middlewares[index++];
                return middleware(req, res, next);
            }
            
            // Encontrar e executar rota
            const route = this.routes.find(r => {
                if (r.method !== method) return false;
                
                const routeRegex = r.path
                    .replace(/:[^/]+/g, '([^/]+)')
                    .replace(/\//g, '\\/');
                
                return new RegExp(`^${routeRegex}$`).test(url.split('?')[0]);
            });
            
            if (route) {
                // Extrair parâmetros
                req.params = this.extractParams(route.path, url);
                req.query = this.extractQuery(url);
                
                // Executar middlewares da rota
                let routeIndex = 0;
                
                const nextRoute = (error) => {
                    if (error) {
                        console.log('❌ Erro no middleware da rota:', error.message);
                        return res.status(500).json({ erro: error.message });
                    }
                    
                    if (routeIndex < route.middleware.length) {
                        const middleware = route.middleware[routeIndex++];
                        return middleware(req, res, nextRoute);
                    }
                    
                    // Executar handler final
                    route.handler(req, res);
                };
                
                nextRoute();
            } else {
                res.status(404).json({ erro: 'Rota não encontrada' });
            }
        };
        
        next();
    }
    
    extractParams(routePath, url) {
        const routeParts = routePath.split('/');
        const urlParts = url.split('?')[0].split('/');
        const params = {};
        
        routeParts.forEach((part, index) => {
            if (part.startsWith(':')) {
                const paramName = part.slice(1);
                params[paramName] = urlParts[index];
            }
        });
        
        return params;
    }
    
    extractQuery(url) {
        const queryString = url.split('?')[1];
        if (!queryString) return {};
        
        const query = {};
        queryString.split('&').forEach(param => {
            const [key, value] = param.split('=');
            query[key] = decodeURIComponent(value || '');
        });
        
        return query;
    }
}

// Criar instância do simulador
const app = new ExpressSimulator();

console.log('=== CRIANDO MIDDLEWARES CUSTOMIZADOS ===');

// 1. Middleware de Log
const loggerMiddleware = (req, res, next) => {
    const timestamp = new Date().toISOString();
    console.log(`📝 [${timestamp}] ${req.method} ${req.url}`);
    req.startTime = Date.now();
    next();
};

// 2. Middleware de Autenticação
const authMiddleware = (req, res, next) => {
    const token = req.headers.authorization;
    
    if (!token) {
        console.log('🔒 Token não fornecido');
        return res.status(401).json({ erro: 'Token de acesso necessário' });
    }
    
    // Simular validação de token
    if (token === 'Bearer token-valido') {
        req.user = { id: 1, nome: 'Usuário Logado', role: 'admin' };
        console.log('✅ Usuário autenticado:', req.user.nome);
        next();
    } else {
        console.log('❌ Token inválido');
        res.status(401).json({ erro: 'Token inválido' });
    }
};

// 3. Middleware de Validação
const validateUserMiddleware = (req, res, next) => {
    if (req.method === 'POST' || req.method === 'PUT') {
        const { nome, email } = req.body || {};
        
        if (!nome || nome.length < 2) {
            return res.status(400).json({ erro: 'Nome deve ter pelo menos 2 caracteres' });
        }
        
        if (!email || !email.includes('@')) {
            return res.status(400).json({ erro: 'Email inválido' });
        }
        
        console.log('✅ Dados validados com sucesso');
    }
    
    next();
};

// 4. Middleware de Rate Limiting
const rateLimitMiddleware = (() => {
    const requests = new Map();
    
    return (req, res, next) => {
        const ip = req.headers['x-forwarded-for'] || 'localhost';
        const now = Date.now();
        const windowMs = 60000; // 1 minuto
        const maxRequests = 10;
        
        if (!requests.has(ip)) {
            requests.set(ip, []);
        }
        
        const userRequests = requests.get(ip);
        
        // Remover requisições antigas
        const validRequests = userRequests.filter(time => now - time < windowMs);
        
        if (validRequests.length >= maxRequests) {
            console.log(`🚫 Rate limit excedido para ${ip}`);
            return res.status(429).json({ erro: 'Muitas requisições. Tente novamente em 1 minuto.' });
        }
        
        validRequests.push(now);
        requests.set(ip, validRequests);
        
        console.log(`📊 Rate limit: ${validRequests.length}/${maxRequests} para ${ip}`);
        next();
    };
})();

// 5. Middleware de Tempo de Resposta
const responseTimeMiddleware = (req, res, next) => {
    const originalJson = res.json;
    const originalSend = res.send;
    
    res.json = function(data) {
        const responseTime = Date.now() - req.startTime;
        console.log(`⏱️ Tempo de resposta: ${responseTime}ms`);
        res.setHeader('X-Response-Time', `${responseTime}ms`);
        return originalJson.call(this, data);
    };
    
    res.send = function(data) {
        const responseTime = Date.now() - req.startTime;
        console.log(`⏱️ Tempo de resposta: ${responseTime}ms`);
        res.setHeader('X-Response-Time', `${responseTime}ms`);
        return originalSend.call(this, data);
    };
    
    next();
};

// Registrar middlewares globais
app.use(loggerMiddleware);
app.use(rateLimitMiddleware);
app.use(responseTimeMiddleware);

// Rotas com middlewares específicos
app.get('/', (req, res) => {
    res.json({ message: 'API funcionando!', timestamp: new Date().toISOString() });
});

app.get('/publico', (req, res) => {
    res.json({ message: 'Rota pública - sem autenticação necessária' });
});

app.get('/privado', authMiddleware, (req, res) => {
    res.json({ 
        message: 'Rota privada - acesso autorizado',
        usuario: req.user
    });
});

app.get('/admin', authMiddleware, (req, res, next) => {
    if (req.user.role !== 'admin') {
        return res.status(403).json({ erro: 'Acesso negado - apenas administradores' });
    }
    next();
}, (req, res) => {
    res.json({ 
        message: 'Área administrativa',
        usuario: req.user,
        permissoes: ['criar', 'editar', 'deletar']
    });
});

console.log('\n=== TESTANDO MIDDLEWARES ===');

// Testando as rotas
app.simulateRequest('GET', '/');
app.simulateRequest('GET', '/publico');
app.simulateRequest('GET', '/privado'); // Sem token
app.simulateRequest('GET', '/privado', { authorization: 'Bearer token-invalido' });
app.simulateRequest('GET', '/privado', { authorization: 'Bearer token-valido' });
app.simulateRequest('GET', '/admin', { authorization: 'Bearer token-valido' });

🔄 Fluxo do Middleware:

📋 Ordem de Execução:
1 Middlewares Globais
2 Middlewares da Rota
3 Handler da Rota
💡 Tipos de Middleware:
  • Logger: Registra requisições
  • Auth: Verifica autenticação
  • Validation: Valida dados
  • Rate Limit: Controla frequência
  • CORS: Controla origem

                    

🎯 Exercício Prático 1: API de Produtos

Crie uma API completa para gerenciar produtos com rotas dinâmicas e middleware de validação!

📋 Requisitos:

  • Rotas: GET /produtos, GET /produtos/:id, POST /produtos
  • Middleware de validação para criação de produtos
  • Filtros por categoria e preço mínimo/máximo
  • Tratamento de erros (produto não encontrado)

💻 Seu Código:

📊 Resultado:

Execute seu código para ver o resultado...
📊 Score: 0/100
✅ Testes: 0/4
🎯 Status: Pendente
💻

Console Interativo

Teste seus conhecimentos em Express.js avançado

💻 Editor de Código

📊 Saída


                        

📈 Estatísticas

Execuções:
0
Sucessos:
0

🎯 Desafios Rápidos

🛣️ Rotas Dinâmicas

Crie uma rota que capture ID e categoria

⚙️ Middleware

Implemente middleware de autenticação

🌐 API REST

Construa CRUD completo com validação

🎉 Parabéns! Você completou a Semana 3

🛣️

Rotas Dinâmicas

Parâmetros, query strings e middleware customizado

🗂️

Estrutura de Projetos

express.Router, controllers e modularização

🌐

APIs REST

CRUD completo, validação e boas práticas

🚀 Próxima Semana: Bancos de Dados

Na Semana 4, você aprenderá sobre MongoDB, Mongoose e persistência de dados!

2

Nível Intermediário

Estrutura de Projetos: express.Router e Controllers

🗂️ Express.Router - Modularização

Organize suas rotas em módulos separados para manter o código limpo e escalável!

📝 Exemplo Prático:

// Simulação de estrutura de projeto modular
console.log('=== ESTRUTURA DE PROJETO EXPRESS ===');

// 1. Simulação do express.Router
class Router {
    constructor() {
        this.routes = [];
        this.middlewares = [];
    }
    
    use(middleware) {
        this.middlewares.push(middleware);
        console.log('🔧 Middleware adicionado ao router');
    }
    
    get(path, ...handlers) {
        this.routes.push({ method: 'GET', path, handlers });
        console.log(`✅ Rota GET ${path} registrada no router`);
    }
    
    post(path, ...handlers) {
        this.routes.push({ method: 'POST', path, handlers });
        console.log(`✅ Rota POST ${path} registrada no router`);
    }
    
    put(path, ...handlers) {
        this.routes.push({ method: 'PUT', path, handlers });
        console.log(`✅ Rota PUT ${path} registrada no router`);
    }
    
    delete(path, ...handlers) {
        this.routes.push({ method: 'DELETE', path, handlers });
        console.log(`✅ Rota DELETE ${path} registrada no router`);
    }
}

// 2. Controllers - Lógica de negócio separada
class UsuarioController {
    static usuarios = [
        { id: 1, nome: 'Ana Silva', email: 'ana@email.com', role: 'admin' },
        { id: 2, nome: 'João Santos', email: 'joao@email.com', role: 'user' },
        { id: 3, nome: 'Maria Costa', email: 'maria@email.com', role: 'user' }
    ];
    
    static listarTodos(req, res) {
        console.log('📋 Controller: Listando todos os usuários');
        
        let usuarios = [...UsuarioController.usuarios];
        
        // Filtros
        if (req.query.role) {
            usuarios = usuarios.filter(u => u.role === req.query.role);
        }
        
        if (req.query.busca) {
            const busca = req.query.busca.toLowerCase();
            usuarios = usuarios.filter(u => 
                u.nome.toLowerCase().includes(busca) ||
                u.email.toLowerCase().includes(busca)
            );
        }
        
        res.json({
            total: usuarios.length,
            filtros: req.query,
            usuarios
        });
    }
    
    static buscarPorId(req, res) {
        console.log(`🔍 Controller: Buscando usuário ID ${req.params.id}`);
        
        const id = parseInt(req.params.id);
        const usuario = UsuarioController.usuarios.find(u => u.id === id);
        
        if (usuario) {
            res.json(usuario);
        } else {
            res.status(404).json({ erro: 'Usuário não encontrado' });
        }
    }
    
    static criar(req, res) {
        console.log('➕ Controller: Criando novo usuário');
        
        const { nome, email, role = 'user' } = req.body;
        
        // Verificar se email já existe
        const emailExiste = UsuarioController.usuarios.some(u => u.email === email);
        if (emailExiste) {
            return res.status(400).json({ erro: 'Email já cadastrado' });
        }
        
        const novoUsuario = {
            id: Math.max(...UsuarioController.usuarios.map(u => u.id)) + 1,
            nome,
            email,
            role
        };
        
        UsuarioController.usuarios.push(novoUsuario);
        
        res.status(201).json({
            message: 'Usuário criado com sucesso',
            usuario: novoUsuario
        });
    }
    
    static atualizar(req, res) {
        console.log(`✏️ Controller: Atualizando usuário ID ${req.params.id}`);
        
        const id = parseInt(req.params.id);
        const index = UsuarioController.usuarios.findIndex(u => u.id === id);
        
        if (index === -1) {
            return res.status(404).json({ erro: 'Usuário não encontrado' });
        }
        
        const { nome, email, role } = req.body;
        
        // Verificar se novo email já existe (exceto o próprio usuário)
        if (email) {
            const emailExiste = UsuarioController.usuarios.some(u => 
                u.email === email && u.id !== id
            );
            if (emailExiste) {
                return res.status(400).json({ erro: 'Email já cadastrado' });
            }
        }
        
        // Atualizar apenas campos fornecidos
        if (nome) UsuarioController.usuarios[index].nome = nome;
        if (email) UsuarioController.usuarios[index].email = email;
        if (role) UsuarioController.usuarios[index].role = role;
        
        res.json({
            message: 'Usuário atualizado com sucesso',
            usuario: UsuarioController.usuarios[index]
        });
    }
    
    static deletar(req, res) {
        console.log(`🗑️ Controller: Deletando usuário ID ${req.params.id}`);
        
        const id = parseInt(req.params.id);
        const index = UsuarioController.usuarios.findIndex(u => u.id === id);
        
        if (index === -1) {
            return res.status(404).json({ erro: 'Usuário não encontrado' });
        }
        
        const usuarioRemovido = UsuarioController.usuarios.splice(index, 1)[0];
        
        res.json({
            message: 'Usuário deletado com sucesso',
            usuario: usuarioRemovido
        });
    }
}

// 3. Middleware de validação específico
const validarUsuario = (req, res, next) => {
    console.log('🔍 Middleware: Validando dados do usuário');
    
    const { nome, email, role } = req.body;
    
    if (!nome || nome.trim().length < 2) {
        return res.status(400).json({ erro: 'Nome deve ter pelo menos 2 caracteres' });
    }
    
    if (!email || !email.includes('@') || !email.includes('.')) {
        return res.status(400).json({ erro: 'Email inválido' });
    }
    
    if (role && !['admin', 'user'].includes(role)) {
        return res.status(400).json({ erro: 'Role deve ser "admin" ou "user"' });
    }
    
    console.log('✅ Dados válidos');
    next();
};

// 4. Criando router de usuários
console.log('\n📁 Criando router de usuários...');
const usuarioRouter = new Router();

// Middleware específico do router
usuarioRouter.use((req, res, next) => {
    console.log(`🔄 Middleware do router: ${req.method} ${req.url}`);
    next();
});

// Definindo rotas do router
usuarioRouter.get('/', UsuarioController.listarTodos);
usuarioRouter.get('/:id', UsuarioController.buscarPorId);
usuarioRouter.post('/', validarUsuario, UsuarioController.criar);
usuarioRouter.put('/:id', validarUsuario, UsuarioController.atualizar);
usuarioRouter.delete('/:id', UsuarioController.deletar);

// 5. Simulação do app principal
class ExpressApp {
    constructor() {
        this.routers = [];
    }
    
    use(path, router) {
        this.routers.push({ path, router });
        console.log(`🔗 Router registrado em ${path}`);
    }
    
    simulateRequest(method, url, body = null) {
        console.log(`\n🌐 ${method} ${url}`);
        if (body) console.log('📦 Body:', body);
        
        // Encontrar router correspondente
        for (const { path, router } of this.routers) {
            if (url.startsWith(path)) {
                const routePath = url.substring(path.length) || '/';
                
                // Encontrar rota no router
                const route = router.routes.find(r => {
                    if (r.method !== method) return false;
                    
                    const routeRegex = r.path
                        .replace(/:[^/]+/g, '([^/]+)')
                        .replace(/\//g, '\\/');
                    
                    return new RegExp(`^${routeRegex}$`).test(routePath.split('?')[0]);
                });
                
                if (route) {
                    const req = {
                        method,
                        url: routePath,
                        body,
                        params: this.extractParams(route.path, routePath),
                        query: this.extractQuery(routePath)
                    };
                    
                    const res = {
                        statusCode: 200,
                        status(code) {
                            this.statusCode = code;
                            return this;
                        },
                        json(data) {
                            console.log(`📤 Status ${this.statusCode}:`, JSON.stringify(data, null, 2));
                        }
                    };
                    
                    // Executar middlewares do router
                    let middlewareIndex = 0;
                    
                    const next = () => {
                        if (middlewareIndex < router.middlewares.length) {
                            const middleware = router.middlewares[middlewareIndex++];
                            middleware(req, res, next);
                        } else {
                            // Executar handlers da rota
                            let handlerIndex = 0;
                            
                            const nextHandler = () => {
                                if (handlerIndex < route.handlers.length) {
                                    const handler = route.handlers[handlerIndex++];
                                    handler(req, res, nextHandler);
                                }
                            };
                            
                            nextHandler();
                        }
                    };
                    
                    next();
                    return;
                }
            }
        }
        
        console.log('❌ Rota não encontrada - 404');
    }
    
    extractParams(routePath, url) {
        const routeParts = routePath.split('/');
        const urlParts = url.split('?')[0].split('/');
        const params = {};
        
        routeParts.forEach((part, index) => {
            if (part.startsWith(':')) {
                const paramName = part.slice(1);
                params[paramName] = urlParts[index];
            }
        });
        
        return params;
    }
    
    extractQuery(url) {
        const queryString = url.split('?')[1];
        if (!queryString) return {};
        
        const query = {};
        queryString.split('&').forEach(param => {
            const [key, value] = param.split('=');
            query[key] = decodeURIComponent(value || '');
        });
        
        return query;
    }
}

// 6. Configurando aplicação principal
console.log('\n🚀 Configurando aplicação principal...');
const app = new ExpressApp();

// Registrando router
app.use('/usuarios', usuarioRouter);

console.log('\n=== TESTANDO ESTRUTURA MODULAR ===');

// Testando as rotas
app.simulateRequest('GET', '/usuarios');
app.simulateRequest('GET', '/usuarios?role=admin');
app.simulateRequest('GET', '/usuarios/1');
app.simulateRequest('POST', '/usuarios', {
    nome: 'Pedro Oliveira',
    email: 'pedro@email.com',
    role: 'user'
});
app.simulateRequest('PUT', '/usuarios/1', {
    nome: 'Ana Silva Santos'
});
app.simulateRequest('DELETE', '/usuarios/2');

📁 Estrutura de Projeto:

🏗️ Organização Recomendada:
📁 projeto/
  📁 controllers/
    📄 usuarioController.js
    📄 produtoController.js
  📁 routes/
    📄 usuarios.js
    📄 produtos.js
  📁 middleware/
    📄 auth.js
    📄 validation.js
  📄 app.js
  📄 server.js
💡 Vantagens da Modularização:
  • Organização: Código mais limpo
  • Manutenção: Fácil de encontrar e editar
  • Reutilização: Controllers reutilizáveis
  • Testes: Mais fácil de testar
  • Colaboração: Equipe pode trabalhar em paralelo

                    

🎯 Exercício Prático 2: Sistema de Posts

Crie um sistema modular para gerenciar posts de blog usando Router e Controllers!

📋 Requisitos:

  • PostController com métodos CRUD completos
  • Router separado para rotas de posts
  • Middleware de validação para posts
  • Filtros por autor e categoria

💻 Seu Código:

📊 Resultado:

Execute seu código para ver o resultado...
📊 Score: 0/100
✅ Testes: 0/4
🎯 Status: Pendente
3

Nível Avançado

APIs REST Completas e Boas Práticas

🌐 APIs REST Completas

Aprenda a criar APIs REST profissionais com todas as boas práticas!

📝 Exemplo Prático:

// API REST Completa com Boas Práticas
console.log('=== API REST PROFISSIONAL ===');

// 1. Simulação de banco de dados
class Database {
    constructor() {
        this.usuarios = [
            { id: 1, nome: 'Ana Silva', email: 'ana@email.com', ativo: true, criadoEm: '2024-01-15' },
            { id: 2, nome: 'João Santos', email: 'joao@email.com', ativo: true, criadoEm: '2024-01-20' },
            { id: 3, nome: 'Maria Costa', email: 'maria@email.com', ativo: false, criadoEm: '2024-01-25' }
        ];
        this.nextId = 4;
    }
    
    findAll(filtros = {}) {
        let resultado = [...this.usuarios];
        
        if (filtros.ativo !== undefined) {
            resultado = resultado.filter(u => u.ativo === filtros.ativo);
        }
        
        if (filtros.busca) {
            const busca = filtros.busca.toLowerCase();
            resultado = resultado.filter(u => 
                u.nome.toLowerCase().includes(busca) ||
                u.email.toLowerCase().includes(busca)
            );
        }
        
        return resultado;
    }
    
    findById(id) {
        return this.usuarios.find(u => u.id === parseInt(id));
    }
    
    create(dados) {
        const novoUsuario = {
            id: this.nextId++,
            ...dados,
            ativo: true,
            criadoEm: new Date().toISOString().split('T')[0]
        };
        
        this.usuarios.push(novoUsuario);
        return novoUsuario;
    }
    
    update(id, dados) {
        const index = this.usuarios.findIndex(u => u.id === parseInt(id));
        if (index === -1) return null;
        
        this.usuarios[index] = { ...this.usuarios[index], ...dados };
        return this.usuarios[index];
    }
    
    delete(id) {
        const index = this.usuarios.findIndex(u => u.id === parseInt(id));
        if (index === -1) return null;
        
        return this.usuarios.splice(index, 1)[0];
    }
    
    emailExists(email, excludeId = null) {
        return this.usuarios.some(u => 
            u.email === email && u.id !== excludeId
        );
    }
}

const db = new Database();

// 2. Classe de resposta padronizada
class ApiResponse {
    static success(data, message = 'Sucesso', statusCode = 200) {
        return {
            success: true,
            statusCode,
            message,
            data,
            timestamp: new Date().toISOString()
        };
    }
    
    static error(message, statusCode = 500, details = null) {
        return {
            success: false,
            statusCode,
            message,
            details,
            timestamp: new Date().toISOString()
        };
    }
    
    static paginated(data, page, limit, total) {
        return {
            success: true,
            statusCode: 200,
            data,
            pagination: {
                page: parseInt(page),
                limit: parseInt(limit),
                total,
                pages: Math.ceil(total / limit)
            },
            timestamp: new Date().toISOString()
        };
    }
}

// 3. Middleware de validação avançado
class Validator {
    static validateUsuario(req, res, next) {
        const { nome, email } = req.body;
        const errors = [];
        
        // Validar nome
        if (!nome || typeof nome !== 'string') {
            errors.push('Nome é obrigatório e deve ser uma string');
        } else if (nome.trim().length < 2) {
            errors.push('Nome deve ter pelo menos 2 caracteres');
        } else if (nome.length > 100) {
            errors.push('Nome deve ter no máximo 100 caracteres');
        }
        
        // Validar email
        if (!email || typeof email !== 'string') {
            errors.push('Email é obrigatório e deve ser uma string');
        } else {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (!emailRegex.test(email)) {
                errors.push('Email deve ter um formato válido');
            }
        }
        
        if (errors.length > 0) {
            return res.status(400).json(
                ApiResponse.error('Dados inválidos', 400, errors)
            );
        }
        
        // Normalizar dados
        req.body.nome = nome.trim();
        req.body.email = email.toLowerCase().trim();
        
        next();
    }
    
    static validateId(req, res, next) {
        const id = parseInt(req.params.id);
        
        if (isNaN(id) || id <= 0) {
            return res.status(400).json(
                ApiResponse.error('ID deve ser um número positivo', 400)
            );
        }
        
        req.params.id = id;
        next();
    }
}

// 4. Controller com tratamento de erros
class UsuarioController {
    static async listar(req, res) {
        try {
            console.log('📋 Listando usuários com filtros:', req.query);
            
            const { page = 1, limit = 10, ativo, busca } = req.query;
            
            // Validar paginação
            const pageNum = parseInt(page);
            const limitNum = parseInt(limit);
            
            if (pageNum < 1 || limitNum < 1 || limitNum > 100) {
                return res.status(400).json(
                    ApiResponse.error('Parâmetros de paginação inválidos', 400)
                );
            }
            
            // Aplicar filtros
            const filtros = {};
            if (ativo !== undefined) {
                filtros.ativo = ativo === 'true';
            }
            if (busca) {
                filtros.busca = busca;
            }
            
            const todosUsuarios = db.findAll(filtros);
            const total = todosUsuarios.length;
            
            // Aplicar paginação
            const startIndex = (pageNum - 1) * limitNum;
            const endIndex = startIndex + limitNum;
            const usuarios = todosUsuarios.slice(startIndex, endIndex);
            
            res.json(ApiResponse.paginated(usuarios, pageNum, limitNum, total));
            
        } catch (error) {
            console.error('❌ Erro ao listar usuários:', error);
            res.status(500).json(
                ApiResponse.error('Erro interno do servidor', 500)
            );
        }
    }
    
    static async buscarPorId(req, res) {
        try {
            console.log(`🔍 Buscando usuário ID ${req.params.id}`);
            
            const usuario = db.findById(req.params.id);
            
            if (!usuario) {
                return res.status(404).json(
                    ApiResponse.error('Usuário não encontrado', 404)
                );
            }
            
            res.json(ApiResponse.success(usuario, 'Usuário encontrado'));
            
        } catch (error) {
            console.error('❌ Erro ao buscar usuário:', error);
            res.status(500).json(
                ApiResponse.error('Erro interno do servidor', 500)
            );
        }
    }
    
    static async criar(req, res) {
        try {
            console.log('➕ Criando novo usuário:', req.body);
            
            const { nome, email } = req.body;
            
            // Verificar se email já existe
            if (db.emailExists(email)) {
                return res.status(409).json(
                    ApiResponse.error('Email já está em uso', 409)
                );
            }
            
            const novoUsuario = db.create({ nome, email });
            
            res.status(201).json(
                ApiResponse.success(novoUsuario, 'Usuário criado com sucesso', 201)
            );
            
        } catch (error) {
            console.error('❌ Erro ao criar usuário:', error);
            res.status(500).json(
                ApiResponse.error('Erro interno do servidor', 500)
            );
        }
    }
    
    static async atualizar(req, res) {
        try {
            console.log(`✏️ Atualizando usuário ID ${req.params.id}:`, req.body);
            
            const { nome, email, ativo } = req.body;
            
            // Verificar se usuário existe
            const usuarioExistente = db.findById(req.params.id);
            if (!usuarioExistente) {
                return res.status(404).json(
                    ApiResponse.error('Usuário não encontrado', 404)
                );
            }
            
            // Verificar se novo email já existe
            if (email && db.emailExists(email, req.params.id)) {
                return res.status(409).json(
                    ApiResponse.error('Email já está em uso', 409)
                );
            }
            
            // Preparar dados para atualização
            const dadosAtualizacao = {};
            if (nome !== undefined) dadosAtualizacao.nome = nome;
            if (email !== undefined) dadosAtualizacao.email = email;
            if (ativo !== undefined) dadosAtualizacao.ativo = ativo === true || ativo === 'true';
            
            const usuarioAtualizado = db.update(req.params.id, dadosAtualizacao);
            
            res.json(
                ApiResponse.success(usuarioAtualizado, 'Usuário atualizado com sucesso')
            );
            
        } catch (error) {
            console.error('❌ Erro ao atualizar usuário:', error);
            res.status(500).json(
                ApiResponse.error('Erro interno do servidor', 500)
            );
        }
    }
    
    static async deletar(req, res) {
        try {
            console.log(`🗑️ Deletando usuário ID ${req.params.id}`);
            
            const usuarioRemovido = db.delete(req.params.id);
            
            if (!usuarioRemovido) {
                return res.status(404).json(
                    ApiResponse.error('Usuário não encontrado', 404)
                );
            }
            
            res.json(
                ApiResponse.success(usuarioRemovido, 'Usuário deletado com sucesso')
            );
            
        } catch (error) {
            console.error('❌ Erro ao deletar usuário:', error);
            res.status(500).json(
                ApiResponse.error('Erro interno do servidor', 500)
            );
        }
    }
}

// 5. Simulador de API REST
class RestApiSimulator {
    constructor() {
        this.routes = [];
    }
    
    get(path, ...handlers) {
        this.routes.push({ method: 'GET', path, handlers });
    }
    
    post(path, ...handlers) {
        this.routes.push({ method: 'POST', path, handlers });
    }
    
    put(path, ...handlers) {
        this.routes.push({ method: 'PUT', path, handlers });
    }
    
    delete(path, ...handlers) {
        this.routes.push({ method: 'DELETE', path, handlers });
    }
    
    async simulateRequest(method, url, body = null) {
        console.log(`\n🌐 ${method} ${url}`);
        if (body) console.log('📦 Body:', JSON.stringify(body, null, 2));
        
        const route = this.routes.find(r => {
            if (r.method !== method) return false;
            
            const routeRegex = r.path
                .replace(/:[^/]+/g, '([^/]+)')
                .replace(/\//g, '\\/');
            
            return new RegExp(`^${routeRegex}$`).test(url.split('?')[0]);
        });
        
        if (!route) {
            console.log('❌ Rota não encontrada - 404');
            return;
        }
        
        const req = {
            method,
            url,
            body,
            params: this.extractParams(route.path, url),
            query: this.extractQuery(url)
        };
        
        const res = {
            statusCode: 200,
            status(code) {
                this.statusCode = code;
                return this;
            },
            json(data) {
                console.log(`📤 Status ${this.statusCode}:`, JSON.stringify(data, null, 2));
            }
        };
        
        // Executar handlers
        let index = 0;
        
        const next = (error) => {
            if (error) {
                console.log('❌ Erro:', error.message);
                return res.status(500).json(
                    ApiResponse.error(error.message, 500)
                );
            }
            
            if (index < route.handlers.length) {
                const handler = route.handlers[index++];
                handler(req, res, next);
            }
        };
        
        next();
    }
    
    extractParams(routePath, url) {
        const routeParts = routePath.split('/');
        const urlParts = url.split('?')[0].split('/');
        const params = {};
        
        routeParts.forEach((part, index) => {
            if (part.startsWith(':')) {
                const paramName = part.slice(1);
                params[paramName] = urlParts[index];
            }
        });
        
        return params;
    }
    
    extractQuery(url) {
        const queryString = url.split('?')[1];
        if (!queryString) return {};
        
        const query = {};
        queryString.split('&').forEach(param => {
            const [key, value] = param.split('=');
            query[key] = decodeURIComponent(value || '');
        });
        
        return query;
    }
}

// 6. Configurando API
console.log('\n🚀 Configurando API REST...');
const api = new RestApiSimulator();

// Rotas da API
api.get('/usuarios', UsuarioController.listar);
api.get('/usuarios/:id', Validator.validateId, UsuarioController.buscarPorId);
api.post('/usuarios', Validator.validateUsuario, UsuarioController.criar);
api.put('/usuarios/:id', Validator.validateId, Validator.validateUsuario, UsuarioController.atualizar);
api.delete('/usuarios/:id', Validator.validateId, UsuarioController.deletar);

console.log('\n=== TESTANDO API REST ===');

// Testando a API
api.simulateRequest('GET', '/usuarios');
api.simulateRequest('GET', '/usuarios?page=1&limit=2&ativo=true');
api.simulateRequest('GET', '/usuarios/1');
api.simulateRequest('GET', '/usuarios/999');
api.simulateRequest('POST', '/usuarios', {
    nome: 'Pedro Oliveira',
    email: 'pedro@email.com'
});
api.simulateRequest('POST', '/usuarios', {
    nome: 'A',
    email: 'email-invalido'
});
api.simulateRequest('PUT', '/usuarios/1', {
    nome: 'Ana Silva Santos',
    ativo: false
});
api.simulateRequest('DELETE', '/usuarios/2');

📊 Códigos de Status HTTP:

200 OK

Sucesso geral

201 Created

Recurso criado

400 Bad Request

Dados inválidos

404 Not Found

Recurso não encontrado

500 Server Error

Erro interno


                    

🎯 Exercício Prático 3: API de E-commerce

Crie uma API REST completa para um sistema de e-commerce com produtos e categorias!

📋 Requisitos:

  • CRUD completo para produtos e categorias
  • Validação de dados e tratamento de erros
  • Respostas padronizadas com códigos HTTP corretos
  • Paginação e filtros avançados

💻 Seu Código:

📊 Resultado:

Execute seu código para ver o resultado...
📊 Score: 0/100
✅ Testes: 0/4
🎯 Status: Pendente