Node.js & Express Basics
Build server-side web applications with Node.js and the Express framework — routes, middleware, request handling, and JSON APIs.
Learning Objectives
- → Understand Node.js's event loop and non-blocking I/O
- → Create a basic HTTP server with Node's http module
- → Build routes and middleware with Express
- → Parse request bodies and return JSON responses
- → Handle errors and 404s in Express
What is Node.js?
Node.js runs JavaScript on the server using Chrome's V8 engine. Key characteristics:
- Single-threaded with an event loop for concurrency
- Non-blocking I/O — handles many connections without threads
- npm — 2M+ packages available
node --version # v20.x.x
npm --version # 10.x.x
The Event Loop
┌─── timers (setTimeout, setInterval)
│
├─── I/O callbacks (network, file)
│
├─── idle / prepare
│
├─── poll (wait for new I/O events)
│
├─── check (setImmediate)
│
└─── close callbacks
Node handles async work by registering callbacks and continuing — never blocking.
Simple HTTP Server (no framework)
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ host: req.headers.host, url: req.url }));
});
server.listen(3000, () => console.log("Listening on :3000"));
Setting Up Express
mkdir api && cd api
npm init -y
npm install express
// server.js
const express = require("express");
const app = express();
app.use(express.json()); // parse JSON bodies
app.use(express.urlencoded({ extended: true })); // parse form bodies
app.listen(3000, () => console.log("Server on :3000"));
Routing
// GET /
app.get("/", (req, res) => {
res.json({ status: "ok", message: "CyberLearn API" });
});
// GET /scan/:host
app.get("/scan/:host", (req, res) => {
const { host } = req.params;
const { port = 80 } = req.query;
res.json({ host, port: Number(port), scanning: true });
});
// POST /scan
app.post("/scan", (req, res) => {
const { host, ports } = req.body;
if (!host) {
return res.status(400).json({ error: "host is required" });
}
// ... perform scan
res.status(202).json({ queued: true, host, ports });
});
// DELETE /scan/:id
app.delete("/scan/:id", (req, res) => {
res.json({ deleted: req.params.id });
});
Middleware
Middleware = functions that run between request and response:
// Logger middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next(); // MUST call next() or request hangs
});
// Auth middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token || token !== process.env.API_TOKEN) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
// Apply to specific route
app.get("/admin", requireAuth, (req, res) => {
res.json({ admin: true });
});
// Apply to all routes under /api
app.use("/api", requireAuth);
Request Object
req.params // URL params: /user/:id → req.params.id
req.query // Query string: ?page=2 → req.query.page
req.body // Parsed request body (needs express.json())
req.headers // All request headers
req.method // "GET", "POST", etc.
req.url // "/scan?host=10.0.0.1"
req.ip // Client IP
req.cookies // Cookies (needs cookie-parser)
Response Object
res.json({ key: "value" }) // send JSON
res.status(201).json({ id: 1 }) // set status + send JSON
res.send("Hello") // send text
res.sendFile("/path/to/file") // send file
res.redirect(301, "/new-url") // redirect
res.set("X-Custom", "value") // set header
res.cookie("session", "abc") // set cookie
Error Handling
// 404 handler (after all routes)
app.use((req, res) => {
res.status(404).json({ error: "Not found", path: req.url });
});
// Error handler (4 args — Express identifies by signature)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || "Internal server error",
});
});
// Async error forwarding
app.get("/data", async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
next(err); // forward to error handler
}
});
Mini Project – Scan API
const express = require("express");
const net = require("net");
const app = express();
app.use(express.json());
async function isOpen(host, port, timeout = 500) {
return new Promise(resolve => {
const s = net.createConnection({ host, port });
s.setTimeout(timeout);
s.on("connect", () => { s.destroy(); resolve(true); });
s.on("error", () => { s.destroy(); resolve(false); });
s.on("timeout", () => { s.destroy(); resolve(false); });
});
}
app.post("/api/scan", async (req, res, next) => {
try {
const { host, ports = [22, 80, 443] } = req.body;
if (!host) return res.status(400).json({ error: "host required" });
const results = await Promise.all(
ports.map(async p => ({ port: p, open: await isOpen(host, p) }))
);
res.json({ host, results });
} catch (err) {
next(err);
}
});
app.listen(3000, () => console.log("Scan API on :3000"));
Create a new npm project. Install Express. Write server.js with: GET / returning {status:'ok'}, GET /health returning {uptime: process.uptime()}. Start with node server.js and test with curl.
What npm command initializes a new Node.js project?
What Express method registers a GET route handler?
Add a logging middleware that prints METHOD URL IP timestamp for every request. Add a requireAuth middleware that checks for Authorization: Bearer secret123 header. Apply it to a /protected route.
What must middleware call to pass control to the next function?
How many arguments does an Express error handler take?
Implement the mini project scan API. Test with: curl -X POST http://localhost:3000/api/scan -H 'Content-Type: application/json' -d '{"host":"127.0.0.1","ports":[22,80]}'
What does next(err) do in an Express route handler?