Servers & Web Frameworks
What a web framework like Express actually does for you, versus the raw, repetitive plumbing you'd otherwise have to write by hand.
What is it?
At its core, a web server just needs to do one thing: listen for incoming network connections, read the request that arrives, and send back a response. Node.js gives you a built-in way to do exactly that with its http module — but if you use it directly, you quickly notice that every single thing is your job: figuring out which URL was requested, which HTTP method was used, parsing the body of the request yourself, handling errors so one bad request doesn't crash the whole server, and so on.
A web framework (Express is the most common one in the Node.js world) is a library that has already solved that repetitive plumbing for you. It gives you a clean way to say "when a GET request comes in for /users, run this function" and takes care of the underlying request parsing, so you can focus on what your app should actually do rather than how HTTP messages are structured.
Explain like I'm 10
Writing a server with raw Node.js is like building a house by mining your own ore to forge nails. A framework like Express is a fully stocked hardware store: the nails, screws, and pre-cut lumber already exist, so you spend your time designing the house instead of smelting metal.
Examples
Raw Node.js — doing everything yourself
const http = require("http");
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/hello") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello!");
} else {
res.writeHead(404);
res.end("Not found");
}
});
server.listen(3000);You have to manually check the method and URL, manually set status codes and headers, and manually handle every path that isn't matched.
The same server with Express
const express = require("express");
const app = express();
app.get("/hello", (req, res) => {
res.send("Hello!");
});
app.listen(3000);Express matches the method and path for you, defaults to a 200 status and sensible headers, and automatically returns a 404 for anything unmatched.
How it works
Under the hood, a framework like Express is still built on top of Node's raw http module — it hasn't replaced it, it's layered on top of it. When a request arrives, Express looks through the routes you've registered, finds the one whose method and path match, and calls your function with convenient req and res objects that already have helper methods (res.json(), res.status(), and so on) instead of the raw, low-level ones.
Why does it exist?
Nearly every backend needs the same basic scaffolding: routing requests to handlers, parsing request bodies, setting consistent headers, handling errors gracefully. Writing that from scratch for every project is repetitive and error-prone. A framework exists so thousands of developers don't each re-solve the same low-level problems, and so code across different projects looks familiar and predictable.
When to use it
Reach for a web framework for essentially any real backend project — even small ones benefit from not having to hand-roll routing and error handling. It's the default starting point unless you have a very specific reason not to use one.
When not to use it
For a tiny script that just needs to respond to one fixed request (a health-check endpoint with no other logic, for instance), pulling in a full framework can be overkill — Node's raw http module or a minimal library might be all that's needed.
Common mistakes
Thinking a framework replaces Node.js — it's built on top of it, not instead of it.
Assuming Express is the only option — other frameworks (Fastify, Koa, NestJS) solve the same problem with different trade-offs.
Not realizing that a framework still runs your own code — it organizes and simplifies, but the business logic is still yours to write.
Practice exercises
- Easy:
Write a raw Node.js
httpserver that responds with "pong" to any request. - Medium:
Rewrite that same server using Express, and add a second route that returns JSON.
- Hard:
List three specific things Express does for you automatically that you would otherwise have to write by hand in raw Node.js.
Interview questions
What problem does a web framework solve?
It handles the repetitive low-level plumbing of building a server — routing, parsing request data, response helpers, error handling — so developers can focus on application logic instead of rebuilding that scaffolding for every project.
Is Express a replacement for Node.js?
No — Express is a library layered on top of Node's built-in http module; it simplifies working with raw requests and responses but still relies on Node underneath to actually create the server and handle connections.
Name one thing you'd have to write manually with raw Node.js that a framework provides out of the box.
Routing requests to the right handler based on method and path — with raw Node you must check req.method and req.url yourself with if-statements, while a framework lets you declare app.get("/path", handler) directly.
What does `app.listen(3000)` in Express correspond to under the hood in raw Node.js?
It ultimately calls the same underlying mechanism as http.createServer(...).listen(3000) — Express builds its app object on top of Node's http server rather than replacing it.
Why does raw `http.createServer` funnel every request through a single callback function, and what problem does that create as an app grows?
Node's http module is deliberately low-level: it hands you one entry point and expects you to branch on method and URL yourself, so as more endpoints are added, that one function fills up with more and more if-statements, becoming harder to read and maintain.
In raw Node.js, what happens to a request for a URL you never explicitly checked with an if-statement?
It falls into whatever else branch (or lack of one) you wrote — if you didn't handle the case, the response could hang or default to whatever your final fallback logic does, since Node itself provides no automatic "not found" behavior.
Compare `res.send("Hello!")` in Express to what you'd write manually with raw `http` to send the same response.
With raw http you'd need to call res.writeHead(200, { "Content-Type": "text/plain" }) and then res.end("Hello!") yourself; res.send() picks a sensible status code and content type automatically and ends the response for you.
Why is it wrong to think of Express and Node.js as two competing ways to build a server?
They aren't alternatives — Express is built using Node's own http module internally, so using Express still means using Node; you're choosing whether to work with Node's low-level API directly or through Express's higher-level convenience layer.
Where do the `req` and `res` objects in an Express handler actually come from?
They originate from Node's underlying http module's request and response objects; Express wraps and extends them with convenience methods like res.json() and res.status() rather than replacing them with something unrelated.
Name two other Node.js web frameworks besides Express, and what does having multiple options tell you about framework choice?
Fastify and Koa (also NestJS) are common alternatives; the existence of several frameworks shows there's no single "correct" one — each makes different trade-offs (performance, structure, minimalism) while solving the same underlying routing-and-request-handling problem.
A raw Node http server needs a 10th route added by hand. What specifically gets harder as each new `if` branch is added?
The single handler function grows linearly with every new route, making it harder to see which condition matches which URL, easier to introduce bugs in the ordering of checks, and harder for multiple developers to work on different routes without touching the same function.
Why does Express automatically respond with 404 to an unmatched route, when raw `http.createServer` does not do this automatically?
Express includes default fallback behavior as part of its routing system, since "no route matched" is such a common case that solving it once, framework-wide, saves every project from writing that same catch-all logic by hand.
What does it mean that "a framework still runs your own code"? What decisions does it not make for you?
A framework handles structural concerns — matching requests to handlers, formatting responses — but it has no idea what your application should actually do; the business logic inside each route handler (what to fetch, what to compute, what rules to apply) is still entirely up to you.
When would using raw Node's http module instead of a framework actually make sense?
For something extremely small and fixed, like a single health-check endpoint with no other logic — pulling in a full framework's routing and middleware machinery for one static response is more overhead than the raw module's few lines of code.
If you removed the `else` branch from a raw Node http server that checks `req.method === "GET" && req.url === "/hello"`, what would happen to an unmatched request?
No response would ever be written or ended for that request, so the client would hang waiting indefinitely — Node doesn't send any default response on its own, unlike a framework's built-in 404 fallback.
Why do frameworks provide convenience methods like `res.json()` instead of just handing you Node's raw response object?
Sending JSON manually would require setting the Content-Type header, calling JSON.stringify, and ending the response in the right order every time; res.json() bundles that repeated sequence into one call so you can't forget a step.
What's a concrete cost of pulling in a full framework for a project that only ever needs one fixed health-check endpoint?
You add a dependency (and its own transitive dependencies) to install, update, and trust, plus a small amount of startup overhead and unused features, for a case simple enough that a few lines of raw Node code would do the same job.
Why is raw Node http described as "mining your own ore to forge nails" compared to a framework?
It's illustrating that with raw Node, even the most basic, universally-needed pieces (routing, header handling, error fallbacks) have to be built from scratch every time, whereas a framework supplies those already-solved basics so you can focus on the actual application.
If you swapped Express for Fastify in a project, what would change, and what would stay solved either way?
The specific API for defining routes and middleware would change since each framework has its own syntax and conventions, but the underlying problem — routing requests to handlers without hand-rolling the plumbing — would remain solved by whichever framework you chose.
What's the risk of a team writing its own ad hoc routing and parsing logic instead of adopting an existing, widely used framework?
They'd be re-solving problems (routing, error handling, header parsing) that thousands of other developers have already solved and battle-tested, spending time on infrastructure instead of features, and likely introducing edge-case bugs an established framework has long since fixed.