Skip to content

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).

JavaScript
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.

The primary source

Related concepts

← All concept guides