Build an Express 5 permit API serving two reviewers with nonoverlapping case sets. Reviewer 47 can read case 62 but reviewer 81 cannot. The service accepts a bounded JSON approval request, verifies the caller and case permission, and commits one logical approval under a stable operation ID. A paged search returns at most 47 summaries and cancels downstream read work when the client disconnects. An unexpected async rejection must produce one controlled error response when headers are still available. During deployment, the instance reports not ready, stops admitting work, lets short requests finish, and closes repository resources under a 29-second deadline. A response lost after approval commit is recoverable by replaying the same operation ID.
Project: Express permit API lifecycle
Build contract
- Register body limits, authentication, private routes, unmatched-route handling, and error middleware in dependency order.
- Authorize the exact case in a server service; classify validation, forbidden, missing, conflict, and unexpected failures.
- Reject oversized search work, bound output to 47 rows, and cancel supported reads without claiming write rollback.
- Track readiness and drain state, close the HTTP listener once, and release resources within the deployment budget.
Implementation checkpoint
function mayApplyApproval(operationId, committedIds) {
return !committedIds.has(operationId);
}
const committedIds = new Set(["permit-62-reviewer-47-command-81"]);
console.log(mayApplyApproval("permit-62-reviewer-47-command-81", committedIds));
// Output: falseCost and boundaries
Authentication and case permission add database work to each private route. That cost belongs before the protected effect, even when the HTTP handler is small. A body limit caps parser memory; a 47-row page caps response memory at O(page size) instead of O(all cases). A large synchronous sort still blocks unrelated JavaScript callbacks, so indexed database order or offloaded jobs may be required. Canceling a read can save downstream work, but the database may already have finished and an approval may already be committed. Drain time keeps old capacity alive during rollout and must fit inside the platform's termination budget. Operation deduplication uses O(m) storage for m retained commands and provides the recovery path for ambiguous lost responses.
Failure drill
Move authentication below the private router in a negative test, observe the failed prerequisite, and restore the correct order. Submit malformed and oversized bodies, a valid session for a forbidden case, and an unknown route. Inject a rejected promise before headers and verify one safe 500. Start a slow read, disconnect the client, and inspect downstream cancellation. Commit an approval, drop its response, and replay the same operation ID: one status transition and notification must remain. Signal termination during a short read and a hanging read. Check readiness, new request admission, repository close order, and the hard deadline behind the actual proxy.
Acceptance checks
- No private route or repository effect accepts a reviewer without exact case permission.
- Every request ends in one response or a documented closed-stream outcome; errors disclose no private trace.
- One costly request cannot starve small requests or exceed the declared memory and output bounds.
- Shutdown changes readiness, drains bounded work, closes resources, and permits safe replay of a lost write response.
Common Mistakes
- Registering a prerequisite after the route it should protect.
- Launching a critical promise without awaiting it.
- Treating client abort as database rollback.
- Exiting immediately on a termination signal.
Related lessons
Express Request and Process Boundaries; Express Middleware Order and Request Identity; Express 5 Async Errors and Response Contracts; Node Request Budgets, Abort, and Event Loop Fairness; Node HTTP Drain, Readiness, and Graceful Shutdown.
