Course outline

Express.js / Basic: request and response ownership / Lesson 1

The application, HTTP and request lifecycle

Place Express inside the Node.js HTTP lifecycle and separate application construction from owning a listening server.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain request, response and next as distinct responsibilities.
  • Trace a synthetic request without assuming the framework provides storage or authentication.

A small framework still needs application contracts

Express is a routing and middleware framework for Node.js. It coordinates handlers around an HTTP request and response, while the application supplies data access, identity, validation and domain rules. A successful route match does not mean the caller may read its object, and a JSON response does not prove a durable write occurred. Keep these guarantees explicit from the first endpoint. This course selects the Express 5.x API family and studies framework fragments without installing packages, starting a listener or contacting a configured service. Pure local functions provide separately identified execution evidence.

Construct an app without opening a socket

An Express app is a request handler object with registration and configuration methods. Creating it and attaching routes is separate from app.listen, which creates a listening HTTP server. The factory below makes that distinction visible and accepts its domain dependency explicitly. It is an illustrative CommonJS fragment, not a complete runnable server, because express and the service implementation are intentionally absent. Importing a module should not unexpectedly bind a port during tests or start jobs. Let a separate authorized entry point own listening, startup checks and eventual shutdown.

javascript Illustrative application factory
function createApp(express, catalog) {
  const app = express();
  app.get("/courses/:id", async (req, res) => {
    const course = await catalog.findPublic(req.params.id);
    if (!course) return res.status(404).json({code:"COURSE_NOT_FOUND"});
    return res.json({id:course.id, title:course.title});
  });
  return app;
}

The fragment registers a GET route and returns the app. It never calls listen, and its domain lookup remains an injected unexecuted dependency.

Every selected handler must transfer or finish ownership

A normal middleware receives req, res and next. It can inspect the request, attach request-scoped information, finish the response or transfer control. Calling next continues the applicable stack; it does not mean the response was sent. Sending a response also does not stop JavaScript execution inside the function, so use explicit return when later statements must not run. If a middleware neither ends the response nor calls next and has no remaining asynchronous completion path, the request remains pending. Treat that state as missing lifecycle ownership rather than fixing it with a longer timeout.

Keep public projection independently testable

The local function projects only public card fields from a synthetic source record. Its deterministic output does not depend on Express or HTTP. Keeping the projection separate makes an internal-only property visibly absent and lets a test check the intended public shape. This is not a complete authorization system: the caller still has to supply an eligible record under the domain policy. Never serialize an entire database entity merely because res.json can serialize it. A framework convenience cannot decide whether enrollment notes or internal review fields belong in a public response.

javascript Complete offline public-projection model
function courseCard(record) {
  return {id:record.id, title:record.title};
}
const record = {id:"c1",title:"HTTP foundations",internalNote:"lab-only"};
const card = courseCard(record);
console.assert(JSON.stringify(card) === '{"id":"c1","title":"HTTP foundations"}');
console.assert(!Object.hasOwn(card,"internalNote"));

Both assertions pass for the synthetic record. They check shape and omission only; no Express route or network request is executed.

Record the actual version and operational owner

Express 5 requires Node.js 18 or later, but that minimum does not establish which runtime line remains supported at a future deployment date. Choose and record a supported runtime independently, pin the framework and review its migration notes. CommonJS and ESM examples need matching module configuration rather than mixing require and import arbitrarily. Configuration validation, listeners, background tasks and shutdown belong to an explicit process owner. This course uses Node.js 22.23.3 only for isolated local models and makes no claim to have exercised an Express installation or production deployment.

Common errors and fixes

The request remains pending forever

Why it happens
A selected middleware neither completed the response nor transferred control.
How to fix it
Give every branch an explicit response, next call or owned asynchronous completion.
Prevent it next time
Review the handlerโ€™s completion branches with positive and rejected fixtures.

Tests unexpectedly bind a port

Why it happens
Importing the app module also called listen.
How to fix it
Separate app construction from the process entry point.
Prevent it next time
Keep startup side effects behind an explicit owner.

Private source fields appear in JSON

Why it happens
The entire internal entity was serialized as the public contract.
How to fix it
Project allowed fields after the domain eligibility check.
Prevent it next time
Test exact output keys and confidential-field omission.

Check your understanding

1. Does createApp above open a listening port or prove that catalog access works?

Show worked solution

No. It demonstrates registration and dependency injection, while the actual package and service remain unexecuted. An entry point would separately own a listener and its lifecycle. Keep that distinction in the test report: a pure projection test can prove a narrow output shape but cannot certify route matching, middleware, network handling or storage behavior.

2. Why is return res.status(404).json(...) useful inside an if branch?

Show worked solution

The response method sends the response, while return prevents later JavaScript statements in that function from executing. Without the return, later work could run and attempt a second response or an inappropriate domain call. The return does not undo earlier effects, so authorization and validation still need to precede any write.

Key takeaways

  • Express coordinates request handling; domain and process guarantees need explicit owners.
  • A local modelโ€™s evidence is narrower than an executed server integration.

Official references