Development Guide
This guide is for contributors working on the Grant platform itself. If you are integrating Grant into your application, see the Integration Guide.
Project Structure
├── apps/
│ ├── api/ # Apollo Server + Express REST API
│ │ ├── src/
│ │ │ ├── config/ # Environment configuration
│ │ │ ├── graphql/ # Resolvers (queries, mutations, field resolvers)
│ │ │ ├── handlers/ # Orchestration layer (caching, transactions)
│ │ │ ├── services/ # Business logic and validation
│ │ │ ├── repositories/ # Database access (Drizzle ORM)
│ │ │ ├── rest/ # REST routes, schemas, OpenAPI
│ │ │ ├── middleware/ # Express middleware
│ │ │ └── lib/ # Shared utilities (logger, errors, auth)
│ │ └── tests/ # Unit, integration, and E2E tests
│ └── web/ # Next.js 15 dashboard (App Router)
├── packages/@grantjs/
│ ├── schema/ # GraphQL schema + codegen types
│ ├── core/ # Domain ports, interfaces, exceptions
│ ├── constants/ # Resource definitions, permissions, groups
│ ├── database/ # Drizzle schemas, migrations, seeds
│ ├── logger/ # Pino adapter
│ ├── errors/ # HTTP error adapter
│ ├── cache/ # Redis / in-memory adapter
│ ├── storage/ # S3 / local adapter
│ ├── email/ # SMTP / SES adapter
│ ├── jobs/ # node-cron / BullMQ adapter
│ ├── server/ # Server SDK (Express, Fastify, NestJS, Next.js)
│ ├── client/ # Browser SDK (React hooks, GrantGate)
│ └── cli/ # Grant CLI
└── docs/ # VitePress documentation siteLayer Boundaries
Transport (GraphQL resolvers, REST routes)
→ Handlers (orchestration, caching, transactions)
→ Services (business logic, validation, audit logging)
→ Repositories (Drizzle ORM, field selection)
→ Database (PostgreSQL + RLS)Hard rules:
- Handlers never import repositories — they go through services
- Repositories never import services or handlers
- REST/GraphQL resolvers never import repositories — they call handlers only
- Import logger from
@/lib/logger, errors from@/lib/errors, shared types from@/types
Development Workflow
# 1. Start infrastructure
docker compose up -d
# 2. Install dependencies
pnpm install
# 3. Run database migrations and seed
pnpm --filter @grantjs/database db:migrate
pnpm --filter @grantjs/database db:seed
# 4. Generate schema types (after GraphQL changes)
pnpm --filter @grantjs/schema generate
# 5. Start dev servers
pnpm --filter api dev # API on :4000
pnpm --filter web dev # Web on :3000Adding a GraphQL Feature
- Schema — Define types, inputs, queries, and mutations in
packages/@grantjs/schema/src/schema/{feature}/ - Operations — Add operation documents in
packages/@grantjs/schema/src/operations/{feature}/ - Generate — Run
pnpm --filter @grantjs/schema generateto produce TypeScript types - Repository — Add database access methods in
apps/api/src/repositories/ - Service — Add business logic with Zod validation in
apps/api/src/services/ - Handler — Add orchestration (caching, transactions) in
apps/api/src/handlers/ - Resolver — Register query/mutation resolvers in
apps/api/src/graphql/resolvers/
Adding a REST Endpoint
See Adding REST Endpoints for the full step-by-step guide covering Zod schemas, routes, RBAC guards, and OpenAPI registration.
TypeScript 7 dual-install
tsc is TypeScript 7 (native). The package named typescript is the 6.x JS API that eslint and vite-plugin-dts still import. Do not replace that alias with typescript@7 until 7.1 and those tools ship a JS API — see decisions/0006-typescript-7-dual-install.md.
Confirm after install: pnpm exec tsc --version prints 7.0.x and pnpm exec tsc6 --version prints 6.0.x.
Code Style
- TypeScript strict mode everywhere
- Conventional Commits:
feat:,fix:,docs:,chore: - Use
@/and@grantjs/*aliases — no deep relative paths - Use
import typefor type-only imports - Use domain-specific errors (
NotFoundError,ValidationError) — never rawthrow new Error(...) - Use the structured logger — never
console.login runtime code
Contributing
Prefer the Agentic SDLC for non-trivial work: story trunk, stacked human-reviewable PRs, then one final PR to main. Avoid month-long single branches and mega-PRs.
- Fork the repository (external contributors)
- Open or approve a story brief, then a stack plan (see templates under
docs/contributing/templates/) - Create the story trunk
feat/<slug>frommain(use a git worktree if another story is already in flight) - Land stacked slice PRs into the trunk (db → schema → api → web as needed); keep each PR reviewable
- Follow layer boundaries and code style
- Ensure checks pass (
pnpm build,pnpm test, Verifier for touched packages) - Open the final pull request: story trunk →
main(deep review); release pipeline ships complete features frommain
Tiny fixes may use a single short-lived branch and PR directly to main.
Related:
- Agentic SDLC — Agent roster, stacked PRs, gates, worktrees
- Architecture Overview — System design and package graph
- Adding REST Endpoints — REST development guide
- Testing — Test setup and patterns