Template fullstack opinativo (NÃO é framework: sem packages publicados, sem CLI de scaffold — clona e usa). Stack: Hono + oRPC + Drizzle/Postgres + Zod no back; React + TanStack + Tailwind/shadcn no web; Expo no mobile; Tauri no desktop.
api/ back: modules/<recurso>/*.controller.ts (oRPC fino) + *.service.ts (regra)
web/ front web: file-based TanStack Router (routes/arquivo = rota) + shadcn
mobile/ Expo Router (file-based) + mesmo client oRPC
desktop/ Tauri 2: serve web/dist (zero UI própria)
lib/ compartilhado: env.ts db.ts openapi.ts server.ts (único lugar c/ lógica reusável)
contracts/ oRPC contract-first (oc.route) — FONTE da API, swagger e clients
routes/api.ts contrato-RAIZ + Hono shell (health/spec/docs) + mount OpenAPIHandler
database/ schema/tables (Model: tabela+Zod+tipos) + migrations + seeders + factories
config/ env.ts (Zod, falha no boot) + database.ts (conexão ÚNICA db)
docs/ architecture.md (regras) · api.md (ponteiro p/ Scalar) · openapi.json (gerado)
Detalhe das regras em docs/architecture.md — leia antes de codar.
pnpm install
docker compose -f docker/compose.yml up -d db # Postgres local (obrigatório p/ dev)
pnpm seed && pnpm dev # API :3333 · /docs (Scalar) · /openapi.json
pnpm check # tsc api+web+mobile
pnpm test # bun test, PGlite em memória (NODE_ENV=test)
pnpm build # build do web
pnpm --filter @mystack/api openapi:export # regenera docs/openapi.json (commit!)
pnpm --filter @mystack/desktop dev|build # precisa Rust + webkit (Linux)database/schema/tables/tickets.ts— tabela Drizzle (pgTable, uuid client-side,timestamp mode:'string') + Zod (Create/Update/Params/Query) + tipos. Um arquivo só.contracts/tickets.contract.ts—oc.route({method, path})+ input/output do schema.summary/tagsviram swagger. Registre emcontracts/index.ts(appContract).api/src/modules/tickets/tickets.service.ts— regra comdbde@app/config/database. NuncaContextaqui.nullp/ ausente (router converte emORPCError).api/src/modules/tickets/tickets.controller.ts—implement(contrato)+ handlers de 1 linha. Registre emroutes/api.ts(orpcRouter).database/migrate.ts— DDL da tabela +database/migrations/NNNN_nome.sql.api/tests/— cubra CRUD + 400 + 404.- Telas:
web/src/routes/tickets/index.tsx(createFileRoute) e/oumobile/app/tickets/index.tsx. Front usaorpc.tickets.list.queryOptions({ input })— nunca monte URL. pnpm openapi:export(via filter api),pnpm check,pnpm test.
- Paths canônicos SEM trailing slash (
/products, não/products/). oRPC não casa igual Hono. - Sem
z.coerceem query: SmartCoercion converte?limit=20sozinho. exactOptionalPropertyTypes: true— código gerado (shadcn) às vezes precisa de patch mínimo.- Alias
@app/*→ raiz do repo. Bun, Vite e Metro resolvem (metro viametro.config.js); se criar pasta nova fora do padrão, confira os 3 resolvers + includes dos tsconfigs. - TS 7: sem
baseUrlnos tsconfigs;routeTree.gen.tsdo web é COMMITADO (tsc precisa dele). - Banco fora do repo:
DATABASE_URLobrigatório; dev = compose, teste = PGlite, prod = gerenciado.timestampsempremode:'string'(senão o output Zod quebra comDate). - Versões no
catalog:dopnpm-workspace.yaml. Front trava no Expo SDK 57 (react 19.2.3,react-native 0.86.3).pnpm dlx shadcn addok, mas passe as deps p/ o catalog e confira o diff (patches emcalendar.tsx/spinner.tsx, CSS emshadcn-tailwind.css). docs/openapi.jsoné artefato: regenerar e commitar após mudar contracts.- Desktop build exige Rust + webkit2gtk; AppImage exige FUSE (use
--bundles debsem FUSE).