Projeto Final: Revisão Completa e Aplicação de Produção

[134] Projeto Final: Revisão Completa e Aplicação de Produção

O fim da série reúne tudo numa aplicação de produção: React com rotas e estado, API em camadas, banco com índices, fila para o trabalho pesado, tempo real por WebSocket, testes, pipeline e monitoramento. O artigo revisa o caminho percorrido e mostra como as peças se encaixam quando precisam funcionar juntas.
Javascript

25 min de leitura

Conclusão da Série — Módulo 9

Introdução

Chegamos ao final. Cinquenta e dois artigos, nove módulos, uma jornada de um ano de aprendizado estruturado. Este artigo faz duas coisas ao mesmo tempo: revisita o caminho percorrido e entrega o projeto final — uma aplicação de produção que integra cada conceito da série em um sistema coeso e profissional.

Antes de começar o projeto, vale parar um momento para ver a distância percorrida. no artigo Dominando o JavaScript você aprendeu o que é uma variável. no artigo Filas de Tarefas e Processamento Assíncrono você está construindo sistemas distribuídos com arquitetura limpa, filas de tarefas, websockets, CI/CD automatizado e segurança em camadas. Essa é a jornada de um desenvolvedor júnior para sênior.

Mapa completo da série

MÓDULO 1 — Fundamentos do JavaScript (aulas 01 a 10)
  01. O que é JavaScript e por que aprender?
  02. Condicionais: if, else e switch
  03. Laços de Repetição: for, while e do...while
  04. Funções: declaração, expressão e arrow functions
  05. Arrays: criando e manipulando listas
  06. Objetos: estruturando dados do mundo real
  07. Desestruturação, Spread e Rest Operator
  08. Escopo, Hoisting e Closures
  09. Tratamento de Erros com try, catch e finally
  10. Mini Projeto: Calculadora no Console

MÓDULO 2 — JavaScript no Navegador — o DOM (aulas 11 a 17)
  11. O que é o DOM e como o JavaScript interage com o HTML
  12. Eventos: click, input, submit e muito mais
  13. Criando e Removendo Elementos Dinamicamente
  14. Formulários: validação e coleta de dados
  15. LocalStorage e SessionStorage
  16. Temporizadores: setTimeout e setInterval
  17. Mini Projeto: Quiz Interativo

MÓDULO 3 — Assíncrono, APIs e formatos de dados (aulas 18 a 29)
  18. O que é Programação Assíncrona? O Event Loop Explicado
  19. Callbacks: o começo de tudo
  20. Promises: resolvendo o Callback Hell
  21. Async/Await: escrevendo código assíncrono de forma limpa
  22. Fetch API: consumindo dados da internet
  23. A evolução das requisições: de XMLHttpRequest ao Fetch
  24. Trabalhando com JSON
  25. Trabalhando com arquivos CSV
  26. Trabalhando com datas e horas em JavaScript
  27. Tratamento de erros em requisições HTTP
  28. APIs públicas: exemplos práticos
  29. Mini Projeto: App de Clima Completo

MÓDULO 4 — Node.js e Back-end (aulas 30 a 35)
  30. Introdução ao Node.js: JavaScript fora do navegador
  31. NPM: gerenciando pacotes e dependências
  32. Criando um servidor HTTP com Node.js puro
  33. Express.js: o framework web do Node
  34. MongoDB e Mongoose: banco de dados com Node
  35. Projeto: API REST com autenticação JWT

MÓDULO 5 — Qualidade de Código (aulas 36 a 40)
  36. Testes automatizados com Jest
  37. ESLint e Prettier: código limpo e padronizado
  38. Git avançado e fluxo de trabalho em equipe
  39. Introdução ao TypeScript
  40. Revisão + Projeto: API tipada e testada

MÓDULO 6 — React (aulas 41 a 45)
  41. Introdução ao React
  42. React Hooks em profundidade
  43. Estado global com Zustand e React Query
  44. React Router: navegação em SPAs
  45. Revisão + Projeto Final: SPA Completa

MÓDULO 7 — Deploy, Segurança e Performance (aulas 46 a 49)
  46. Deploy: do código ao ar
  47. Segurança em aplicações web
  48. Performance em aplicações web
  49. Revisão + Projeto Final: Produção Real

MÓDULO 8 — Arquitetura de Software (aulas 50 a 52)
  50. Padrões de Projeto em JavaScript
  51. Arquitetura de Software: SOLID, Clean Architecture e DDD
  52. Revisão do Módulo 8 + Projeto: Refatoração com Clean Architecture

MÓDULO 9 — Tópicos Avançados (aulas 53 a 55)
  53. WebSockets e Comunicação em Tempo Real
  54. Filas de Tarefas e Processamento Assíncrono
  55. Projeto Final: Revisão Completa e Aplicação de Produção  ← você está aqui

