FULL-STACK MONOREPO

NaQuadra

Plataforma brasileira de acompanhamento da NBA construída como monorepo completo — placares ao vivo, standings, estatísticas de jogadores e foco em UX educativa para o público brasileiro.

Next.js 14 NestJS TypeScript Monorepo Tailwind CSS
NaQuadra — Plataforma NBA Brasileira

O Contexto

A NBA é a liga esportiva mais seguida pelos brasileiros fora do futebol, mas as plataformas existentes são majoritariamente em inglês e não contextualizam o esporte para o público local. O NaQuadra surgiu como resposta direta a essa lacuna: uma plataforma 100% em português, com dados em tempo real e foco na experiência educativa do fã casual.

O projeto nasceu como um desafio técnico pessoal para demonstrar domínio de arquitetura full-stack moderna — monorepo com separação clara entre frontend (Next.js) e backend (NestJS), consumindo a API pública da NBA com cache inteligente para evitar rate limiting.

Arquitetura & Stack

// Arquitetura do Monorepo
apps/web
Next.js 14 (App Router)
React Server Components
Tailwind CSS
TypeScript
HTTP / REST
packages/shared
Types comuns
apps/api
NestJS
Módulos decorados
Cache Layer
NBA API proxy
↓ Deploy independente
Vercel (web)
Railway / Render (api)

Frontend

  • • Next.js 14 (App Router)
  • • React Server Components
  • • TypeScript strict
  • • Tailwind CSS
  • • Lucide Icons

Backend

  • • NestJS
  • • Módulos injetáveis
  • • Guards & Interceptors
  • • HTTP Cache (TTL)
  • • NBA API client

Monorepo

  • • Turborepo
  • • packages/shared
  • • Types unificados
  • • Build pipeline
  • • .env por workspace

DevOps

  • • Vercel (web)
  • • vercel.json (root dir)
  • • GitHub Actions CI
  • • Preview deployments
  • • Env management

Por que Monorepo?

A decisão de usar um monorepo foi arquitetural deliberada. Com frontend e backend no mesmo repositório, compartilhamos tipos TypeScript entre as camadas — o mesmo GameDTO que o NestJS retorna é o mesmo que o Next.js consome. Isso elimina erros de integração e mantém o contrato de API sempre atualizado.

Cache na Borda

A NBA API tem rate limiting rigoroso. Implementei um cache TTL no NestJS e configurei revalidate no Next.js para dados históricos. Dados de placar ao vivo têm TTL curto (30s); standings têm TTL de 1h. Essa estratégia reduziu as requisições à API externa em ~85%.

apps/api/src/games/games.service.ts
Cache Strategy
// Cache com TTL dinâmico baseado no tipo de dado
@Injectable()
export class GamesService {
  constructor(
    private readonly httpService: HttpService,
    private readonly cacheManager: Cache,
  ) {}

  async getLiveScores(): Promise<GameDTO[]> {
    const cacheKey = 'live_scores';
    const cached = await this.cacheManager.get<GameDTO[]>(cacheKey);
    
    if (cached) return cached; // Hit — sem API call
    
    const data = await this.fetchFromNBAApi('/games/today');
    await this.cacheManager.set(cacheKey, data, 30_000); // TTL 30s
    
    return data;
  }
}

O Maior Desafio

Configurar o Vercel para reconhecer apenas o apps/web em um repositório monorepo sem acionar builds do backend foi o maior ponto de atrito. O Vercel por padrão tenta buildar a raiz do projeto — foi necessário criar um vercel.json apontando para o subdiretório correto e configurar variáveis de ambiente isoladas por workspace.

A Solução

Criei um vercel.json dentro de apps/web que instrui o Vercel a usar aquele diretório como root. Combinado com Turborepo para gerenciar o pipeline de build do monorepo, o deploy passou a ser determinístico e isolado — web e API deployam independentemente sem interferência.

Aprendizados

Arquitetura

Monorepos com Turborepo exigem convenções claras desde o início — naming de packages, scripts unificados e .env isolados por app.

Performance

React Server Components mudaram radicalmente como penso data fetching — buscar no servidor e entregar HTML pronto elimina waterfalls de cliente.

Deploy

Configurar deploy de monorepos em PaaS requer documentação específica. Valeu criar um ADR (Architecture Decision Record) para cada decisão de infraestrutura.