01

One Request, One Complete System

Build: ParcelFlow accepts one tenant-scoped order and returns a delivery promise from one Bun process.

The enterprise problem and today’s slice

Enterprise problem: A merchant cannot promise delivery when an order disappears between validation, payment, and inventory, and a tenant leak would turn an operational mistake into a customer trust failure.

Whole-course context: This is the first slice of ParcelFlow, the recurring order-to-delivery system; the course starts with one complete process so later service boundaries must be earned by evidence rather than assumed.

Today’s slice: Build one POST /orders request with Bun.serve, explicit validation, an in-memory repository, and a fake payment adapter; real payment networks and durable storage remain outside the boundary.

End-of-day evidence: A red-then-green Bun test and one HTTP request show a valid tenant receives an order identifier while malformed input receives a stable rejection without creating state.

Still unsolved: A process restart loses every order, concurrent reservations can oversell inventory, and no production authentication, database, message broker, or service extraction exists yet.

The thesis is simple: begin with the smallest complete system that can prove a customer outcome, then preserve seams inside it. The smallest complete model is request, application decision, and observable response.

Customer use cases

When the customer job is vague, implementation can pass unit tests while still failing the merchant; use cases bind code to visible outcomes.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D01-UC-01Tenant checkout clientSubmit a valid order for an available parcel itemHTTP 201 contains a tenant-scoped order ID and accepted statusA malformed item or missing tenant header returns HTTP 400 and no order is stored
D01-UC-02ParcelFlow developerProve one request crosses validation, payment, inventory, and persistenceA deterministic end-to-end test observes one stored order and one fake payment authorizationA declined fake payment returns HTTP 402 and inventory remains unchanged

Actor-centred user stories

Without observable stories, a team can confuse internal function calls with delivered value and miss negative-path regressions.

Story IDUse case IDsUser storyObservable acceptance conditions
D01-US-01D01-UC-01As a tenant checkout client, I want to submit one valid order, so that I can show the buyer a delivery promiseResponse is 201, body contains an order ID and accepted status, and a missing tenant or invalid quantity returns 400 with no stored record
D01-US-02D01-UC-02As a ParcelFlow developer, I want a tracer-bullet test through the whole process, so that future refactors preserve customer behaviourThe fake payment records one authorization, the repository contains one matching tenant order, and a configured decline produces 402 without a reservation

End-to-end product flows

If a flow starts inside a service method, customer-triggered failures vanish from the design and the response contract becomes accidental.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D01-FLOW-01D01-UC-01, D01-UC-02HappyCheckout sends POST /orders with tenant header and one valid line1. Parse JSON.<br>2. Validate tenant and quantity.<br>3. Ask fake payment to authorize.<br>4. Reserve in-memory stock.<br>5. Save order.<br>6. Return 201.Response body, captured payment call, inventory decrement, and repository record share tenant and order IDs
D01-FLOW-02D01-UC-01, D01-UC-02DeniedCheckout omits tenant, sends invalid quantity, or uses the fake decline token1. Parse request.<br>2. Reject invalid identity or shape with 400, or reject decline with 402.<br>3. Do not reserve or save.<br>4. Read repository and fake-call log.Stable error code plus an empty repository and unchanged stock provide negative evidence

System design derived from the flows

When one process is treated as one undifferentiated module, changing HTTP parsing can silently alter payment or inventory behaviour.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D01-UC-01POST /orders on Bun.serveHTTP adapter delegates to order application; fake payment and inventory repository are replaceable adaptersIn-memory order map owned by the order moduleHTTP 400/402 with no inserted order and no stock decrement
D01-UC-02bun test calling the same app fetch handlerTest client, order application, fake payment adapter, and in-memory repositoriesTest-owned in-memory maps and captured adapter callsAssertion shows mismatched response, call count, order state, or inventory state

Data model and ownership

If ownership is postponed because storage is temporary, later persistence can encode the wrong invariants and leak one tenant’s order into another tenant’s query.

Generated-application database: Not created in this slice — the order module owns intentionally disposable in-memory records while the request contract is proved; Day 04 replaces them with durable PostgreSQL state.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
OrderIn-memory Map owned by order moduleorder_id UUIDpayment_authorization_id opaque fake-adapter referencetenant_id from validated headerQuantity is positive; status starts accepted; all reads include tenantCreated after authorization and reservation; disappears on restart; test teardown clears itD01-UC-01, D01-UC-02
InventoryItemIn-memory Map owned by inventory adapterskuNone — item is a root record in this temporary storetenant_id is part of lookup keyAvailable count never becomes negative in this single-request modelSeeded per test, decremented on acceptance, reset at teardown or restartD01-UC-01, D01-UC-02

Bun’s minimal runtime model