O projeto final — Plataforma de Gestão Completa

O projeto final integra tudo que foi aprendido na série. É uma plataforma de gestão de projetos e tarefas com as seguintes características:

Back-end: Node.js com Express, Clean Architecture em camadas, MongoDB Atlas, autenticação JWT, WebSockets com Socket.IO, filas de tarefas com BullMQ, segurança completa com Helmet, rate limiting e Zod.

Front-end: React com Vite, React Router, Zustand, React Query, notificações em tempo real, upload com acompanhamento de progresso, lazy loading e otimizações de performance.

Infraestrutura: Deploy automatizado com GitHub Actions, Railway (API), Vercel (web), MongoDB Atlas, Redis para filas e Socket.IO.

Arquitetura final do sistema

Antes de qualquer código, vamos estabelecer a visão completa do sistema e como todas as partes se comunicam.

┌─────────────────────────────────────────────────────────────────┐
│                        USUÁRIO (Browser)                        │
│              React SPA — Vercel CDN global                      │
└──────────────────────┬──────────────────────────────────────────┘
                       │ HTTPS
         ┌─────────────┼─────────────────────┐
         │ REST API    │ WebSocket           │ SSE (fallback)
         ▼             ▼                     ▼
┌─────────────────────────────────────────────────────────────────┐
│                     API Node.js — Railway                       │
│  Express + Socket.IO + BullMQ                                   │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────────────┐  │
│  │  Interfaces │  │  Application │  │  Domain               │  │
│  │  (HTTP/WS)  │→ │  (Use Cases) │→ │  (Entities/Events)    │  │
│  └─────────────┘  └──────────────┘  └───────────────────────┘  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Infrastructure (Mongo, Redis, Email, Storage)          │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────┬──────────────┬──────────────────────────────────────────-┘
       │              │
       ▼              ▼
┌──────────┐   ┌──────────────┐
│ MongoDB  │   │    Redis     │
│  Atlas   │   │  (BullMQ +   │
│          │   │  Socket.IO)  │
└──────────┘   └──────────────┘

Estrutura final do projeto

plataforma-gestao/
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
│
├── apps/
│   ├── api/
│   │   ├── src/
│   │   │   ├── domain/
│   │   │   │   ├── entities/
│   │   │   │   │   ├── Usuario.js
│   │   │   │   │   ├── Projeto.js
│   │   │   │   │   └── Tarefa.js
│   │   │   │   ├── repositories/    ← interfaces (contratos)
│   │   │   │   ├── events/
│   │   │   │   │   └── EventosDominio.js
│   │   │   │   └── valueObjects/
│   │   │   │       ├── Email.js
│   │   │   │       └── Dinheiro.js
│   │   │   │
│   │   │   ├── application/
│   │   │   │   └── useCases/
│   │   │   │       ├── auth/
│   │   │   │       ├── projetos/
│   │   │   │       └── tarefas/
│   │   │   │
│   │   │   ├── infrastructure/
│   │   │   │   ├── database/
│   │   │   │   │   ├── models/
│   │   │   │   │   └── repositories/
│   │   │   │   ├── queue/
│   │   │   │   │   ├── filas.js
│   │   │   │   │   ├── produtores.js
│   │   │   │   │   └── agendamentos.js
│   │   │   │   ├── cache/
│   │   │   │   ├── email/
│   │   │   │   └── events/
│   │   │   │
│   │   │   ├── interfaces/
│   │   │   │   └── http/
│   │   │   │       ├── controllers/
│   │   │   │       ├── middlewares/
│   │   │   │       ├── validators/
│   │   │   │       └── routes/
│   │   │   │
│   │   │   ├── workers/
│   │   │   │   ├── emailWorker.js
│   │   │   │   ├── notificacaoWorker.js
│   │   │   │   └── importacaoWorker.js
│   │   │   │
│   │   │   ├── socketHandlers/
│   │   │   │   ├── tarefas.js
│   │   │   │   └── notificacoes.js
│   │   │   │
│   │   │   ├── container.js
│   │   │   └── index.js
│   │   │
│   │   ├── tests/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   └── integration/
│   │   │
│   │   ├── .env.example
│   │   └── package.json
│   │
│   └── web/
│       ├── src/
│       │   ├── components/
│       │   │   ├── Layout.jsx
│       │   │   ├── RotaProtegida.jsx
│       │   │   ├── SinoBadge.jsx
│       │   │   └── ImportacaoProdutos.jsx
│       │   ├── hooks/
│       │   │   ├── useSocket.js
│       │   │   ├── useNotificacoes.js
│       │   │   ├── useProjetoTempoReal.js
│       │   │   ├── useDebounce.js
│       │   │   └── useProdutos.js
│       │   ├── pages/
│       │   │   ├── Login.jsx
│       │   │   ├── Dashboard.jsx
│       │   │   ├── Projetos.jsx
│       │   │   ├── DetalheProjeto.jsx
│       │   │   ├── Tarefas.jsx
│       │   │   └── Perfil.jsx
│       │   ├── services/
│       │   ├── stores/
│       │   │   └── authStore.js
│       │   └── utils/
│       │       └── webVitals.js
│       ├── vercel.json
│       └── vite.config.js
│
└── README.md

