Table of Contents
Introduction to Express.js
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It simplifies the process of building web servers and APIs.
Installing Express.js
# Initialize a new project
npm init -y
# Install Express.js
npm install express
# Install development dependencies
npm install --save-dev nodemon
Your First Express Application
// app.js
const express = require("express");
const app = express();
const PORT = 3000;
// Basic route
app.get("/", (req, res) => {
res.send("Hello, Express.js!");
});
// JSON response
app.get("/api/info", (req, res) => {
res.json({
message: "Welcome to Express.js API",
version: "1.0.0",
timestamp: new Date().toISOString()
});
});
// Start server
app.listen(PORT, () => {
console.log(`Express server running on http://localhost:${PORT}`);
});
Middleware in Express
Middleware functions execute during the request-response cycle:
const express = require("express");
const app = express();
// Built-in middleware for parsing JSON
app.use(express.json());
// Built-in middleware for parsing URL-encoded data
app.use(express.urlencoded({ extended: true }));
// Custom logging middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next(); // Pass control to next middleware
});
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
// Simulate token validation
if (token === "Bearer valid-token") {
req.user = { id: 1, name: "John Doe" };
next();
} else {
res.status(401).json({ error: "Invalid token" });
}
};
// Protected route
app.get("/api/profile", authenticate, (req, res) => {
res.json({
message: "Profile data",
user: req.user
});
});
app.listen(3000);
Routing in Express
const express = require("express");
const app = express();
app.use(express.json());
// Route parameters
app.get("/users/:id", (req, res) => {
const userId = req.params.id;
res.json({
message: `User profile for ID: ${userId}`,
userId: userId
});
});
// Multiple parameters
app.get("/users/:userId/posts/:postId", (req, res) => {
const { userId, postId } = req.params;
res.json({
message: `Post ${postId} by user ${userId}`,
userId,
postId
});
});
// Query parameters
app.get("/search", (req, res) => {
const { q, limit = 10, page = 1 } = req.query;
res.json({
query: q,
limit: parseInt(limit),
page: parseInt(page),
results: []
});
});
// Route with optional parameters
app.get("/products/:category/:id?", (req, res) => {
const { category, id } = req.params;
if (id) {
res.json({ message: `Product ${id} in ${category}` });
} else {
res.json({ message: `All products in ${category}` });
}
});
app.listen(3000);
Building a RESTful API
const express = require("express");
const app = express();
app.use(express.json());
// In-memory data store
let books = [
{ id: 1, title: "Node.js Guide", author: "John Doe", year: 2023 },
{ id: 2, title: "Express.js Handbook", author: "Jane Smith", year: 2023 }
];
let nextId = 3;
// GET all books
app.get("/api/books", (req, res) => {
res.json({ books });
});
// GET single book
app.get("/api/books/:id", (req, res) => {
const id = parseInt(req.params.id);
const book = books.find(b => b.id === id);
if (book) {
res.json({ book });
} else {
res.status(404).json({ error: "Book not found" });
}
});
// POST new book
app.post("/api/books", (req, res) => {
const { title, author, year } = req.body;
// Validation
if (!title || !author || !year) {
return res.status(400).json({
error: "Title, author, and year are required"
});
}
const newBook = {
id: nextId++,
title,
author,
year: parseInt(year)
};
books.push(newBook);
res.status(201).json({ book: newBook });
});
// PUT update book
app.put("/api/books/:id", (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: "Book not found" });
}
const { title, author, year } = req.body;
if (title) books[bookIndex].title = title;
if (author) books[bookIndex].author = author;
if (year) books[bookIndex].year = parseInt(year);
res.json({ book: books[bookIndex] });
});
// DELETE book
app.delete("/api/books/:id", (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: "Book not found" });
}
const deletedBook = books.splice(bookIndex, 1)[0];
res.json({ message: "Book deleted", book: deletedBook });
});
app.listen(3000, () => {
console.log("Books API running on http://localhost:3000");
});
Error Handling
const express = require("express");
const app = express();
app.use(express.json());
// Async error handling wrapper
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Route with potential error
app.get("/api/error-demo", asyncHandler(async (req, res) => {
// Simulate async operation that might fail
const random = Math.random();
if (random < 0.5) {
throw new Error("Something went wrong!");
}
res.json({ message: "Success!", random });
}));
// 404 handler (should be after all routes)
app.use("*", (req, res) => {
res.status(404).json({
error: "Route not found",
path: req.originalUrl
});
});
// Global error handler (should be last)
app.use((err, req, res, next) => {
console.error("Error:", err.message);
res.status(err.status || 500).json({
error: err.message || "Internal server error",
...(process.env.NODE_ENV === "development" && { stack: err.stack })
});
});
app.listen(3000);
Serving Static Files
const express = require("express");
const path = require("path");
const app = express();
// Serve static files from public directory
app.use(express.static("public"));
// Serve static files with custom path
app.use("/assets", express.static("assets"));
// API routes
app.get("/api/data", (req, res) => {
res.json({ message: "API data" });
});
// Catch-all handler for SPA (Single Page Application)
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
app.listen(3000);
Exercise
Create a task management API with Express.js that includes:
- CRUD operations for tasks
- User authentication middleware
- Input validation
- Error handling
- Static file serving for a frontend
What's Next?
In the next lesson, we'll learn to work with databases in Node.js, connecting to MongoDB and MySQL.