When a new runtime is presented as a catalogue of speed claims, developers miss the operational question: which executable owns request handling, tests, dependencies, and compatibility risk?

The general rule is to adopt the smallest runtime surface that proves the workload. In the simple ParcelFlow example, the bun executable runs TypeScript directly, Bun.serve exposes a Fetch-compatible handler, and bun test drives that same handler. In a realistic repository, the package manager and test runner may also be Bun while CI separately runs the TypeScript compiler. The failure mode is assuming “Node-compatible” means every package and edge behaviour is identical. The decision rule is: keep a compatibility test for each important dependency and choose Bun only when measured startup, throughput, or tool consolidation is worth that test burden.

Bun’s official documentation describes the runtime, package manager, bundler, and test runner as one toolkit, while also calling Node compatibility an ongoing effort. The server API accepts a fetch(Request) function, so the domain does not need to know about sockets or framework objects. See Bun overview, HTTP server, and Node.js compatibility.

TDD tracer bullet and request validation

If the first test exercises only a helper, the application can remain unassembled and the first real HTTP request can fail at the seams.

Test-driven development (TDD) means writing a failing behavioural test, making the smallest implementation pass, then improving structure without changing behaviour. A tracer bullet is a thin path through every essential layer. Start with the request-level test, not with repository classes:

import { expect, test } from "bun:test";
import { createApp } from "./app";
import { memoryInventory, memoryOrders } from "./memory";

test("accepts one tenant order end to end", async () => {
  const orders = memoryOrders();
  const inventory = memoryInventory({ "tenant-a:parcel-small": 3 });
  const app = createApp({
    orders,
    inventory,
    payment: { authorize: async () => ({ id: "pay_fake_01" }) },
  });

  const response = await app.fetch(new Request("http://local/orders", {
    method: "POST",
    headers: { "content-type": "application/json", "x-tenant-id": "tenant-a" },
    body: JSON.stringify({ sku: "parcel-small", quantity: 1 }),
  }));

  expect(response.status).toBe(201);
  expect(orders.allFor("tenant-a")).toHaveLength(1);
});

The simple validation rule rejects missing identity, unknown fields, invalid JSON, and non-positive quantity before side effects. A realistic request also has size limits, normalized SKU syntax, a request ID, and a content-type check. The failure mode is validating after payment or inventory mutation. The decision rule is: parsing and validation must finish before the first irreversible adapter call.

One-process composition and lifecycle

When startup constructs dependencies in route handlers, every request can get fresh state and shutdown cannot release resources predictably.

The general rule is to create long-lived adapters once, compose the application once, and make the server lifecycle explicit. For ParcelFlow, repositories and the fake payment adapter are created before Bun.serve; the handler closes over the composed app. A realistic service also installs readiness, graceful shutdown, structured logging, and connection cleanup. The failure mode is module-level hidden state that survives tests or differs between workers. The decision rule is: startup owns construction, request code owns use, and shutdown owns release.

const deps = {
  orders: memoryOrders(),
  inventory: memoryInventory({ "tenant-a:parcel-small": 3 }),
  payment: fakePayment(),
};
const app = createApp(deps);

const server = Bun.serve({
  port: 3000,
  fetch: app.fetch,
});

process.on("SIGTERM", () => server.stop());

The Bun test runner documentation covers its Jest-compatible API and TypeScript support. These snippets are designed to be runnable in a small project, but this lesson does not claim they were executed here.

bun test
bun run src/server.ts
curl -i -X POST http://localhost:3000/orders \
  -H 'content-type: application/json' \
  -H 'x-tenant-id: tenant-a' \
  --data '{"sku":"parcel-small","quantity":1}'

Key takeaways

Without one end-to-end slice, architecture discussions optimize imaginary boundaries instead of customer evidence.

  • Start ParcelFlow as a modular monolith: one deployment, explicit modules, replaceable adapters.
  • Validate tenant and request shape before payment, inventory, or persistence side effects.
  • Use a fake payment adapter only; a real processor is deliberately outside this course.
  • Let the request-level TDD tracer bullet prove response and owned state together.
  • Treat Bun compatibility and type checking as evidence obligations, not assumptions.

Checklist

If any proof remains implicit, the next refactor can break the customer path without a useful signal.

  • [ ] Write the failing request test before the handler implementation.
  • [ ] Confirm the valid path returns 201 and stores exactly one tenant order.
  • [ ] Confirm malformed input and fake decline create no order or reservation.
  • [ ] Keep HTTP, application, payment, inventory, and repository responsibilities named.
  • [ ] Record that restart durability, concurrency, and real payments are still deferred.

Sources

When runtime facts lack primary citations, rapidly changing compatibility and API details can turn a useful lesson into stale guidance.