index.js final — tudo junto

O ponto de entrada da API final integra todos os sistemas aprendidos ao longo da série. A ordem de configuração reflete as dependências entre os sistemas.

// apps/api/src/index.js
require('dotenv').config();

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');

const config = require('./config');
const { conectar } = require('./infrastructure/database/conexao');
const { criarContainer } = require('./container');
const { configurarSocket } = require('./socketHandlers');
const { configurarAgendamentos } = require('./infrastructure/queue/agendamentos');
const { configurarBullBoard } = require('./infrastructure/queue/bullBoard');
const { monitorarPerformance } = require('./interfaces/http/middlewares/performance');

async function iniciar() {
  // ── 1. Conecta ao banco antes de qualquer coisa ──
  await conectar();

  const app = express();
  const servidor = http.createServer(app);

  // ── 2. Cria o Socket.IO vinculado ao servidor HTTP ─
  const io = new Server(servidor, {
    cors: {
      origin: config.eProd
        ? config.frontendUrl
        : ['http://localhost:5173', 'http://localhost:4173'],
      credentials: true,
    },
    pingTimeout: 60000,
    pingInterval: 25000,
  });

  // ── 3. Cria o container com todas as dependências ──
  // Passa o 'io' para que workers e eventos possam notificar via WebSocket
  const { tarefaController, projetoController, authController } =
    criarContainer(io);

  // ── 4. Middlewares de segurança ──────────────────
  app.use(helmet());
  app.use(cors({
    origin: (origin, cb) => {
      const permitidas = [
        config.frontendUrl,
        ...(config.eDev ? ['http://localhost:5173'] : []),
      ].filter(Boolean);
      if (!origin || permitidas.includes(origin)) return cb(null, true);
      cb(new Error(`Origem bloqueada: ${origin}`));
    },
    credentials: true,
  }));

  // ── 5. Compressão e parse ────────────────────────
  app.use(compression({ threshold: 1024 }));
  app.use(express.json({ limit: '10mb' }));

  // ── 6. Rate limiting ─────────────────────────────
  app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100, standardHeaders: true }));

  // ── 7. Monitoramento de performance ─────────────
  app.use(monitorarPerformance);

  // ── 8. Health check ──────────────────────────────
  app.get('/health', (req, res) => {
    res.json({
      status: 'ok',
      versao: process.env.npm_package_version,
      uptime: `${Math.floor(process.uptime())}s`,
      ambiente: config.ambiente,
      memoria: {
        usada: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`,
        total: `${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)}MB`,
      },
    });
  });

  // ── 9. Rotas da API ──────────────────────────────
  const limiteAuth = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, skipSuccessfulRequests: true });
  app.use('/auth',       limiteAuth, require('./interfaces/http/routes/auth')(authController));
  app.use('/projetos',   require('./interfaces/http/routes/projetos')(projetoController));
  app.use('/tarefas',    require('./interfaces/http/routes/tarefas')(tarefaController));
  app.use('/importacao', require('./interfaces/http/routes/importacao'));

  // ── 10. Painel de filas (só em não-produção ou com auth) ─
  configurarBullBoard(app);

  // ── 11. Handlers de erro ─────────────────────────
  const { naoEncontrado, tratadorDeErros } = require('./interfaces/http/middlewares/erros');
  app.use(naoEncontrado);
  app.use(tratadorDeErros);

  // ── 12. Configura WebSockets ─────────────────────
  configurarSocket(io);

  // ── 13. Configura jobs recorrentes ───────────────
  await configurarAgendamentos();

  // ── 14. Inicia o servidor ────────────────────────
  const srv = servidor.listen(config.porta, () => {
    console.info(`[Server] ✅ Pronto na porta ${config.porta} (${config.ambiente})`);
    console.info(`[Server] 🌐 HTTP + WebSocket + Filas`);
  });

  // Encerramento gracioso
  process.on('SIGTERM', () => {
    console.info('[Server] Encerrando graciosamente...');
    srv.close(() => process.exit(0));
  });
}

iniciar().catch((erro) => {
  console.error('[Server] ❌ Falha ao iniciar:', erro.message);
  process.exit(1);
});

App.jsx final — front-end completo

// apps/web/src/App.jsx
import { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { RotaProtegida, RotaPublica } from './components/RotaProtegida';
import Layout from './components/Layout';

// Code splitting — cada página é um chunk separado
const Login         = lazy(() => import('./pages/Login'));
const Cadastro      = lazy(() => import('./pages/Cadastro'));
const Dashboard     = lazy(() => import('./pages/Dashboard'));
const Projetos      = lazy(() => import('./pages/Projetos'));
const DetalheProjeto = lazy(() => import('./pages/DetalheProjeto'));
const Tarefas       = lazy(() => import('./pages/Tarefas'));
const Importacao    = lazy(() => import('./pages/Importacao'));
const Perfil        = lazy(() => import('./pages/Perfil'));
const NaoEncontrado = lazy(() => import('./pages/NaoEncontrado'));

function Carregando() {
  return (
    <div className="pagina-carregando" aria-label="Carregando...">
      <div className="spinner" />
    </div>
  );
}

export default function App() {
  return (
    <Suspense fallback={<Carregando />}>
      <Routes>
        <Route path="/" element={<Navigate to="/dashboard" replace />} />

        {/* Rotas públicas — redireciona para dashboard se logado */}
        <Route element={<RotaPublica />}>
          <Route path="/login"    element={<Login />} />
          <Route path="/cadastro" element={<Cadastro />} />
        </Route>

        {/* Rotas protegidas com Layout */}
        <Route element={<RotaProtegida />}>
          <Route element={<Layout />}>
            <Route path="/dashboard"              element={<Dashboard />} />
            <Route path="/projetos"               element={<Projetos />} />
            <Route path="/projetos/:id"           element={<DetalheProjeto />} />
            <Route path="/tarefas"                element={<Tarefas />} />
            <Route path="/importacao"             element={<Importacao />} />
            <Route path="/perfil"                 element={<Perfil />} />
          </Route>
        </Route>

        <Route path="*" element={<NaoEncontrado />} />
      </Routes>
    </Suspense>
  );
}

Dashboard — síntese visual de tudo

O Dashboard é onde todas as partes se encontram — dados via React Query, estado via Zustand, atualizações em tempo real via WebSocket, e Web Vitals sendo monitorados em background.

// apps/web/src/pages/Dashboard.jsx
import { useQuery } from '@tanstack/react-query';
import { memo, useMemo } from 'react';
import useAuthStore from '../stores/authStore';
import { useNotificacoes } from '../hooks/useNotificacoes';
import { api } from '../services/api';

// Componente de card de estatística — memoizado para evitar re-renders
const CardEstat = memo(function CardEstat({ icone, valor, rotulo, cor }) {
  return (
    <div className={`card-estat card-estat--${cor}`}>
      <span className="card-estat__icone">{icone}</span>
      <div>
        <p className="card-estat__valor">{valor ?? '...'}</p>
        <p className="card-estat__rotulo">{rotulo}</p>
      </div>
    </div>
  );
});

export default function Dashboard() {
  const usuario = useAuthStore((s) => s.usuario);
  const { naoLidas } = useNotificacoes();

  // Busca estatísticas em paralelo — sem bloquear uma pela outra
  const { data: statsTarefas } = useQuery({
    queryKey: ['stats', 'tarefas', usuario?._id],
    queryFn: () => api('/tarefas/estatisticas'),
    staleTime: 1000 * 60 * 2, // revalida a cada 2 minutos
  });

  const { data: statsProjetos } = useQuery({
    queryKey: ['stats', 'projetos'],
    queryFn: () => api('/projetos/estatisticas'),
    staleTime: 1000 * 60 * 5,
  });

  // useMemo para derivar estatísticas sem recalcular a cada render
  const resumo = useMemo(() => {
    const porStatus = statsTarefas?.categorias || [];
    return {
      pendentes: porStatus.find((s) => s.status === 'pendente')?.total ?? 0,
      emProgresso: porStatus.find((s) => s.status === 'em_progresso')?.total ?? 0,
      concluidas: porStatus.find((s) => s.status === 'concluida')?.total ?? 0,
      projetos: statsProjetos?.total ?? 0,
    };
  }, [statsTarefas, statsProjetos]);

  const saudacao = useMemo(() => {
    const hora = new Date().getHours();
    if (hora < 12) return 'Bom dia';
    if (hora < 18) return 'Boa tarde';
    return 'Boa noite';
  }, []);

  return (
    <div className="pagina-dashboard">
      <header className="dashboard-header">
        <div>
          <h1>{saudacao}, {usuario?.nome?.split(' ')[0]}! 👋</h1>
          <p className="subtitulo">
            {new Date().toLocaleDateString('pt-BR', {
              weekday: 'long', day: 'numeric', month: 'long', year: 'numeric',
            })}
          </p>
        </div>
        {naoLidas > 0 && (
          <div className="alerta-notificacoes">
            🔔 Você tem <strong>{naoLidas}</strong> notificação{naoLidas > 1 ? 'ões' : ''} não lida{naoLidas > 1 ? 's' : ''}.
          </div>
        )}
      </header>

      {/* Grade de estatísticas */}
      <section className="grid-estats">
        <CardEstat icone="📋" valor={resumo.pendentes}   rotulo="Tarefas pendentes"    cor="azul" />
        <CardEstat icone="⚡" valor={resumo.emProgresso} rotulo="Em progresso"         cor="amarelo" />
        <CardEstat icone="✅" valor={resumo.concluidas}  rotulo="Concluídas este mês"  cor="verde" />
        <CardEstat icone="📁" valor={resumo.projetos}    rotulo="Projetos ativos"      cor="roxo" />
      </section>

      {/* Tarefas atrasadas — alerta visual */}
      <TarefasAtrasadas />

      {/* Atividade recente */}
      <AtividadeRecente />
    </div>
  );
}

Testes — cobertura da arquitetura final

Com Clean Architecture, os testes se organizam naturalmente em três camadas. Cada camada testa o que lhe cabe, sem interferência das outras.

// Pirâmide de testes da aplicação final

// ── BASE: Testes de domínio (rápidos, sem I/O) ──────
// Entidades, Value Objects, regras de negócio puras
// tests/domain/Tarefa.test.js        — ~50 testes
// tests/domain/Projeto.test.js       — ~30 testes
// tests/domain/valueObjects/*.test.js — ~40 testes
// Tempo de execução: < 500ms

// ── MEIO: Testes de casos de uso (mocks, sem I/O) ───
// Casos de uso com repositórios e serviços em memória
// tests/application/CriarTarefa.test.js    — ~20 testes
// tests/application/ConcluirTarefa.test.js — ~15 testes
// tests/application/GerenciarProjeto.test.js — ~25 testes
// Tempo de execução: < 1s

// ── TOPO: Testes de integração (banco real) ─────────
// Endpoints completos com MongoDB de teste
// tests/integration/auth.test.js     — ~15 testes
// tests/integration/tarefas.test.js  — ~20 testes
// tests/integration/projetos.test.js — ~20 testes
// Tempo de execução: ~10-20s

// O Jest config para cobrir tudo:
// jest.config.json
{
  "preset": "ts-jest",            // ou @jest/globals para JS puro
  "testEnvironment": "node",
  "collectCoverageFrom": [
    "src/domain/**/*.js",
    "src/application/**/*.js",
    "src/interfaces/**/*.js"
  ],
  "coverageThreshold": {
    "global": {
      "branches": 80,
      "functions": 85,
      "lines": 85,
      "statements": 85
    }
  },
  "testPathPattern": {
    "domain": "tests/domain",
    "application": "tests/application",
    "integration": "tests/integration"
  }
}

Checklist final de produção

Este é o checklist definitivo — uma síntese de todos os checklists dos módulos anteriores.

CÓDIGO E QUALIDADE
──────────────────────────────────────────────────────────────────
[ ] npm run lint sem erros
[ ] npm run format:check sem diferenças
[ ] npm test com cobertura ≥ 80% nas camadas de domínio e aplicação
[ ] npm audit sem vulnerabilidades high ou critical
[ ] Todos os commits seguem o padrão Conventional Commits
[ ] .env.example atualizado com todas as variáveis

SEGURANÇA
──────────────────────────────────────────────────────────────────
[ ] Helmet configurado com CSP
[ ] CORS restrito às origens de produção
[ ] Rate limiting: 100 req/15min geral, 5 req/15min para auth
[ ] Senhas com bcrypt (fator ≥ 12)
[ ] JWT com expiração curta (15min) e refresh token (7d)
[ ] Toda entrada validada com Zod
[ ] Usuários só acessam seus próprios recursos
[ ] .env nunca commitado (verificar: git ls-files | grep .env)
[ ] Segredos gerados com crypto.randomBytes(64)

PERFORMANCE
──────────────────────────────────────────────────────────────────
[ ] Lighthouse score ≥ 90 em produção
[ ] LCP < 2.5s, CLS < 0.1, INP < 200ms
[ ] Lazy loading em todas as rotas React
[ ] Bundle analisado com rollup-plugin-visualizer
[ ] Índices MongoDB verificados com .explain('executionStats')
[ ] .lean() em todas as queries de leitura
[ ] Compressão gzip/brotli ativa na API
[ ] React.memo em componentes de lista

DEPLOY E INFRAESTRUTURA
──────────────────────────────────────────────────────────────────
[ ] GitHub Actions: CI roda em PRs, deploy em push para main
[ ] Health check /health respondendo 200
[ ] Variáveis de ambiente configuradas na plataforma
[ ] MONGODB_URL aponta para Atlas (não localhost)
[ ] REDIS_URL configurada (Railway Redis ou Upstash)
[ ] FRONTEND_URL configurada na API
[ ] VITE_API_URL configurada no front-end
[ ] vercel.json com rewrite para SPA

MONITORAMENTO
──────────────────────────────────────────────────────────────────
[ ] Uptime monitor configurado (UptimeRobot ou similar)
[ ] Web Vitals sendo coletados e reportados
[ ] Slow requests logados (> 500ms)
[ ] Workers de fila logando erros
[ ] Bull Board acessível em /admin/filas (com auth)

O que você construiu ao longo da série

Vamos listar explicitamente todas as tecnologias e habilidades acumuladas:

LINGUAGEM E FUNDAMENTOS
  ✅ JavaScript moderno (ES2022+)
  ✅ TypeScript com generics e utility types
  ✅ Orientação a objetos e programação funcional
  ✅ Async/await, Promises, generators

BACK-END
  ✅ Node.js (filesystem, streams, events, process)
  ✅ Express com middlewares, rotas e error handling
  ✅ MongoDB com Mongoose — schemas, índices, aggregation
  ✅ Autenticação JWT com refresh tokens
  ✅ Validação com Zod
  ✅ Upload de arquivos com Multer
  ✅ WebSockets com Socket.IO
  ✅ Filas de tarefas com BullMQ e Redis
  ✅ Emails transacionais com Nodemailer

FRONT-END
  ✅ React com hooks e componentes funcionais
  ✅ Estado global com Zustand (persist middleware)
  ✅ Dados do servidor com React Query (cache, mutations, optimistic updates)
  ✅ Navegação com React Router v6 (lazy loading, rotas protegidas)
  ✅ Formulários controlados e validação
  ✅ WebSockets no React com hooks customizados

QUALIDADE
  ✅ Testes unitários com Jest
  ✅ Testes de integração com Supertest
  ✅ Cobertura de código com relatórios
  ✅ ESLint e Prettier
  ✅ Commits semânticos com Commitizen e Commitlint
  ✅ Husky e lint-staged para pre-commit hooks

ARQUITETURA
  ✅ Padrões de projeto: Singleton, Factory, Builder, Adapter, Decorator, Observer, Strategy, Command
  ✅ Princípios SOLID
  ✅ Clean Architecture em camadas
  ✅ Domain-Driven Design: entidades, value objects, linguagem ubíqua
  ✅ Injeção de dependências manual

DEPLOY E INFRAESTRUTURA
  ✅ Deploy de front-end na Vercel
  ✅ Deploy de back-end no Railway
  ✅ MongoDB Atlas
  ✅ Redis (BullMQ + Socket.IO adapter)
  ✅ CI/CD com GitHub Actions
  ✅ Docker (containers para desenvolvimento)
  ✅ Variáveis de ambiente e gestão de segredos

SEGURANÇA
  ✅ OWASP Top 10 e defesas práticas
  ✅ bcrypt para senhas
  ✅ JWT com algoritmo explícito e expiração
  ✅ Rate limiting por rota
  ✅ Headers de segurança com Helmet
  ✅ CORS configurado corretamente
  ✅ Proteção contra injeção NoSQL
  ✅ npm audit no pipeline de CI

PERFORMANCE
  ✅ Core Web Vitals: LCP, INP, CLS
  ✅ Code splitting e lazy loading
  ✅ Bundle analysis e otimização
  ✅ Virtualização de listas longas
  ✅ Índices MongoDB e .explain()
  ✅ Cache em memória com TTL
  ✅ Compressão gzip/brotli

O próximo passo — para onde ir daqui

Completar esta série não é o fim — é o começo de uma nova fase. Você tem a base. Agora é hora de aprofundar nas áreas que mais te interessam e de construir coisas reais.

Para aprofundar no back-end:

Explore GraphQL com Apollo Server como alternativa ao REST para APIs com dados complexos. Estude microsserviços e comunicação entre serviços com gRPC ou mensageria com RabbitMQ. Aprofunde em bancos relacionais com PostgreSQL e Prisma para os cenários onde SQL é a escolha certa.

Para aprofundar no front-end:

Next.js abre um mundo de possibilidades — SSR, SSG, App Router, Server Components. São os mesmos conceitos React desta série aplicados com renderização no servidor. Aprenda sobre acessibilidade (a11y) — é uma habilidade valorizada e frequentemente negligenciada.

Para aprofundar em infraestrutura:

Aprenda Docker em profundidade e Kubernetes para orquestração. Explore observabilidade com OpenTelemetry, Prometheus e Grafana. Estude estratégias de banco de dados para alta disponibilidade — réplicas, sharding, backup e restore.

Para crescer como desenvolvedor:

Contribua para projetos open source. Construa um projeto pessoal do zero e leve para produção real. Escreva sobre o que aprendeu — artigos, posts, vídeos. Ensinar é a forma mais eficaz de solidificar conhecimento.

Mensagem final

Uma série de 52 artigos é uma construção lenta e acumulativa. Cada artigo parecia um passo pequeno, mas olhando para trás a distância percorrida é enorme. O mesmo acontece com qualquer habilidade técnica — ela não se adquire em um momento de insight, mas em centenas de horas de prática deliberada, de erros cometidos e entendidos, de problemas enfrentados e resolvidos.

O desenvolvedor que você é hoje não é o mesmo de quando começou. E o desenvolvedor que você será em mais um ano, se continuar com a mesma dedicação, será irreconhecível para quem você é hoje.

Continue construindo. Continue errando. Continue aprendendo.

Esta série foi escrita com o objetivo de criar desenvolvedores completos — pessoas que entendem não apenas como usar ferramentas, mas por que elas existem, quais problemas resolvem, e quando escolher uma em vez de outra. Espero ter sido útil nessa jornada.

Referências Completas da Série

Fundamentos JavaScript:

Node.js e Back-end:

React e Front-end:

TypeScript:

Arquitetura:

  • Clean Architecture — Robert C. Martin (Pearson)
  • Domain-Driven Design — Eric Evans (Addison-Wesley)
  • Design Patterns — Gang of Four (Addison-Wesley)
  • Refactoring Guru: https://refactoring.guru/pt-br

Deploy e DevOps:

Segurança:

Performance:

Roadmaps:

52 artigos | 9 módulos | Uma jornada completa

Do console.log('Hello World') à arquitetura de sistemas em produção.

SÉRIE CONCLUÍDA

Exercícios

Exercício 1

Na aplicação final, um mesmo dado aparece em quatro lugares. Qual é a fonte da verdade de cada um, e o que acontece quando eles discordam?

// A — o documento no MongoDB
// B — o cache em Redis, com TTL de 5 minutos
// C — o cache do React Query no navegador
// D — o estado local de um formulário sendo editado
Ver resposta

✓ Resposta: A fonte da verdade é A, e todo o resto é cópia com prazo de validade. B é cópia do servidor e pode estar até cinco minutos atrasada; C é cópia no navegador, cuja validade é o staleTime configurado; D é a única que ainda não existe no banco — é intenção, não fato. Quando discordam, a regra é hierárquica: quem está mais perto do banco vence, exceto pelo estado de edição, que é o único que pode legitimamente divergir de todos, porque representa algo que o usuário ainda está compondo. Os problemas nascem quando essa ordem é confundida. Invalidar o cache do navegador e esquecer o do Redis faz a tela buscar de novo e receber o valor velho — sensação de "atualizei e não mudou". Sobrescrever o formulário com dados que chegaram de uma revalidação em segundo plano apaga o que a pessoa estava digitando, e é um dos defeitos mais irritantes que existem. Duas regras práticas fecham o assunto: invalide na ordem inversa da distância, do banco para fora, e nunca deixe uma atualização automática sobrescrever campo em edição — mostre que há versão nova e deixe o usuário decidir.

Exercício 2

Uma requisição de criar produto atravessa a aplicação inteira. Em que ordem as camadas são atravessadas, e o que cada uma pode e não pode fazer?

POST /produtos  →  ?  →  ?  →  ?  →  MongoDB
Ver resposta

✓ Resposta: A rota chega aos middlewares — CORS, limite de tamanho do corpo, autenticação, rate limit —, segue para o controlador, que traduz HTTP em chamada de domínio, vai ao caso de uso, que orquestra a regra de negócio, e só então ao repositório, que fala com o banco. O que cada camada não pode fazer é tão importante quanto o que ela faz. O controlador não decide regra de negócio: ele lê a requisição, chama, e traduz o resultado de volta para status HTTP — se houver if de negócio ali, a regra deixou de ser reaproveitável por um worker ou por um comando de terminal. O caso de uso não conhece req nem res: recebe dados simples e devolve dados simples, e é isso que permite testá-lo sem subir servidor. O repositório não valida regra: ele persiste e busca. E o domínio não importa Mongoose nem Express. O teste rápido para saber se o desenho está de pé é perguntar quanto do sistema precisaria mudar para expor a mesma operação por uma fila em vez de por HTTP — se a resposta for "só escrever um worker que chama o mesmo caso de uso", as camadas estão cumprindo o papel; se for "reescrever a lógica", elas existem apenas no diagrama.

Exercício 3

A aplicação está no ar. Um usuário relata que "o sistema está lento". Qual a ordem de investigação — e por que não se começa otimizando o React?

// peças em produção:
// front (Vercel) → API (Railway) → MongoDB Atlas → Redis → workers
Ver resposta

✓ Resposta: Começa-se medindo, porque "está lento" não é diagnóstico: pode ser o carregamento inicial, uma tela específica, uma ação demorada ou a rede do próprio usuário. A ordem útil vai do mais barato e mais provável para o mais caro: primeiro os dados de campo das Core Web Vitals, que dizem se o problema é de carregamento ou de interação e para quantas pessoas; depois a aba Network, que separa em dois mundos — se o tempo está em esperando resposta, o problema é do servidor e o front não tem culpa; se está em download e execução de JavaScript, é do bundle. Sendo do servidor, o próximo passo é o log de requisições lentas, e dali para o banco, com explain na consulta suspeita — índice ausente é, de longe, a causa mais comum de lentidão que aparece de repente, porque ela cresce com o volume sem que ninguém mude uma linha. Otimizar o React vem por último porque é o lugar onde o ganho costuma ser menor e o esforço maior, e porque quase sempre o gargalo está antes: numa consulta sem índice, num N+1, num payload grande demais, numa chamada externa sem timeout. A regra que resume a experiência: meça, encontre o maior gargalo, conserte só ele, meça de novo — otimizar dois lugares ao mesmo tempo impede saber qual dos dois resolveu.

Exercício 4

Você vai colocar esta aplicação no ar para clientes de verdade pela primeira vez. Quais destes itens são inegociáveis antes do primeiro usuário entrar?

// A — backup automático do banco, com restauração testada
// B — cobertura de testes acima de 80%
// C — HTTPS e variáveis de ambiente fora do repositório
// D — monitoramento de erro em produção
// E — CDN configurada para os assets
// F — um jeito de saber que a aplicação caiu
Ver resposta

✓ Resposta: Inegociáveis são A, C, D e F. O backup existe porque é o único item da lista cuja ausência produz dano irreversível — e note a exigência: backup que nunca foi restaurado não é backup, é esperança, e descobrir que o dump está corrompido no dia do incidente é um clássico. HTTPS e segredos fora do repositório são pré-requisito para existir em público, não otimização. Monitoramento de erro e alerta de indisponibilidade formam o par que decide se você fica sabendo pelo painel ou pelo cliente — e essa diferença define a percepção de confiabilidade mais do que o tempo de queda em si. Já B e E são importantes e podem esperar: cobertura é um número, não uma garantia, e 80% mal distribuídos valem menos que 40% concentrados na regra de negócio crítica; CDN é desempenho, e desempenho ruim incomoda, enquanto dado perdido não volta. O critério que organiza qualquer lista dessas é reversibilidade: o que é irreversível — dado perdido, segredo vazado, cobrança errada — vem primeiro; o que é incômodo, mas corrigível na semana seguinte, vem depois. E um item que não estava na lista e costuma ser esquecido junto do backup: saber como voltar atrás de um deploy ruim, de preferência com um comando e sem depender de quem escreveu o código.

Exercício 5

Olhando a série inteira, qual ideia aparece em Promises, em transações do Mongo, no update otimista do React Query e nas filas de tarefas?

// Promise      → pendente → resolvida | rejeitada
// Transação    → tudo é gravado | nada é gravado
// Update otimista → aplica na tela → confirma | desfaz
// Fila         → job enfileirado → concluído | falhou, tenta de novo
Ver resposta

✓ Resposta: A ideia é que toda operação que atravessa uma fronteira pode falhar, e o que define a qualidade do sistema é o que acontece quando ela falha. Os quatro mecanismos são respostas à mesma pergunta, em camadas diferentes: a Promise dá um lugar explícito para o caminho do erro, em vez de deixá-lo implícito; a transação garante que uma operação composta não pare pela metade; o update otimista aposta no sucesso para não fazer o usuário esperar, mas porque sabe desfazer; e a fila aceita a falha como rotina e a transforma em nova tentativa. Em todos, o padrão é o mesmo — estado intermediário explícito, dois desfechos possíveis, caminho de volta definido antes de precisar dele. É também a diferença mais visível entre código que funciona na demonstração e código que sobrevive em produção: o primeiro trata o caminho feliz e supõe o resto; o segundo assume que a rede cai, que o banco recusa, que o processo morre no pior instante, e decide de antemão o que fazer. Se houver uma única coisa para levar da série inteira, talvez seja esta: programar é, em boa medida, decidir o que acontece quando não dá certo — e essa decisão precisa estar no código, não na esperança.

Comentários

Mais em Javascript

Estado global com Zustand e React Query
Estado global com Zustand e React Query

Tema, carrinho e filtro são seus; a lista de produtos é uma cópia que…

Introdução ao React
Introdução ao React

Manipular o DOM à mão significa lembrar de atualizar tudo que depende de cada…

NPM: gerenciando pacotes e dependências
NPM: gerenciando pacotes e dependências

Todo projeto Node começa com um package.json de quinze linhas e termina com um…