This repo is a small, end‑to‑end vertical slice of an energy portfolio product. It shows how I work: model the domain first, define a clear contract, implement behind stable seams, and keep change safe with tests.
The slice includes:
- A domain package with rules and factories
- A GraphQL API shaped around real user queries
- DynamoDB Local persistence behind repository interfaces
- A React app consuming the real API
- Local infra, seed scripts, and both unit and integration tests
This repo demonstrates how I think, not how many features I can ship.
packages/domain
- Types: Customer, Project, EnergyAsset
- Enums: ProjectStatus, EnergyAssetType
- Validation helpers for invariants
- Factories and seed helpers
- Unit tests for rules and edge cases
packages/shared
- Typed error helpers
- Cursor pagination helpers used by the API
services/api
- Schema in
schema.graphql - Queries:
customer(id)customers(first, after)projectsByCustomer(customerId, first, after, status)energyAssetsByProject(projectId, first, after, type)
- Thin resolvers with no business logic
- Pagination and filtering tests
services/api/src/repository
interfaces.tsdefines seamsinMemory.tsfor fast testsdynamo.tsadapter for DynamoDB Local- Lists sorted by
createdAtthenidfor stable cursors
apps/web
- React + Vite + TypeScript
- Apollo Client
- Panels for customers, projects, and assets
The slice follows a strict order:
- Domain defines rules and shapes
- Contract exposes those shapes with GraphQL
- Repository hides DynamoDB and storage details
- UI interacts only with the contract
No GraphQL types leak into the domain. No DynamoDB calls leak into resolvers.
This keeps the system testable, replaceable, and easy to extend.
├─ packages/
│ ├─ domain/
│ └─ shared/
├─ services/
│ └─ api/
├─ apps/
│ └─ web/
├─ local/
└─ vitest.workspace.ts
pnpm install
pnpm db:up
pnpm seed
pnpm dev:api:dynamo
API runs at: http://localhost:4000/graphql
pnpm dev:web
Web runs at: http://localhost:5173
# Unit + fast tests
pnpm test
# Dynamo integration tests (DB must be running)
pnpm db:up && pnpm test:api:dynamo
query CustomerById {
customer(id: "cust_1") {
id
name
createdAt
}
}
query Customers {
customers(first: 10) {
edges { cursor node { id name createdAt } }
pageInfo { hasNextPage endCursor }
}
}
query ProjectsByCustomer {
projectsByCustomer(customerId: "cust_1", first: 10, status: ACTIVE) {
edges { cursor node { id name status createdAt } }
pageInfo { hasNextPage endCursor }
}
}
query EnergyAssetsByProject {
energyAssetsByProject(projectId: "proj_3", first: 10, type: SOLAR) {
edges { cursor node { id type capacityKw active createdAt } }
pageInfo { hasNextPage endCursor }
}
}Domain first — rules live in packages/domain.
Contract first — GraphQL schema follows user flows.
Stable seams — repositories abstract persistence.
Small slices — layers stay independent and testable.
Change‑safe — pagination and filters have tests to prevent drift.
If you want a 30–45 minute review:
- Domain types:
packages/domain/src/types.ts - Validation:
packages/domain/src/validation.ts - GraphQL contract:
services/api/src/graphql/schema.graphql - Repositories:
services/api/src/repository - UI panels:
apps/web/src/components
If this were to grow beyond a portfolio slice:
- Auth + roles (JWT, API key, role map)
- Error taxonomy (consistent domain → GraphQL mapping)
- Batching (Dataloader for list queries)
- Observability (trace IDs, structured logs)
- CI + Deploy (schema drift check, test matrix, minimal AWS deploy)
Done:
- Domain model
- GraphQL contract
- DynamoDB persistence
- Pagination + filtering
- React slice
- Local infra + seeding
Optional next steps:
- Auth / roles
- Error taxonomy
- Tracing and batching
- Workflow mutation + events
- Deployment story