Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Node Inventory API

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.

Architecture

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.ts selects 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.

HTTP delivery

Express listens on http://localhost:3000 by default. PORT changes the listening port without changing the route structure.

MethodURLPreview keyPurpose
GET/healthNoProcess liveness. Returns 200 with {"status":"ok"} without querying PostgreSQL.
GET/readyNoDatabase readiness. Runs SELECT 1 through Prisma and returns 200 with {"status":"ready"} or 503 with {"status":"not_ready"}.
GET or POST/graphqlYesGraphQL Yoga interface. POST executes operations; HTML GET requests open Yoga's browser interface.
GET/YesReturns the current Hello World! application response.
GET/lifetimeYesDemonstrates 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/graphql

GraphQL delivery

GraphQL 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, and CONFLICT errors;
  • a stable extensions.issues[] error shape;
  • masking for unexpected internal failures;
  • integer-cent conversion into the domain Money value 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:

mutationCreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
idorganizationIdsupplierIdskulabelunitpurchasePriceCentisActive
}
}
{
"input": {
"organizationId": "01JZQ4QAZ4JZX2N1EHMEKYP2YJ",
"supplierId": "01JZQ4R2D96B5YD3YJ9HXXE89P",
"sku": "SKU993",
"label": "T-shirt",
"unit": "PIECE",
"purchasePriceCent": 2500,
"isActive": true
}
}

PostgreSQL persistence

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.

Testing

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 test

Database tests with Docker Desktop or a Docker-compatible CI runner:

npm run test:database

Database tests with local Colima:

npm run test:database:colima

Testcontainers starts postgres:17-alpine, applies migrations with prisma migrate deploy, provides the generated connection URL to Vitest, and removes the container after the suite.

Local development

Install dependencies:

npm install

Create the local environment file:

cp .env-sample .env

Set 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 generate

Start the API:

npm run dev

The server listens on http://localhost:3000; GraphQL Yoga is mounted at /graphql.

Local container verification

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 api

Verify 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/graphql

Set HTTP_PORT when port 3000 is already in use:

HTTP_PORT=32000 docker compose up --detach --wait api

Stop the environment while retaining its database volume:

docker compose down

Remove the environment and its local database data:

docker compose down --volumes

Commands

npm 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 database

Project structure

src/
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

About

Domain-first Node.js inventory API practice project with TypeScript, tests, GraphQL, Prisma, and PostgreSQL

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages