Imported from geravr/charro-stack (
apps/api/AGENTS.md). Install upstream withnpx skills add geravr/charro-stack --skill api. Copyright stays with the author.
AGENTS.md
Local guidance for backend work in apps/api. Read ../../AGENTS.md first for monorepo-wide rules.
Scope
- Applies to Hono app setup, tRPC routers, Better Auth configuration, database queries (Drizzle), backend tests (Vitest), and upload/storage logic under
apps/api. - Source of truth is live code in
apps/api, especially index.ts, dev.ts, serve.ts, lib/app.ts, lib/trpc.ts, lib/context.ts, lib/auth.ts, routers/, and services/.
Architecture & Layering
Follows Router/Handler → Service → Database/Infrastructure:
routers/: Request validation, auth checks, tRPC procedures, and mapping HTTP requests to services.services/: Business logic, orchestration, database writes, and third-party integrations.lib/: Infrastructure setup (PostHog, Better Auth, Drizzle instance).
Runtime and Entrypoints
dev.ts— Starts Hono and tRPC server locally for development using Bun.serve.ts— Production entrypoint for VPS or Docker deployments.- Always use the Bun runtime path and environment. No Redis, BullMQ queues, or WebSockets are utilized in this configuration.
Database & Drizzle
- Drizzle clients are initialized in lib/db.ts using
postgresand a direct connection pool. - Both
dbanddbDirectare request-scoped handles indev.tsandserve.ts. Currently,dbDirectis an alias pointing to the same database connection pool. - Database schemas reside in the
@repo/dbworkspace package underdb/schema/. - All database migrations are generated and applied via the Drizzle kit CLI from the root workspace:
bun db:generateto generate Drizzle migrations.bun db:migrateto apply migrations.
- Migration Hygiene:
- Never hand-write SQL migration files or manually patch a generated migration.
- One in-progress feature should have at most one uncommitted migration. If schemas change again during development, delete the uncommitted migration and regenerate a single replacement.
- Never rewrite a migration that has already been pushed or shared.
db:pushis only for disposable local iteration and must never replace the migration workflow.
Observability & Telemetry
- Server-side PostHog integration is centralized in lib/posthog.ts (
captureServerException, etc.). - Telemetry events must never include raw personal keys, token values, or customer secrets.
tRPC and Hono Boundary
- tRPC (
/api/trpc/*) is the authenticated JSON RPC for the SPA. Keep the tRPC adapter error protocol: do not put Hono auth middleware in front of/api/trpc/*. - Hono owns Better Auth (
/api/auth/*), pre-session public-auth wrappers, multipart upload, binary object download, and health. First-party Hono JSON errors use{ code, message }fromappJsonError. - Pre-session OTP wrappers stay on Hono; new product JSON stays on thin tRPC routers.
- Do not add Hono RPC /
hcfor new product JSON. New product mutations and queries go on tRPC routers.
tRPC and Hono Routers
- lib/app.ts composes the Hono app, mounts
/api/trpc/*,/api/auth/*handlers, and mounts custom endpoints like/api/storage/uploadand/api/storage/object. - Thin Routers: Routers and Hono endpoints must remain thin. Their job is request validation (Zod input schemas), authentication/authorization guards, and delegating actual business logic execution to the service layer.
- Hono handlers should follow the flow: validate input, delegate logic, send response.
Service Layer Conventions
- Business logic, Drizzle queries, transactional blocks, and external integrations (e.g. Resend for emails in lib/email.ts, AWS S3 for storage in lib/storage.ts) must live in services under
services/(such asadmin-service.ts). - Services,
lib/storage.ts, and first-party Hono handlers throwAppErrorfor request-facing failures. They must not throwTRPCErroror import@trpc/server. tRPC procedure middleware and the HonoerrorHandlertranslateAppErrorat the transport edge.
Global Invariants & Access Control
- Single-User Accounts: Product accounts are single-user. Do not register the Better Auth
organizationplugin, mount anorganizationtRPC router, or reintroduce org tables /activeOrganizationId. - Platform invitations (
platform_invitation) and super-admin flows remain. Do not conflate them with product workspaces. - Multi-write business operations must use transactions (
ctx.db.transaction) to preserve atomic flows. - Language Policy: Code identifiers, comments, and logs stay in English. User-facing failures expose stable
appCodevalues from@repo/core; clients map codes to localized copy. Englishmessagefields remain for logs only. - Storage: Object keys are always under
users/{userId}/….canAccessStorageObjectmust denyworkspaces/keys. - List pagination: Collection procedures return the
@repo/coreenvelope (items,page,pageSize,total) viapaginate()in lib/paginate.ts. Each service ownswhere/orderByand applies the same predicate to list andCOUNT. Escape LIKE metacharacters inq. Do not return a bare array. A lonelimitis not pagination. Exceptions: singletons, aggregates, and explicitly tiny bounded lists.
Environment
- Environment variables are validated on startup using Zod in lib/env.ts.
- Schema fields:
ENVIRONMENT,APP_NAME,APP_ORIGIN,DATABASE_URL,BETTER_AUTH_SECRET,RESEND_API_KEY,RESEND_EMAIL_FROM. Optional: PostHog (POSTHOG_*), S3 (STORAGE_S3_*),SUPER_ADMIN_EMAIL.API_ORIGINis consumed by the SPA proxy / marketing templates, not by lib/env.ts. - Non-Zod process env used by this package:
PORT(listen),AUTO_MIGRATE(Docker entrypoint only). - Shared template: root
.env.example. Do not treat removed ghost keys as required config.
Anti-patterns
- Embedding complex business logic or multi-step database orchestration directly inside routers or Hono handlers.
- Throwing a raw
ErrororTRPCErrorinstead ofAppErrorfor request-facing failures. - Leaking database details or raw SQL errors to the API clients.
- Telemetry captures containing raw prompts, credentials, or personal keys.
- Bypassing type safety (avoid
any,@ts-ignore, etc.) unless there is a documented technical reason. - Reintroducing Better Auth organizations, product workspaces, or org-scoped storage prefixes.
Review Priorities
- Catch authorization guard bypasses and data mutations without session validation.
- Catch missing transactional boundaries on multi-write services.
- Ensure proper error-handling (
AppError+ catalogappCode) and input schema validation (Zod). - Confirm storage object reads stay ACL-checked and deny
workspaces/keys.
Commands
See the root README for the canonical command reference.
From inside apps/api:
bun dev # Dev server
bun build # Build API
bun test # Run Vitest tests
bun typecheck # Typecheck API code