Express Intro & Routing
Reviewed & published by Brayan K
Express is the most popular web framework for Node.js — a thin layer over the http module that gives you a clean API for defining routes, handling requests, and sending responses with far less boilerplate.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll have installed Express, written a server that responds to a request, defined routes for different HTTP methods and URLs, read route parameters and query strings, and sent back both plain text and JSON with the right status codes.
What You'll Learn in This Lesson
- How to install Express with npm and create an app
- How to start a server with app.listen()
- How to define routes for GET, POST, PUT, and DELETE
- How to read route parameters with req.params
- How to read query strings with req.query
- How to send text and JSON with res.send and res.json
1️⃣ Installing Express & Your First Server
Express isn't built into Node — it lives on npm, the package registry. From inside your project folder you install it once, and it lands in node_modules so your code can require it.
# Inside your project folder, install Express from npm.
# This downloads it into node_modules and records it in package.json.
npm install express
# (If you don't have a package.json yet, run "npm init -y" first.)Now the classic "hello server". Read every line below — it builds an app, defines one route, and starts listening. Save it as server.js and run node server.js.
// Bring in the Express library you just installed.
const express = require('express');
// Calling express() creates an application object — your whole server.
const app = express();
// Define a route: when a GET request hits "/", run this handler.
// req = the incoming request, res = the response you send back.
app.get('/', (req, res) => {
res.send('Hello'); // send plain text back to the browser
});
// Start listening for connections on port 3000.
// The callback runs once the server is up and ready.
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});2️⃣ Routing: Methods, Params & Queries
A route is the pairing of an HTTP method and a URL path with the function that handles it. Express gives you one method per verb — app.get, app.post, app.put, app.delete — and two ways to read variable data from the URL:
- • Route parameters — a segment starting with : like /users/:id is captured into req.params.id.
- • Query strings — the part after ? like /search?q=cats shows up parsed in req.query.q.
const express = require('express');
const app = express();
// One method per HTTP verb. Same path, different verb = different handler.
app.get('/users', (req, res) => res.send('List of users'));
app.post('/users', (req, res) => res.send('Create a user'));
app.put('/users/:id', (req, res) => res.send('Replace a user'));
app.delete('/users/:id', (req, res) => res.send('Delete a user'));
// Route parameters: anything after ":" is captured into req.params.
// A request to /users/42 sets req.params.id to the string "42".
app.get('/users/:id', (req, res) => {
res.send(`User ${req.params.id}`); // -> "User 42"
});
// Query strings come in through req.query.
// A request to /search?q=cats sets req.query.q to "cats".
app.get('/search', (req, res) => {
res.send(`You searched for: ${req.query.q}`);
});
app.listen(3000);Your turn. The route below is almost done — fill in the two blanks marked ___ using the 👉 hints so a request to /ping returns JSON.
const express = require('express');
const app = express();
// 🎯 YOUR TURN — add a route, then run the server.
// 1) Handle GET requests to "/ping"
app.___('/ping', (req, res) => { // 👉 the method for GET requests
// 2) Send back JSON: { pong: true }
res.___({ pong: true }); // 👉 the method that sends JSON
});
app.listen(3000);
// ✅ Expected output when you visit http://localhost:3000/ping :
// {"pong":true}3️⃣ Sending Responses
The response object res is how you reply. The three you'll reach for constantly are res.send (text or HTML), res.json (data as JSON), and res.status (the HTTP status code, chained before a body). Each route may send exactly one response.
const express = require('express');
const app = express();
// res.send() picks a sensible Content-Type for you (text or HTML).
app.get('/hello', (req, res) => {
res.send('Hello there');
});
// res.json() serialises an object to JSON and sets the JSON header.
app.get('/api/user', (req, res) => {
res.json({ id: 1, name: 'Ada', active: true });
});
// res.status(code) sets the HTTP status. Chain .json() to send a body.
// 404 = "Not Found" — perfect for a missing resource.
app.get('/api/user/:id', (req, res) => {
const id = req.params.id;
if (id !== '1') {
return res.status(404).json({ error: 'User not found' });
}
res.json({ id: 1, name: 'Ada' });
});
app.listen(3000);Mini-Challenge: A Tiny Greeting API
No blanks this time — just a brief and an outline. Write the two routes yourself, run the server, and check your output against the example in the comments. This is exactly the kind of small API that builds real fluency.
const express = require('express');
const app = express();
// 🎯 MINI-CHALLENGE: a tiny greeting API
// 1. GET / -> send the text "Welcome"
// 2. GET /greet/:name -> send JSON { message: "Hello, <name>" }
// (read the name from req.params.name)
// 3. Anything else -> you can leave it; Express 404s by default
// 4. app.listen(3000) so the server starts.
//
// ✅ Example output:
// GET / -> Welcome
// GET /greet/Sam -> {"message":"Hello, Sam"}
// your code here
app.listen(3000);Common Errors (and the fix)
- "Cannot find module 'express'" — You forgot to install it, or you're in the wrong folder. Run npm install express inside your project (the one with package.json) before running the file.
- Program exits immediately / nothing happens — You forgot app.listen(3000). Without it the server is never started, so it has nothing to do and Node exits.
- "Cannot set headers after they are sent to the client" — You sent two responses in one handler (for example res.send(...) then res.json(...)). Send exactly once, and return after sending so the rest of the handler doesn't run.
- req.params is undefined for your value — The key must match the route. /users/:id gives you req.params.id, not req.params.userId. The param name and the property name are the same word.
- A specific route never runs — Route order matters. A broad route like /users/:id declared first will shadow /users/new. Put literal, specific routes before parameterised ones.
Pro Tips
- 💡 Use res.json() for APIs: it sets the application/json header explicitly, making your intent clear to both clients and reviewers.
- 💡 Always return after sending: writing return res.status(404).json(...) prevents the dreaded "headers already sent" crash.
- 💡 Try a REST client like the browser, curl, or an extension to test POST/PUT/DELETE, which a browser address bar can't send on its own.
📋 Quick Reference — Express Basics
| Concept | Express Syntax |
|---|---|
| Create the app | const app = express() |
| Define a GET route | app.get(path, fn) |
| Read a route param | req.params.id |
| Read a query value | req.query.q |
| Send text | res.send(x) |
| Send JSON | res.json(obj) |
| Set the status code | res.status(404) |
| Start the server | app.listen(3000) |
🎉 Lesson Complete!
- ✅ Express is a thin layer over http installed with npm install express
- ✅ const app = express() creates your server; app.listen(3000) starts it
- ✅ app.get/post/put/delete define routes per HTTP method
- ✅ req.params reads route params; req.query reads query strings
- ✅ res.send, res.json, and res.status shape the response — send exactly one
- ✅ Route order matters — put specific routes before parameterised ones
Practice quiz
What does calling express() return?
- A database connection
- An application object that is your whole server
- A single route handler
- The raw http module
Answer: An application object that is your whole server. express() creates the app object you attach routes to and start with app.listen().
Which method defines a handler for GET requests to a path?
- app.route()
- app.fetch()
- app.get()
- app.handle()
Answer: app.get(). app.get(path, handler) registers a handler for GET requests to that path.
A request to /users/42 hits app.get('/users/:id', ...). What is req.params.id?
- The string "42"
- The number 42
- undefined
- An array [42]
Answer: The string "42". Route params arrive on req.params as strings, so req.params.id is "42".
Where does Express put parsed query-string values like ?q=cats?
- req.params
- req.body
- req.headers
- req.query
Answer: req.query. Query strings after the ? are parsed onto req.query, so req.query.q is "cats".
What does res.json() do that res.send() does not always do?
- It always sets the application/json header and serializes its argument to JSON
- It sends plain text only
- It closes the server
- It sets a 404 status
Answer: It always sets the application/json header and serializes its argument to JSON. res.json() always serializes to JSON and sets the JSON Content-Type explicitly.
How do you send a 404 status with a JSON error body?
- res.send(404)
- res.error(404)
- return res.status(404).json({ error: 'not found' })
- res.code(404).json(...)
Answer: return res.status(404).json({ error: 'not found' }). res.status(code) sets the HTTP status; chain .json() to send the body and return after sending.
What starts the server listening for connections?
- app.start(3000)
- app.run(3000)
- app.open(3000)
- app.listen(3000)
Answer: app.listen(3000). app.listen(3000, cb) binds the server to a port; without it Node has nothing to do and exits.
Why must /users/new be declared before /users/:id?
- Alphabetical order is required
- Express runs the first matching route, so :id would capture 'new' as the id
- Parameter routes are disabled otherwise
- It prevents a memory leak
Answer: Express runs the first matching route, so :id would capture 'new' as the id. Express matches top to bottom; a broad :id route declared first shadows the specific /users/new.
What causes 'Cannot set headers after they are sent to the client'?
- Forgetting app.listen
- Using app.get instead of app.post
- Reading req.query
- Sending two responses in one handler, e.g. res.send then res.json
Answer: Sending two responses in one handler, e.g. res.send then res.json. Each handler may send exactly one response; sending twice triggers the headers-already-sent error.
What is Express in relation to Node's http module?
- A thin framework layer on top of http with routing and helpers
- A replacement that removes http entirely
- A separate runtime like Node
- A database driver
Answer: A thin framework layer on top of http with routing and helpers. Express is a thin layer over http, adding routing, parsed params/query, and response helpers.
Continue this course
- Previous: Error Handling
- Next: Middleware — Run logic on every request with Express middleware functions
- Quick reference: Node.js cheat sheet › Express Basics
Frequently asked questions
What does Express give me that the raw Node http module doesn't?
With the built-in http module you get a single request handler and have to inspect req.method and req.url yourself, parse query strings and bodies by hand, and write boilerplate to set headers and status codes. Express turns all of that into a clean API: app.get('/path', handler) for routing by method and URL, req.params and req.query already parsed for you, helpers like res.json() and res.status(), and a middleware system for shared logic. You can still drop down to the raw request and response objects when you need to — Express is a thin layer on top of http, not a replacement for it.
Is Express the same thing as Node.js?
No. Node.js is the runtime that lets JavaScript run outside the browser and ships with core modules like http, fs, and path. Express is a third-party web framework you install from npm (npm install express) that runs on top of Node. You can build a web server with Node's http module alone, but Express makes routing and responses far less tedious, which is why it's the most popular choice.
What is the difference between res.send and res.json?
res.send() is general-purpose: it detects the type of what you pass it and sets a reasonable Content-Type — a string becomes text or HTML, a Buffer is sent as-is, and an object is actually converted to JSON. res.json() always serialises its argument to JSON and explicitly sets the application/json header, even for values like null or arrays. When you're returning data for an API, prefer res.json() so the intent and the header are unambiguous.
Does the order I declare routes in matter?
Yes. Express checks routes top to bottom and runs the first one that matches. A broad route declared early can shadow a more specific one declared later — for example, a parameter route like /users/:id placed before /users/new will capture 'new' as the id and the /users/new handler will never run. Put specific, literal routes before parameterised or catch-all ones to avoid surprises.