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

Git avançado e fluxo de trabalho em equipe
Git avançado e fluxo de trabalho em equipe

Saber add, commit e push é o mínimo. O que aparece em projeto com mais gente é…

Tratamento de erros em requisições HTTP
Tratamento de erros em requisições HTTP

Código que só funciona quando tudo dá certo não está pronto para produção…

Segurança em aplicações web
Segurança em aplicações web

Segurança não é etapa final, é decisão em cada linha. O artigo percorre as…