SaaS Starter

Estrutura do projeto

Layout do monorepo, camadas DDD e onde cada coisa vive.

Nível superior

apps/
  client/                Next.js — landing, dashboard, auth, /docs
  server/                Express — API, auth, billing, jobs
packages/
  contracts/             Schemas Zod compartilhados + tipos inferidos
  shared/                Catálogo de permissions, Result, errors, branded types
  tsconfig/              tsconfig bases
  eslint-config/         flat configs que aplicam o layering DDD
tests/                   Playwright e2e

O repo é um workspace Bun. Todo import cross-package interno passa por @app/contracts, @app/shared, etc. — nunca relativo entre apps.

apps/server

src/
  index.ts               Entry point. otel-init DEVE ser o primeiro import.
  otel-init.ts           Inicia o OTel antes de qualquer outra coisa.
  bootstrap/
    env.ts               Schema Zod do env. Fonte de verdade.
    container.ts         Registry de DI com Awilix.
    app.ts               Constrói a app Express e monta todos os routers em /api.
    worker.ts            Bootstrap do worker BullMQ.
  infrastructure/
    db/                  Cliente Prisma + helpers.
    http/                Helper api-route, error handler, rate-limit, registry OpenAPI, requirePermission.
    events/              Bus de eventos em memória.
    jobs/                Factories de queue + worker BullMQ, Bull Board.
    cache/               Adapter Redis (no-op quando REDIS_URL não definido).
    logger/              Setup do pino.
    observability/       Sentry, exporters OTel, helper captureError.
    i18n/                Runtime de tradução server-side.
  modules/<context>/     Bounded contexts (veja abaixo).
  shared/
    aggregate-root.ts    Base AggregateRoot + tipo DomainEvent.
    usecase.ts           Contrato UseCase<Input, Output>.
    value-object.ts      Base de value object.

Camadas DDD dentro de um módulo

Todo módulo em apps/server/src/modules/<context>/ segue o mesmo layout:

domain/                  Entities, value objects, repository interfaces.
                         Sem I/O. Sem imports de framework. Sem Prisma. Sem Express.
application/             Use cases, ports (output adapters), DTOs.
infrastructure/          Repos Prisma, adapters de terceiros, mappers.
interfaces/http/         Controladores Express, registro de rotas, schemas Zod.

A regra de dependências é apenas para dentro: interfaces/ depende de application/, que depende de domain/. Infrastructure implementa os ports declarados em application/ (ou as repository interfaces declaradas em domain/).

ESLint aplica. domain/ não pode importar express, @prisma/client, nem nada de application/ / infrastructure/ / interfaces/. Tente — o import é sinalizado antes do commit.

Os bounded contexts entregues hoje: iam, tenancy, billing, notifications, storage, audit, feature-flags. Ver Bounded contexts.

apps/client

app/
  page.tsx, es/, pt/     Landing pages (i18n).
  _landing/              Componentes da landing + dicionário.
  (auth)/                Sign-in, sign-up, forgot-password.
  (dashboard)/           Surface autenticado da app.
  docs/[[...slug]]/      Fumadocs.
  api/search/            Endpoint de busca Orama.
  globals.css            Tokens (OKLCH) + base + utilities.
  layout.tsx             Carrega Geist Sans + Geist Mono uma única vez.
components/
  brand/                 Primitives do design system UseDeploy. Leia /docs/pt/frontend.
  ui/                    Primitives derivados de shadcn.
content/docs/            O MDX que você está lendo.
lib/
  api/                   Cliente openapi-fetch + tipos gerados.
  validations/           Refinements client-only construídos sobre @app/contracts.
  source.ts              Content source do Fumadocs.

O client fala com o server via openapi-fetch e os paths tipados em lib/api/openapi-types.ts. Nunca redefina um campo conhecido do server em um schema Zod do client — estenda o contract, não bifurque.

packages/contracts

Schemas Zod + tipos inferidos que cruzam o limite client/server. Forms e mutations importam daqui:

src/
  auth.ts                Login, register, password reset.
  user.ts                Perfil de user, update, list.
  common.ts              Primitives compartilhados (paginação, IDs).
  index.ts               Barrel.

Se um endpoint do server muda seu body ou response, o contract muda aqui, o OpenAPI é regenerado (bun run generate:api), e o client compile-checa a nova forma.

packages/shared

src/
  permissions/           Catálogo resource:action + helper hasPermission.
  result/                Result<T, E> + construtores ok/err.
  errors/                DomainError, UnauthorizedError, ForbiddenError, etc.
  types/                 Tipos branded para IDs.
  index.ts               Barrel.

Importado como @app/shared em ambas as apps.

Nesta página