Nexus

2025 • WEB APP

O Nexus é uma aplicação "Link-in-Bio" completa, projetada para simular um produto SaaS real. O projeto adota uma arquitetura de Monorepo para compartilhar contratos de dados e lógica entre o frontend (Next.js) e o backend (Express), demonstrando domínio sobre o ciclo de vida completo de desenvolvimento de software, desde a modelagem de dados até a orquestração de containers.


🧩 Desafios Técnicos & Soluções

1. Concorrência em Autenticação (Promise Singleton)

O Problema: Em SPAs complexas, múltiplas requisições simultâneas podem falhar devido a um token expirado. Se cada uma tentar renovar o token independentemente, o mecanismo de Refresh Token Rotation invalidaria os tokens anteriores em cascata, eventualmente deslogando o usuário.

A Solução: Implementei um padrão de Promise Singleton no interceptor do cliente HTTP. A primeira falha 401 instancia uma promessa de refresh e todas as requisições subsequentes se inscrevem nessa mesma promessa pendente (queueing) em vez de disparar novos refreshes.

Resultado:

  • Após a resolução, todas as requisições pausadas são reexecutadas com o novo token.
  • Garante atomicidade na renovação da sessão.

2. Integridade e Segurança de Sessão

O Problema: Prevenir replay attacks e garantir que tokens não pudessem ser forjados ou colidissem, além de permitir a validação de sessão no servidor (Next.js) sem expor tokens sensíveis.

A Solução: Injetei entropia criptográfica (16 bytes hex) via JTI (JWT ID) em cada token gerado e criei um endpoint read-only (BFF Pattern) que permite ao Next.js validar a sessão via cookies httpOnly no SSR.

Resultado:

  • Unicidade absoluta no banco de dados para revogação precisa.
  • Não aciona lógica de rotação de escrita inadvertidamente.
  • Segurança e performance mantidas no SSR.

3. Upload com Deduplicação (Content-Addressable Storage)

O Problema: O upload de imagens binárias diretamente pela API Node.js bloqueia a thread principal, degradando a performance geral. Além disso, alguns usuários enviam a mesma imagem (ex: logo de redes sociais), desperdiçando armazenamento e banda com arquivos duplicados.

A Solução: Implementei uma lógica de hash SHA-256 no cliente (Content-Based Addressing). Antes do upload, o servidor verifica via R2/S3 HeadObject se o arquivo já existe. Se existir, reutiliza-o instantaneamente; se não, gera uma Presigned URL para upload direto para o bucket.

Resultado:

  • Deduplicação automática de arquivos idênticos.
  • Upload direto (Client-to-Storage) sem bloquear o servidor Node.js.

4. Type-Safety End-to-End (Shared Contracts)

O Problema: Manter tipos TypeScript sincronizados manualmente entre Frontend e Backend é propenso a erro humano. Uma simples mudança no nome de um campo na API pode quebrar silenciosamente a interface em produção, pois não há validação de compilação cruzada entre os projetos.

A Solução: Utilizei a arquitetura de Monorepo para criar um pacote compartilhado (@repo/shared). Os schemas Zod definidos neste pacote servem como a única fonte da verdade, gerando tanto os tipos estáticos (TypeScript) quanto as regras de validação de formulários (Frontend) e inputs de API (Backend).

Resultado:

  • Refatorações seguras: alterar um campo no backend gera erro de build imediato no frontend.
  • Validação isomórfica: a mesma regra de email/senha roda no cliente e no servidor.

5. Reordenação Otimista e Restrições de Banco

O Problema: Atualizar a ordem de itens em uma coluna com restrição UNIQUE no banco de dados SQL frequentemente causa erros de colisão. Tentar trocar o item "1" pelo "2" falha imediatamente se a transação não for atômica, pois o índice "2" já está ocupado no momento da escrita.

A Solução: No frontend, utilizei SWR para atualizações otimistas instantâneas. No backend, implementei uma transação que primeiro atualiza os índices para valores negativos temporários, contornando a restrição de unicidade antes de aplicar a nova ordem definitiva.

Resultado:

  • UX fluida sem "pulos" ou espera de rede.
  • Integridade dos dados garantida a nível de banco.

6. Auto-Save Inteligente (Debounce & Dirty Check)

O Problema: Implementar salvamento automático ouvindo cada evento de digitação (onChange) cria um excesso de requisições desnecessárias (overhead). Além disso, a latência de rede variável causa race condition, onde uma resposta antiga pode sobrescrever dados mais recentes, revertendo a modificação do usuário.

A Solução: Criei um hook customizado useAutoSaveForm que combina o monitoramento de estado do React Hook Form com um useDebounceValue. O hook verifica inteligentemente se houve mudança real (isDirty) comparando com o valor inicial antes de disparar a submissão automática.

Resultado:

  • Experiência de edição fluida e moderna.
  • Redução drástica de chamadas de rede desnecessárias.

🏗️ Arquitetura

O sistema é orquestrado via Turborepo, permitindo o compartilhamento de código com Type-Safety End-to-End:

  • Apps: web (Next.js 14 App Router) e api (Express.js).
  • Packages Compartilhados:
    • @repo/database: Cliente Prisma e migrações, garantindo que API e scripts de seed usem a mesma fonte de verdade.
    • @repo/shared: Schemas Zod e tipos TypeScript isomórficos. Uma mudança no schema de validação reflete imediatamente em erros de compilação tanto no frontend quanto no backend.

Infraestrutura

  • Docker Multi-stage: Builds otimizados para produção com redução drástica do tamanho da imagem final.
  • CI/CD: Pipeline GitHub Actions para testes automatizados (Jest/Supertest) e deploy contínuo.

🛠️ Tech Stack

  • Frontend: Next.js, React, Tailwind
  • Backend: Node.js, Express
  • Dados: PostgreSQL, Prisma
  • Infra: Docker, Fly.io