Web & Backend
Node.js and Express
Node.js runs JavaScript outside a browser, on Chrome's V8 engine, with an event loop and non-blocking I/O so a single process can handle many concurrent connections without a thread per request. Express is a thin routing and middleware layer built on Node's built-in http module — it doesn't replace Node, it just removes the boilerplate of parsing routes and chaining request handling by hand.
Why it matters
- It lets a team use one language across the frontend and backend
- A React frontend and a Node backend share JavaScript or TypeScript, which reduces context-switching and lets some code (validation logic, types) be shared directly.
- Non-blocking I/O suits typical API workloads well
- Most backend requests spend most of their time waiting on a database or another network call, which is exactly the pattern Node's event loop is built to handle efficiently on one thread.
- npm is the largest package ecosystem in the industry
- Nearly any common task — parsing a file format, calling a third-party API, validating input — already has a well-used package, for better and for worse.
- Express is the shared baseline most other Node frameworks are explained against
- Fastify, NestJS, and Koa each make different trade-offs, and understanding Express first makes those trade-offs concrete instead of abstract.
The event loop, briefly
JavaScript in Node runs on a single thread, but I/O operations — reading a file, querying a database, calling another server — are handed off and run in the background, with a callback (or an awaited promise) picking up the result later. This is why Node handles many concurrent, I/O-bound requests well on one thread: while one request is waiting on a database, the thread is free to work on another. It's also why one long, CPU-heavy synchronous operation blocks every other request until it finishes — there's no second thread waiting to pick up the slack.
A minimal Express server
Express wraps Node's http module with routing (matching a method and path to a handler) and middleware (functions that run before or around a request, for things like parsing JSON bodies, logging, or authentication).
const express = require("express");
const app = express();
app.use(express.json());
app.get("/orders/:id", async (req, res) => {
const order = await db.orders.findById(req.params.id);
if (!order) return res.status(404).json({ error: "not found" });
res.json(order);
});
app.listen(3000);Middleware, the core idea
A middleware function sits in the chain between the incoming request and the final route handler, and can inspect or modify the request, short-circuit it with a response, or pass control along with next(). Order matters: middleware registered before a route applies to it, and an error handler defined before the routes it's meant to catch will never actually see their errors.
Mistakes people make here
- Running CPU-heavy synchronous work on the main thread
- A big loop, synchronous cryptography, or heavy JSON parsing done inline blocks the single JavaScript thread, which means every other request the server is handling stalls until that one operation finishes.
- Not catching errors in async route handlers
- An unhandled rejection inside an async function used as a route handler can crash the process or leave a request hanging with no response, unless it's wrapped in a try/catch or handled by middleware built for it.
- Treating npm packages as free
- Every dependency is code you didn't write, running with the same privileges as your own — an unmaintained or compromised package is a real, recurring source of security incidents, not a theoretical risk.
- Registering middleware in the wrong order
- Express applies middleware in the order it's registered — an authentication check placed after the routes it's supposed to protect, or an error handler placed before the routes that raise errors, simply never does its job.
Strengths and trade-offs
Where it is strong
- One language, JavaScript or TypeScript, across both the frontend and the backend.
- Non-blocking I/O handles many concurrent, I/O-bound connections efficiently on a single thread.
- An enormous package ecosystem covers most common tasks without writing them from scratch.
- Express's minimalism keeps the request-handling flow visible and easy to reason about, which makes it a good first framework to learn on.
The trade-offs
- A single-threaded event loop means one long CPU-bound task stalls every other request, unless the work is offloaded to worker threads or a separate service.
- Asynchronous code has a real learning curve, and unhandled promise rejections are a common, easy-to-miss source of bugs.
- Express provides almost no project structure on its own, so larger applications need conventions the team has to impose and maintain itself.
- The scale of npm's ecosystem is also a large dependency-security surface that has to be actively managed, not ignored.
Who needs this
A practical starting point for anyone building a JavaScript or TypeScript backend; the underlying concepts transfer directly to more structured Node frameworks and to Next.js's server-side code.
Questions about node.js and express
- Is Node.js really single-threaded?
- JavaScript execution is single-threaded, but I/O operations are handed off to the underlying system (via a library called libuv) and run outside that single thread, which is what lets Node handle many concurrent I/O-bound requests efficiently despite the single-threaded execution model.
- Is Express still worth learning with newer frameworks around?
- Yes — it's still widely used in production, and its minimal, unopinionated design makes the fundamentals of routing and middleware clear in a way that's harder to see in a more structured framework that hides them.
- Can Node.js handle CPU-heavy work well?
- Not on the main thread — a long computation blocks every other request. For real CPU-bound work, Node offers worker_threads to run it on a separate thread, or teams offload it to a separate service written for that purpose.
- Do I need Express to use Node.js?
- No, Node's built-in http module can handle requests on its own; Express just removes the boilerplate of routing, parsing, and chaining handlers that you'd otherwise write by hand.