Pular para o conteúdo
Ecossistema Anahê · 05/05api.anahe.com.br

Anahê · API

O que fica atrás dos três sites e do painel.

Papel
Arquitetura & Back-end
Ano
2026
Status
No ar

— Ecossistema

Quatro apps, uma API

Anahê Canavezzi é escritora e designer. Ela precisava de três sites públicos (o do livro, o do estúdio e o blog) e de um painel para cuidar de tudo sem depender de mim. Montei os quatro apps Next.js num monorepo com Turborepo e pnpm, servidos por uma única API NestJS e um único banco.

Decisão de arquitetura

Considerei micro-frontends e descartei: não há vários times nem composição em runtime. São quatro sites que compartilham design system, tipos e schemas, que é o caso de uso do Turborepo.

PostgreSQL · Redis · R2Railway · Cloudflare

Um painel e uma API cuidam do conteúdo dos três sites públicos.

— A história

Como construí cada parte

Capítulo 01Turborepo

Um monorepo com regras de dependência

Os quatro apps Next.js e a API vivem no mesmo repositório, com Turborepo e pnpm workspaces. Apps importam pacotes; pacotes nunca importam apps. Dos apps de produto, só a API importa packages/db, então nenhum site toca o banco diretamente: regra de negócio e invalidação de cache ficam num lugar só.

Diagrama · Turborepo
apps/
writerNext.js 16studioNext.js 16blogNext.js 16adminNext.js 16apiNestJS 11
importam
packages/
uishadcn + tokenssharedschemas Zodapi-clientfetchers tipadoscontent-rendererblocos do editoranalytics<Tracker />emailsReact Emailconfigeslint · tsconfig · tailwind
dos apps de produto, só a api importa
packages/db
schema.prismamigrationsseedPostgreSQL 18

Os apps Next.js nunca acessam o banco. Leem e escrevem pela API, com os tipos de packages/shared.

  • packages/shared depende só de zod
  • Cache do Turbo no CI: o que não mudou não roda de novo

Capítulo 02/public · /admin

Duas superfícies, dois níveis de confiança

