Node.js and Express course menu
Express.js Tutorial: Your First Express Server
Get started with Express 5: installing it, creating an app, defining GET and POST routes, sending text and JSON, reading route and query parameters and JSON bodies, the 404 response, and running the server with node --watch.
On this page
Express is the most widely used web framework for Node.js. It keeps the speed and flexibility of Node's http module while removing the repetitive work: routing, parsing request bodies, sending JSON, and organising code with middleware. The examples in this course use Express 5 (version 5.3.0 when this was written).
Install#
In a project folder with a package.json set to "type": "module":
npm install expressHello, Express#
import express from "express";
const app = express();
const PORT = 3000;
app.get("/", (req, res) => {
res.send("Welcome to the ShopKart API");
});
app.get("/api/health", (req, res) => {
res.json({ status: "ok", service: "shopkart-api" });
});
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));node app.jsServer running at http://localhost:3000curl -s localhost:3000/
curl -s localhost:3000/api/healthWelcome to the ShopKart API
{"status":"ok","service":"shopkart-api"}Compared with the plain http server:
app.get(path, handler)registers a route for one method and path; Express matches requests for you.res.send()sends text (or HTML), setsContent-Typeand ends the response.res.json()converts data to JSON and setsContent-Type: application/json.- Requests that match no route get an automatic 404 response.
During development, run node --watch app.js so the server restarts whenever you save.
Route parameters and query strings#
import express from "express";
const app = express();
const products = [
{ id: 1, name: "Notebook", category: "stationery", price: 60 },
{ id: 2, name: "Gel pen", category: "stationery", price: 25 },
{ id: 3, name: "Backpack", category: "bags", price: 1299 },
];
// /api/products?category=stationery&maxPrice=50
app.get("/api/products", (req, res) => {
const { category, maxPrice } = req.query;
let result = products;
if (category) result = result.filter((p) => p.category === category);
if (maxPrice) result = result.filter((p) => p.price <= Number(maxPrice));
res.json(result);
});
// /api/products/3
app.get("/api/products/:id", (req, res) => {
const product = products.find((p) => p.id === Number(req.params.id));
if (!product) return res.status(404).json({ error: "Product not found" });
res.json(product);
});
app.listen(3000);curl -s "localhost:3000/api/products?category=stationery"
curl -s "localhost:3000/api/products?category=stationery&maxPrice=50"
curl -s localhost:3000/api/products/3
curl -s localhost:3000/api/products/42[{"id":1,"name":"Notebook","category":"stationery","price":60},{"id":2,"name":"Gel pen","category":"stationery","price":25}]
[{"id":2,"name":"Gel pen","category":"stationery","price":25}]
{"id":3,"name":"Backpack","category":"bags","price":1299}
{"error":"Product not found"}req.paramsholds the named parts of the path (:id), always as strings.req.queryholds the query string as an object of strings.res.status(404).json(...)sets the status code before sending; chainable methods keep it on one line.
Receiving JSON#
Express does not read request bodies unless you ask it to. express.json() is middleware that parses JSON bodies into req.body:
import express from "express";
const app = express();
app.use(express.json());
const messages = [];
app.post("/api/messages", (req, res) => {
const { name, text } = req.body;
if (!name || !text) {
return res.status(400).json({ error: "name and text are required" });
}
const message = { id: messages.length + 1, name, text, createdAt: "2026-10-25" };
messages.push(message);
res.status(201).json(message);
});
app.get("/api/messages", (req, res) => res.json(messages));
app.listen(3000);curl -s -X POST localhost:3000/api/messages -H "Content-Type: application/json" -d '{"name":"Asha","text":"Is the backpack waterproof?"}'
curl -s -X POST localhost:3000/api/messages -H "Content-Type: application/json" -d '{"name":"Ravi"}'
curl -s localhost:3000/api/messages{"id":1,"name":"Asha","text":"Is the backpack waterproof?","createdAt":"2026-10-25"}
{"error":"name and text are required"}
[{"id":1,"name":"Asha","text":"Is the backpack waterproof?","createdAt":"2026-10-25"}](A fixed createdAt keeps the example's output repeatable; a real app would use new Date().toISOString().)
201 Created is the right status for a successfully created resource. The data lives in memory, so it disappears when the server restarts; the database lesson fixes that.
What happens with unknown routes#
curl -s localhost:3000/api/unknown<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /api/unknown</pre>
</body>
</html>Express's default 404 response is a small HTML page. APIs usually replace it with a JSON response, as the error handling lesson shows.
Common mistakes#
Practice#
Practice exercise
Build an Express app with GET /api/convert?km=5 that returns { "km": 5, "miles": 3.11 } (1 km = 0.621371 miles, rounded to 2 decimals), and a 400 response with an error message when km is missing or not a number.
Show hint
const km = Number(req.query.km); then check req.query.km === undefined || Number.isNaN(km).
Show solution
import express from "express";
const app = express();
app.get("/api/convert", (req, res) => {
const km = Number(req.query.km);
if (req.query.km === undefined || Number.isNaN(km)) {
return res.status(400).json({ error: "Provide km as a number, e.g. ?km=5" });
}
res.json({ km, miles: Math.round(km * 0.621371 * 100) / 100 });
});
app.listen(3000);curl -s "localhost:3000/api/convert?km=5"
curl -s "localhost:3000/api/convert?km=42.195"
curl -s "localhost:3000/api/convert?km=far"{"km":5,"miles":3.11}
{"km":42.195,"miles":26.22}
{"error":"Provide km as a number, e.g. ?km=5"}Key takeaway#
Next: Routing and Route Parameters.
Finished this lesson?