A warehouse inventory API built with Node.js, TypeScript, GraphQL, Prisma, and PostgreSQL.
The codebase separates domain rules, application workflows, delivery, and persistence. Business logic depends on repository interfaces rather than GraphQL, Express, Prisma, or PostgreSQL, allowing each boundary to be tested and replaced independently.
See the domain vocabulary and business rules.
HTTP / GraphQL
|
Application use cases
|
Repository ports
|
+-- Prisma repositories ---- PostgreSQL
|
`-- In-memory repositories
The dependency direction points inward:
- Domain objects enforce business invariants.
- Use cases coordinate domain objects through repository ports.
- GraphQL and Express translate transport concerns.
- Prisma repositories translate persisted records into domain objects.
main.tsselects and assembles the production adapters.
This view describes the application boundaries. See the AWS architecture for the deployed network, runtime, IAM, Terraform ownership, and lifecycle relationships.
Express listens on http://localhost:3000 by default. PORT changes the
listening port without changing the route structure.
| Method | URL | Preview key | Purpose |
|---|---|---|---|
GET |
/health |
No | Process liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL. |
GET |
/ready |
No | Database readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}. |
GET or POST |
/graphql |
Yes | GraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface. |
GET |
/ |
Yes | Returns the current Hello World! application response. |
GET |
/lifetime |
Yes | Demonstrates application, request, and transient object lifetimes. |
Example local requests:
curl http://localhost:3000/health
curl http://localhost:3000/ready
curl \
--header "X-Preview-Key: $APP_PREVIEW_KEY" \
--header 'content-type: application/json' \
--data '{"query":"{ health }"}' \
http://localhost:3000/graphqlGraphQL Yoga exposes the application use cases through thin resolvers. Zod validates external input before it enters the application layer.
The delivery boundary also provides:
- intentional
BAD_USER_INPUT,NOT_FOUND, andCONFLICTerrors; - a stable
extensions.issues[]error shape; - masking for unexpected internal failures;
- integer-cent conversion into the domain
Moneyvalue object; - a GraphQL product-unit enum aligned with the domain values.
The assembled HTTP boundary uses an application preview key to keep local and disposable environments private. Organization registration, login, JWT verification, and authenticated GraphQL context creation are implemented. Tenant-scoped authorization remains under development. See the preview-key and authentication designs for those boundaries.
Example product mutation:
mutation CreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
id
organizationId
supplierId
sku
label
unit
purchasePriceCent
isActive
}
}{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}One Prisma client is shared by all repositories. Repository constructors accept that client explicitly, which also allows database tests to supply an isolated Testcontainers connection.
See the setup for PostgreSQL, Prisma, migration, and database test commands.
The test suite is separated by boundary:
- Domain tests verify entities, value objects, invariants, and calculations.
- Use-case tests verify workflows through individual in-memory repositories.
- GraphQL tests execute Yoga operations with fresh in-memory repository sets.
- HTTP integration tests verify the assembled Express application.
- Database tests apply committed migrations to disposable PostgreSQL containers and verify Prisma repositories. The stock-movement adapter tests cover aggregate reconstruction and prove rollback when one nested line fails.
The default suite stays independent from Docker:
npm testDatabase tests with Docker Desktop or a Docker-compatible CI runner:
npm run test:databaseDatabase tests with local Colima:
npm run test:database:colimaTestcontainers starts postgres:17-alpine, applies migrations with
prisma migrate deploy, provides the generated connection URL to Vitest, and
removes the container after the suite.
Install dependencies:
npm installCreate the local environment file:
cp .env-sample .envSet DATABASE_URL, APP_PREVIEW_KEY, and JWT_SECRET in .env, start
PostgreSQL, and apply the committed migrations. Use at least 32 random bytes
for secrets in shared environments; for local development you can generate a
value with openssl rand -base64 32.
Then prepare the database:
docker compose up --wait database
npx prisma migrate deploy
npx prisma generateStart the API:
npm run devThe server listens on http://localhost:3000; GraphQL Yoga is mounted at
/graphql.
Compose runs PostgreSQL on an internal network and also publishes it on the host loopback interface for development tools such as TablePlus. The database uses a named volume so it survives ordinary container restarts. Migrations remain an explicit operation and are not part of the API entry point.
The Compose API service reads APP_PREVIEW_KEY and JWT_SECRET from .env and
refuses to start if either is missing.
Build the image, migrate the database, and start the API:
docker compose build
docker compose up --detach --wait database
docker compose run --rm api \
npx --no-install prisma migrate deploy
docker compose up --detach --wait apiVerify the operational and GraphQL endpoints with the preview key from .env.
Export the same value in the current shell so curl can read it:
curl http://localhost:3000/health
curl http://localhost:3000/ready
export APP_PREVIEW_KEY="replace-with-local-preview-key"
curl \
--header "X-Preview-Key: $APP_PREVIEW_KEY" \
--header 'content-type: application/json' \
--data '{"query":"{ health }"}' \
http://localhost:3000/graphqlSet HTTP_PORT when port 3000 is already in use:
HTTP_PORT=32000 docker compose up --detach --wait apiStop the environment while retaining its database volume:
docker compose downRemove the environment and its local database data:
docker compose down --volumesnpm test # fast domain/application/delivery tests
npm run test:database # PostgreSQL tests through Testcontainers
npm run test:database:colima # PostgreSQL tests through local Colima
npm run typecheck # TypeScript validation
npm run lint # ESLint
npm run build # compile to dist
npx prisma format # format Prisma schema file
npx prisma validate # validate Prisma configuration and schema
npx prisma generate # regenerate Prisma Client
npx prisma migrate dev # create/apply development migrations
npx prisma migrate deploy # apply committed migrations
npx prisma studio # inspect the development databasesrc/
domain/ domain entities and value objects
application/
auth/ authenticated actor types under development
ports/ repository contracts
use-cases/ application workflows
delivery/
graphql/ schema, resolvers, validation, error mapping
http/ Express server and routes
infra/
inmemory/ map-backed repository adapters
database/ Prisma client factory and repository adapters
auth/ password-hashing and token components
prisma/
schema.prisma relational model
migrations/ committed PostgreSQL migrations
tests/
domain/ domain behavior
application/ use-case behavior
delivery/ GraphQL behavior
integration/ HTTP application behavior
database/ Prisma/PostgreSQL behavior