/public/* atende os sites: sem login, quase só leitura, só aceita a origem dos três sites e tem Turnstile nos formulários. /admin/* aceita só a origem do painel, com cookies, e passa por CSRF, JWT e papéis. As duas validam o body com o mesmo schema Zod que valida o formulário.

Diagrama · /public · /admin
/public/*sites · sem login
HelmetCORS (sites)Throttler · RedisTurnstile (formulários)ZodValidationPipeController
/admin/*painel · cookies
HelmetCORS (painel)Throttler · RedisCsrfGuardAdminAuthGuard · JWT + papéisZodValidationPipeController
as duas terminam no mesmo service
ServicePrisma 7PostgreSQL 18
  • nestjs-zod gera os DTOs de packages/shared
  • Erros no formato { code, message, details }

Capítulo 03Módulos

23 módulos, um por responsabilidade

Cada domínio virou um módulo NestJS: o conteúdo dos três sites, as pessoas (painel, leitores, leads e briefings) e a plataforma que sustenta tudo. Dentro de content, um registro de coleções dá a qualquer lista do painel reordenar e mostrar/esconder.

Diagrama · Módulos
Conteúdo5 módulos
postscontentmediapublicrevalidation
Pessoas6 módulos
authmereadersleadsbriefingsaudit
Plataforma12 módulos
configloggerprismaredisstoragemailjobsanalyticsdashboardcryptoturnstilehealth

Em destaque, os módulos que os outros usam: content (coleções que reordenam e escondem), revalidation (cache dos sites) e jobs (filas).

  • config valida o env com Zod e falha no boot
  • Swagger em /docs fora de produção

Capítulo 04Publicar

Publicar acorda só o que mudou

Os sites públicos guardam os dados em cache, por tag. Quando a Anahê publica, a API grava no banco e põe na fila a revalidação de cada site afetado, com as tags certas. Se um site estiver fora do ar, o job tenta de novo, e a ação no painel nunca falha por isso.

Diagrama · Publicar
  1. Painel → API: POST /admin/posts/:id/publish
  2. API → Postgres: status = PUBLISHED
  3. API → BullMQ: fila revalidation · blog, writer
  4. API → Painel: 200 OK
  5. BullMQ → Blog: POST /api/revalidate · posts, post:slug, archive

Cada site chama revalidateTag() só nas tags recebidas, e o resto continua no cache. A Escritora recebe posts pelo mesmo caminho, e writings quando o texto também vai para lá.

  • Header x-revalidate-secret
  • Reações e comentários carregam no client, fora do cache

Capítulo 05BullMQ

O que roda fora da requisição

Tudo que pode esperar vai para o BullMQ, no Redis, em quatro filas. Post agendado vira job com delay, e um job de segurança a cada 5 minutos publica o que tiver ficado para trás se o Redis perder algo. E-mails e revalidações tentam de novo com espera exponencial; métricas e limpezas rodam em horários fixos.

Diagrama · BullMQ
Redis 7 · BullMQ4 filas
  • Agendar postpublishing · delayPublica e revalida
  • Post publicadorevalidationPOST /api/revalidate nos sites
  • Lead · comentário · código de loginmailResend + React Email
  • A cada 5 minmaintenancePublica agendados atrasados
  • Todo diamaintenancePageView → resumo diário
  • Todo diamaintenanceApaga leitores não confirmados > 7 dias
  • Job de segurança a cada 5 min
  • Limpeza LGPD de pendências com mais de 7 dias

Capítulo 06Leitor · BFF

Comentar sem conta e sem senha

O leitor confirma o e-mail uma vez por aparelho. Como a API está em outro subdomínio, o blog funciona como BFF: guarda o token num cookie httpOnly só do blog e repassa para a API no header x-reader-token. A API guarda apenas o hash.

Diagrama · Leitor · BFF
  1. Leitor → Blog · BFF: informa o e-mail
  2. Blog · BFF → API: POST /public/readers/identify
  3. API → E-mail: link de confirmação · 30 min
  4. E-mail → Leitor: abre o link
  5. Leitor → Blog · BFF: /confirmar?token
  6. Blog · BFF → API: POST /public/readers/verify
  7. API → Blog · BFF: token de sessão
  8. Blog · BFF → Leitor: cookie reader_session · httpOnly

O cookie é só do blog. Emitido pela API, ele precisaria do domínio raiz e a sessão vazaria para os outros subdomínios.

  • Link de confirmação de uso único, 30 min
  • Sessão de 1 ano por aparelho

Capítulo 07Deploy

Onde cada parte roda

O domínio .br continua no Registro.br, com DNS na Cloudflare. Os sites e o painel ficam na Vercel com DNS direto, como ela recomenda. A API, o Postgres e o Redis ficam na Railway. A Cloudflare ainda entrega o R2 da mídia e o Turnstile dos formulários.

Diagrama · Deploy
Registro.brdomínio .brCloudflareDNS · DNSSEC
VercelDNS only
writerstudioblogadmin
RailwayDNS only
apiPostgreSQL 18Redis 7
a API também fala com
Cloudflare R2mídiaResende-mail

Em desenvolvimento, o Docker Compose sobe Postgres, Redis, RustFS e Maildev no lugar dos serviços de produção.

  • SSL Full (strict) na zona
  • Mídia pública em media.anahe.com.br

— Tecnologias

API

  • NestJS 11
  • nestjs-zod
  • Passport JWT
  • @nestjs/throttler
  • @nestjs/swagger
  • pino
  • Helmet

Dados

  • Prisma 7
  • PostgreSQL 18
  • Redis 7
  • BullMQ

Infra

  • Railway
  • Cloudflare
  • R2
  • Resend
  • Sentry

Testes · CI

  • Vitest
  • Supertest
  • GitHub Actions

E2E

  • Playwright
  • axe-core