-
Notifications
You must be signed in to change notification settings - Fork 2
ADR 007 Project Structure
Claude product-architect (Opus 4.6) edited this page Feb 22, 2026
·
2 revisions
Accepted
Cornerstone is a full-stack TypeScript application with a React client, Fastify server, and shared type definitions. The project needs a structure that:
- Keeps client and server code separated but in one repository
- Shares TypeScript types between client and server
- Supports independent build steps for each package
- Works with npm's native workspace feature (no additional monorepo tooling)
- Keeps the structure simple for a small team (<5 users, <3 developers)
Single-package structure (flat)
- All code in one
src/directory withclient/andserver/subdirectories - Simplest setup but makes independent builds difficult
- Shared types require import path conventions rather than package boundaries
- Build configuration becomes complex (one tsconfig handling both client and server)
Turborepo / Nx monorepo
- Powerful build orchestration with caching and parallel execution
- Additional tooling dependency and configuration complexity
- Designed for large monorepos with many packages -- overkill for 3 packages
- Adds a learning curve for new contributors
npm workspaces (native)
- Built into npm; no additional tooling required
- Supports
npm run <script> -w <workspace>for targeted execution - Workspace packages reference each other via
"*"version specifier - Simple, well-understood, and sufficient for 3 packages
Use npm workspaces with three packages: shared, server, and client.
cornerstone/
package.json # Root workspace config, shared dev dependencies
tsconfig.base.json # Base TypeScript config (extended by each package)
eslint.config.js # ESLint flat config (shared across all packages)
.prettierrc # Prettier config
vitest.config.ts # Vitest config (discovers tests across all packages)
Dockerfile # Multi-stage Docker build
CLAUDE.md # Project guide and conventions
.gitignore
.dockerignore
shared/ # @cornerstone/shared
package.json
tsconfig.json
src/
types/ # API request/response types, entity types
index.ts # Package entry point (re-exports)
server/ # @cornerstone/server
package.json
tsconfig.json
drizzle.config.ts # Drizzle-kit configuration
src/
app.ts # Fastify app factory
server.ts # Entry point (starts HTTP server)
routes/ # Route handlers organized by domain
plugins/ # Fastify plugins (auth, database, etc.)
services/ # Business logic layer
db/
schema.ts # Drizzle ORM schema definitions
migrations/ # Generated SQL migration files
types/ # Server-only types
client/ # @cornerstone/client
package.json
tsconfig.json
vite.config.ts # Vite configuration
index.html # SPA entry HTML
src/
main.tsx # Application entry point
App.tsx # Root component with routing
components/ # Reusable UI components
pages/ # Route-level page components
hooks/ # Custom React hooks
lib/ # Utility functions, API client
styles/ # Global styles (Tailwind entry point)
@cornerstone/shared <-- @cornerstone/server
<-- @cornerstone/client
Both server and client depend on shared. The shared package has no dependencies on either.
-
shared(tsc) -- must be built first since others depend on it -
client(vite build) -- produces static assets inclient/dist/ -
server(tsc) -- produces Node.js modules inserver/dist/
The root build script enforces this order:
npm run build -w shared && npm run build -w client && npm run build -w server
| Context | Convention | Example |
|---|---|---|
| Database columns | snake_case |
created_at, budget_category_id
|
| TypeScript variables/functions | camelCase |
createdAt, getBudgetCategory
|
| TypeScript types/interfaces | PascalCase |
WorkItem, BudgetCategory
|
| File names (TS) | camelCase |
workItem.ts, budgetService.ts
|
| File names (React components) | PascalCase |
WorkItemCard.tsx, GanttChart.tsx
|
| API endpoints | kebab-case |
/api/work-items, /api/budget-categories
|
| CSS class names | Tailwind utilities | (no custom BEM/class conventions) |
- No additional monorepo tooling beyond npm
- Clear separation of concerns between client, server, and shared code
- TypeScript project references ensure correct build ordering
- Each package can have its own tsconfig tuned to its environment (browser vs Node.js)
- Simple enough for a small team to understand immediately
- npm workspaces lack build caching (every
npm run buildrebuilds from scratch) - No parallel build orchestration (sequential build order required)
- Adding a fourth package (e.g., e2e tests) requires updating the root workspace config
- Shared package must be built before running server or client in development (handled by tsx/vite resolving source directly)