Skip to content

Express.js Basics

Before building real routes and APIs, you need a working mental model of the pieces Express is made of. This lesson walks through the app object, routes, middleware, and how a request flows through an Express application from start to finish.

The Building Blocks of an Express App

Every Express application starts with a single object, app, created by calling express(). That object exposes methods to register routes (app.get, app.post, ...), register middleware (app.use), configure settings (app.set), and start the server (app.listen).

When a request arrives, Express runs it through a pipeline: matching middleware and route handlers execute in the order they were registered, each one able to modify the request, send a response, or call next() to pass control forward.

const express = require('express');
const app = express();

app.use(express.json());

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000);

express.json() is middleware that runs on every request, and app.get('/health', ...) is a route that only runs for GET /health requests.

The Request-Response Cycle

Request in
   -> middleware 1 (next())
   -> middleware 2 (next())
   -> matching route handler (res.send() / res.json())
Response out
  • Middleware and routes are checked in the exact order they were registered.
  • A handler either ends the cycle by sending a response, or calls next() to continue.
  • If nothing matches and nothing responds, Express eventually returns a 404.
  • Errors thrown or passed to next(err) skip straight to error-handling middleware.

Express Basics Cheatsheet

Common building blocks you will use in almost every Express file.

Piece Example Description
App instance const app = express(); The core application object
Setting app.set('view engine', 'ejs') Configures app-wide options
Route app.get('/users', handler) Registers a GET route
Middleware app.use(cors()) Runs for every incoming request
Router app.use('/api', router) Mounts a group of routes at a path
Start app.listen(3000) Starts the HTTP server

The App Object

The app object is created once, usually at the top of your entry file, and is the anchor for everything else: routes, middleware, template engine settings, and locals shared across requests.

const express = require('express');
const app = express();

app.set('trust proxy', 1);
app.set('view engine', 'ejs');

module.exports = app;

Splitting app into its own module (as shown above) makes it easy to import into test files without also calling listen().

Routes vs Middleware

A route is middleware scoped to a specific HTTP method and path. Plain middleware, registered with app.use(), runs for every request (or every request under a path prefix) regardless of method.

  • app.use(fn) — runs for all methods and all paths.
  • app.use('/admin', fn) — runs for all methods, only under /admin.
  • app.get('/users', fn) — runs only for GET /users.
  • Both middleware and routes receive (req, res, next).

Common Mistakes

  • Forgetting to call app.listen(), the app never starts accepting connections.
  • Registering routes before required middleware like express.json(), so req.body is undefined.
  • Not returning or calling next() after sending a response, which can cause "headers already sent" errors.
  • Treating app.use() and app.get() as interchangeable when method-specific behavior is required.

Key Takeaways

  • Every Express app is built around one app object created by express().
  • Requests flow through middleware and routes in registration order.
  • Routes are middleware scoped to a method and path; general middleware runs for everything.
  • Exporting app separately from listen() makes testing much easier.

Pro Tip

Keep your entry file (server.js) tiny: create the app in app.js, export it, and only call app.listen() from server.js. This lets test files import app without starting a real network listener.