Express.js Framework
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand Express.js framework
- - Create Express applications
- - Work with routes and middleware
- - Handle requests and responses
- - Use template engines
- - Build web applications with Express
- - Organize Express applications
Lesson 29.2: Express.js Framework
Learning Objectives
By the end of this lesson, you will be able to:
- Understand Express.js framework
- Create Express applications
- Work with routes and middleware
- Handle requests and responses
- Use template engines
- Build web applications with Express
- Organize Express applications
Express Introduction
Express.js is a fast, unopinionated, minimalist web framework for Node.js. It simplifies building web applications and APIs.
What is Express?
- Web Framework: Simplifies Node.js web development
- Minimalist: Lightweight and flexible
- Middleware: Powerful middleware system
- Routing: Easy routing system
- Template Engines: Support for various template engines
- Popular: Most popular Node.js framework
Why Express?
- Simple: Easy to learn and use
- Flexible: Unopinionated, use as needed
- Fast: Lightweight and performant
- Middleware: Rich ecosystem of middleware
- Community: Large community and resources
- Standard: Industry standard for Node.js
Express Introduction
Installation
# Install Express
npm install express
# Install with dev dependencies
npm install express --save-dev
# Install specific version
npm install express@4.18.0
Basic Express App
const express = require('express');
const app = express();
const PORT = 3000;
// Basic route
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Express vs Native HTTP
// Native HTTP (verbose)
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello');
}
});
// Express (simple)
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello');
});
Routes and Middleware
Basic Routes
const express = require('express');
const app = express();
// GET route
app.get('/', (req, res) => {
res.send('Home page');
});
// POST route
app.post('/users', (req, res) => {
res.send('Create user');
});
// PUT route
app.put('/users/:id', (req, res) => {
res.send('Update user');
});
// DELETE route
app.delete('/users/:id', (req, res) => {
res.send('Delete user');
});
// All methods
app.all('/test', (req, res) => {
res.send('Any method');
});
Route Parameters
const express = require('express');
const app = express();
// Single parameter
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});
// Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.send(`User ${userId}, Post ${postId}`);
});
// Optional parameter
app.get('/users/:id?', (req, res) => {
const userId = req.params.id || 'all';
res.send(`User: ${userId}`);
});
Query Parameters
const express = require('express');
const app = express();
// Query parameters
app.get('/search', (req, res) => {
const { q, page, limit } = req.query;
res.json({
query: q,
page: page || 1,
limit: limit || 10
});
});
// Usage: /search?q=express&page=1&limit=10
Route Handlers
const express = require('express');
const app = express();
// Single handler
app.get('/single', (req, res) => {
res.send('Single handler');
});
// Multiple handlers
app.get('/multiple',
(req, res, next) => {
console.log('First handler');
next();
},
(req, res) => {
res.send('Second handler');
}
);
// Array of handlers
const handlers = [
(req, res, next) => {
console.log('Handler 1');
next();
},
(req, res, next) => {
console.log('Handler 2');
next();
},
(req, res) => {
res.send('Handler 3');
}
];
app.get('/array', handlers);
Middleware
const express = require('express');
const app = express();
// Application-level middleware
app.use((req, res, next) => {
console.log('Request:', req.method, req.url);
next(); // Continue to next middleware
});
// Route-specific middleware
const checkAuth = (req, res, next) => {
const isAuthenticated = true; // Check authentication
if (isAuthenticated) {
next();
} else {
res.status(401).send('Unauthorized');
}
};
app.get('/protected', checkAuth, (req, res) => {
res.send('Protected route');
});
// Multiple middleware
app.use('/api', checkAuth, (req, res, next) => {
console.log('API middleware');
next();
});
Built-in Middleware
const express = require('express');
const app = express();
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
// CORS (if needed)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
next();
});
Request Handling
Request Object
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/users', (req, res) => {
// Request properties
console.log('Method:', req.method);
console.log('URL:', req.url);
console.log('Path:', req.path);
console.log('Query:', req.query);
console.log('Params:', req.params);
console.log('Headers:', req.headers);
console.log('Body:', req.body);
console.log('IP:', req.ip);
console.log('Protocol:', req.protocol);
console.log('Hostname:', req.hostname);
res.send('Request logged');
});
Response Methods
const express = require('express');
const app = express();
app.get('/response', (req, res) => {
// Send response
res.send('Hello, World!');
// Send JSON
res.json({ message: 'Hello' });
// Send status
res.status(200).send('OK');
// Send file
res.sendFile('/path/to/file.html');
// Redirect
res.redirect('/other-route');
// Set header
res.set('X-Custom-Header', 'value');
// Set multiple headers
res.set({
'X-Header-1': 'value1',
'X-Header-2': 'value2'
});
// Cookie
res.cookie('name', 'value');
// Clear cookie
res.clearCookie('name');
});
Error Handling
const express = require('express');
const app = express();
// Error handling middleware (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: err.message
});
});
// Route with error
app.get('/error', (req, res, next) => {
try {
throw new Error('Test error');
} catch (error) {
next(error); // Pass to error handler
}
});
// Async error handling
app.get('/async-error', async (req, res, next) => {
try {
// Async operation
await someAsyncOperation();
} catch (error) {
next(error);
}
});
Template Engines
What are Template Engines?
Template engines allow you to use static template files and replace variables with actual values at runtime.
Setting Up Template Engine
const express = require('express');
const app = express();
// Set view engine
app.set('view engine', 'ejs');
// Set views directory
app.set('views', './views');
// Render template
app.get('/', (req, res) => {
res.render('index', {
title: 'Home Page',
message: 'Hello, World!'
});
});
EJS Template
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p><%= message %></p>
<!-- Loop -->
<ul>
<% users.forEach(user => { %>
<li><%= user.name %></li>
<% }); %>
</ul>
<!-- Condition -->
<% if (isLoggedIn) { %>
<p>Welcome back!</p>
<% } else { %>
<p>Please log in</p>
<% } %>
</body>
</html>
Handlebars Template
// Install: npm install express-handlebars
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');
app.get('/', (req, res) => {
res.render('index', {
title: 'Home',
users: [
{ name: 'Alice' },
{ name: 'Bob' }
]
});
});
<!-- views/index.handlebars -->
<h1>{{title}}</h1>
<ul>
{{#each users}}
<li>{{name}}</li>
{{/each}}
</ul>
Pug Template
// Install: npm install pug
const express = require('express');
const app = express();
app.set('view engine', 'pug');
app.get('/', (req, res) => {
res.render('index', {
title: 'Home',
users: ['Alice', 'Bob']
});
});
//- views/index.pug
html
head
title= title
body
h1= title
ul
each user in users
li= user
Practice Exercise
Exercise: Express App
Objective: Practice creating Express applications, working with routes and middleware, handling requests, and using template engines.
Instructions:
- Create an Express application
- Set up routes and middleware
- Handle requests and responses
- Use template engines
- Practice:
- Creating Express apps
- Setting up routes
- Using middleware
- Handling requests
- Rendering templates
Example Solution:
// src/app.js
const express = require('express');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Set view engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// Logging middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// In-memory data store
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'Home',
message: 'Welcome to Express App!'
});
});
app.get('/users', (req, res) => {
res.render('users', {
title: 'Users',
users: users
});
});
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (user) {
res.render('user-detail', {
title: `User: ${user.name}`,
user: user
});
} else {
res.status(404).render('error', {
title: 'Not Found',
message: 'User not found'
});
}
});
app.get('/api/users', (req, res) => {
res.json(users);
});
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (user) {
res.json(user);
} else {
res.status(404).json({ error: 'User not found' });
}
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
const newUser = {
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
app.put('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: 'User not found' });
}
const { name, email } = req.body;
users[userIndex] = { ...users[userIndex], ...req.body };
res.json(users[userIndex]);
});
app.delete('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: 'User not found' });
}
users.splice(userIndex, 1);
res.status(204).send();
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong!'
});
});
// 404 handler
app.use((req, res) => {
res.status(404).render('error', {
title: 'Not Found',
message: 'Page not found'
});
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1><%= title %></h1>
<p><%= message %></p>
<nav>
<a href="/">Home</a>
<a href="/users">Users</a>
<a href="/api/users">API Users</a>
</nav>
</div>
</body>
</html>
<!-- views/users.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1><%= title %></h1>
<nav>
<a href="/">Home</a>
<a href="/users">Users</a>
</nav>
<ul class="user-list">
<% users.forEach(user => { %>
<li>
<a href="/users/<%= user.id %>">
<%= user.name %> - <%= user.email %>
</a>
</li>
<% }); %>
</ul>
</div>
</body>
</html>
<!-- views/user-detail.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1><%= title %></h1>
<nav>
<a href="/">Home</a>
<a href="/users">Users</a>
</nav>
<div class="user-detail">
<p><strong>ID:</strong> <%= user.id %></p>
<p><strong>Name:</strong> <%= user.name %></p>
<p><strong>Email:</strong> <%= user.email %></p>
</div>
</div>
</body>
</html>
<!-- views/error.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1><%= title %></h1>
<p><%= message %></p>
<nav>
<a href="/">Home</a>
</nav>
</div>
</body>
</html>
/* public/css/style.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f4f4;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 20px;
}
nav {
margin: 20px 0;
}
nav a {
display: inline-block;
margin-right: 10px;
padding: 8px 16px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
}
nav a:hover {
background-color: #0056b3;
}
.user-list {
list-style: none;
padding: 0;
}
.user-list li {
padding: 10px;
margin: 5px 0;
background-color: #f8f9fa;
border-radius: 4px;
}
.user-list a {
text-decoration: none;
color: #007bff;
}
.user-list a:hover {
text-decoration: underline;
}
.user-detail {
margin-top: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 4px;
}
.user-detail p {
margin: 10px 0;
}
// package.json
{
"name": "express-app-practice",
"version": "1.0.0",
"description": "Express.js application practice",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js"
},
"dependencies": {
"express": "^4.18.0",
"ejs": "^3.1.9"
},
"devDependencies": {
"nodemon": "^2.0.0"
},
"keywords": ["express", "nodejs"],
"author": "",
"license": "ISC"
}
Expected Output:
- Express server running
- HTML pages rendered
- API endpoints working
- Static files served
- Error handling working
Challenge (Optional):
- Add authentication
- Implement sessions
- Add database integration
- Build a complete application
Common Mistakes
1. Not Using next() in Middleware
// ❌ Bad: Missing next()
app.use((req, res) => {
console.log('Request');
// Request hangs
});
// ✅ Good: Call next()
app.use((req, res, next) => {
console.log('Request');
next();
});
2. Wrong Middleware Order
// ❌ Bad: Error handler before routes
app.use((err, req, res, next) => {
// Error handler
});
app.get('/', (req, res) => {
// Route
});
// ✅ Good: Routes before error handler
app.get('/', (req, res) => {
// Route
});
app.use((err, req, res, next) => {
// Error handler
});
3. Not Parsing Body
// ❌ Bad: Body not parsed
app.post('/users', (req, res) => {
console.log(req.body); // undefined
});
// ✅ Good: Parse body
app.use(express.json());
app.post('/users', (req, res) => {
console.log(req.body); // { name: 'Alice' }
});
Key Takeaways
- Express: Fast, minimalist web framework
- Routes: Easy route definition
- Middleware: Powerful middleware system
- Request/Response: Simplified request/response handling
- Template Engines: Support for various engines
- Best Practice: Use middleware, handle errors, organize code
- Popular: Industry standard for Node.js
Quiz: Express
Test your understanding with these questions:
-
Express is:
- A) Web framework
- B) Database
- C) Both
- D) Neither
-
app.get:
- A) GET route
- B) POST route
- C) Both
- D) Neither
-
Middleware:
- A) Uses next()
- B) Doesn't use next()
- C) Both
- D) Neither
-
req.params:
- A) Route parameters
- B) Query parameters
- C) Both
- D) Neither
-
req.query:
- A) Route parameters
- B) Query parameters
- C) Both
- D) Neither
-
Template engine:
- A) Renders views
- B) Serves static files
- C) Both
- D) Neither
-
Error handler:
- A) 4 parameters
- B) 3 parameters
- C) Both
- D) Neither
Answers:
- A) Web framework
- A) GET route
- A) Uses next()
- A) Route parameters
- B) Query parameters
- A) Renders views
- A) 4 parameters (err, req, res, next)
Next Steps
Congratulations! You've learned Express.js framework. You now know:
- How to create Express applications
- How to work with routes and middleware
- How to handle requests
- How to use template engines
What's Next?
- Lesson 29.3: RESTful APIs with Express
- Learn REST principles
- Build REST APIs
- Handle API requests/responses
Additional Resources
- Express Documentation: expressjs.com
- Express Guide: expressjs.com/en/guide/routing.html
- Middleware: expressjs.com/en/guide/using-middleware.html
Lesson completed! You're ready to move on to the next lesson.
Course Navigation
- HTTP Server Basics
- HTTP Server with Node.js
- Express.js Framework
- RESTful APIs with Express
- HTTP Server Basics
- HTTP Server with Node.js
- Express.js Framework
- RESTful APIs with Express