Chapter 12 of 18 · Node.js for Beginners — Complete Free Tutorial

Getting Started with Express

Writing routing ladders and body parsers by hand gets old fast. The Node.js for Beginners series now adds Express — the minimal framework that turns the raw http module into a readable application. This chapter covers installing it, structuring a project, sending responses, and the two pieces of built-in middleware you will use in every app.

What Express actually is

Express sits on top of node:http. It does not replace the request and response objects so much as decorate them: you still write the same handler functions, but path matching, body parsing, headers and static files come out of the box. Its entire vocabulary is small — app, route, middleware, response helpers — and the project for the rest of this series is built on it. Express 5 is the current major version; the examples here work on both 4 and 5, which is why no deprecated syntax appears.

Install and hello

mkdir todo-api && cd todo-api
npm init -y
npm pkg set type=module
npm install express
// src/server.js
import express from 'express';

const app = express();
app.use(express.json());

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Todo API', docs: '/api/todos' });
});

const port = process.env.PORT ?? 3000;
app.listen(port, () => {
  console.log(`Todo API listening on http://localhost:${port}`);
});
node src/server.js
curl -s http://localhost:3000/

Expected output:

Todo API listening on http://localhost:3000
{"message":"Welcome to the Todo API","docs":"/api/todos"}

In a CommonJS project the first line becomes const express = require('express'); and everything else stays identical — the API is the same in both module systems. Note also process.env.PORT ?? 3000 from chapter 8: platforms assign the port, your laptop defaults it.

Project shape

FileRole
package.jsonManifest: type: module, scripts, dependencies
src/server.jsEntry point — creates the app and starts listening
src/routes/*.jsRoute definitions, split by resource (added from chapter 15)
src/store.jsPersistence (chapter 16)
public/Frontend served statically (chapter 17)

Keeping listening code separate from route code is the single most valuable structural habit: tests can import the app without opening a port.

Ways to send a response

app.get('/api/status', (req, res) => {
  res.status(200).json({ ok: true });          // JSON with a chosen status
});

app.post('/api/things', (req, res) => {
  res.status(201).location('/api/things/1').end();  // created, no body
});

app.get('/legacy', (req, res) => {
  res.redirect('/api/status');                 // 302 to somewhere sane
});

Expected output: {"ok":true} at /api/status, an empty 201 with a Location header from the POST route, and a redirect from /legacy. Express also offers res.send(stringOrBuffer), which picks the content type for you — handy for HTML pages, while res.json is the default for APIs.

Two built-ins you will never skip

  • express.json() — parses JSON request bodies into req.body. Without it, req.body is undefined — the number-one Express bug for newcomers.
  • express.static('public') — serves files from a folder: HTML, CSS, images, favicons. Chapter 17 uses it to host the to-do frontend.
app.use(express.json());
app.use(express.static('public'));

Both are middleware in the plainest sense: they run for every request, do one job, and pass control on. Chapter 14 gives the full picture.

Development convenience

Restarting by hand is a tax. Node's built-in watcher covers it with zero dependencies:

"scripts": {
  "start": "node src/server.js",
  "dev": "node --watch src/server.js"
}

Run npm run dev and every save restarts the process. A classic alternative is nodemon (npm install --save-dev nodemon), which adds a few niceties such as ignoring data folders — either is fine, but keep the tool in dev dependencies.

Common mistakes

  • Missing express.json() — req.body stays undefined and validation fails mysteriously.
  • Calling app.listen at module load — importing the app in a test also binds the port; guard or split the files.
  • Registering routes after the catch-all — order decides which handler wins (chapter 13).
  • Hard-coding port 3000 in production — read process.env.PORT or the platform's bind fails.
  • Using res.send and res.json interchangeably — a raw string with a JSON shape but no content type confuses clients.

Practice checklist

  1. Scaffold the project exactly as shown and confirm the welcome JSON with curl.
  2. Add GET /api/health returning { status: 'up' }.
  3. Switch the entry file to CommonJS and prove the app still runs.
  4. Add the dev script and verify a save triggers a restart.
  5. Delete express.json(), POST some JSON, and observe undefined — then restore it.

One route is a demo; an application needs a real routing scheme. Next: Routes and Route Parameters.

Quiz: Getting Started with Express

5 questions · 70% to pass

Frequently Asked Questions

Node.js is a runtime that executes JavaScript outside the browser. It combines Google's V8 engine with APIs for files, networks, processes and streams, plus a package ecosystem (npm). You use it to build servers, command-line tools, APIs and anything that needs JavaScript running on a machine rather than in a page.

JavaScript is the language; Node.js is one environment that runs it. In the browser the language comes with a DOM, events and window APIs — in Node those are replaced by the file system, network and process modules. Syntax, functions, arrays, promises and async/await are identical, so your JavaScript knowledge transfers directly.

Browser code is sandboxed: it cannot read your files or open arbitrary ports. Node code has full machine access, which is why it is used for servers and tools but also why you never run untrusted code in it. There is no document or window in Node — the equivalents are global, process and modules.

npm is JavaScript's package manager and public registry. package.json describes your project: name, entry point, dependencies with version ranges, and scripts such as npm run dev. Installing a package copies it into node_modules and records the exact intent in package-lock.json so every machine installs identical versions.

Express is a minimal web framework for Node: a few thousand lines that give you routing, middleware, request parsing and static files on top of the raw HTTP server. It is the most common starting point for REST APIs and the backbone of this tutorial's project chapters.

Both work. CommonJS (require/module.exports) is the classic Node format; ES modules (import/export) are the standard and activated in a package.json with "type": "module". Modern projects favour ESM for browser compatibility, but you will read both in existing code, so this course teaches each.

Middleware are functions that run between the incoming request and the final response, in order: body parsing, logging, authentication, rate limits, error handling. Each receives the request, response and next function and can respond itself or pass control along — Express's pipeline model in one sentence.

A JSON file is perfect for learning CRUD: no setup, readable data, easy to reset. The moment you need concurrent writes, querying or multiple users, move to a real database — MySQL/PostgreSQL for relational data or MongoDB for documents. The storage chapter isolates persistence behind one module so swapping it later is straightforward.

A REST API exposes resources over HTTP using verbs on URLs: GET /api/todos lists, POST creates, PUT or PATCH updates, DELETE removes, and everything answers with JSON plus correct status codes (200, 201, 400, 404, 500). It is stateless — each request carries its own authentication — which makes it easy to cache, scale and consume from any frontend.

Node apps are long-running processes, not static files: deploy them to a Node-friendly host (a VPS, Render, Railway or Fly.io), keep secrets in environment variables, terminate HTTPS at a reverse proxy or platform edge, and send logs somewhere durable. The final chapter covers the full checklist including health checks and graceful shutdown.