Express.js Tutorial
Learn Express.js, the fast and minimalist web framework for Node.js, from basics to advanced concepts including middleware, routing, and production deployment.
Overview
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's the de facto standard server framework for Node.js.
Getting Started
Installation
bash
# Create new project
mkdir my-express-app
cd my-express-app
npm init -y
# Install Express
npm install express
# Install development dependencies
npm install --save-dev nodemon
# Update package.json scripts
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
Basic Server
javascript
// server.js
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000
// Basic route
app.get('/', (req, res) => {
res.send('Hello World!')
})
// Start server
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`)
})
Project Structure
my-express-app/
├── server.js # Main application file
├── package.json # Dependencies and scripts
├── routes/ # Route definitions
│ ├── index.js
│ ├── users.js
│ └── auth.js
├── middleware/ # Custom middleware
│ ├── auth.js
│ ├── logger.js
│ └── errorHandler.js
├── models/ # Data models
│ ├── User.js
│ └── Product.js
├── controllers/ # Route handlers
│ ├── userController.js
│ └── productController.js
├── config/ # Configuration files
│ ├── database.js
│ └── config.js
├── public/ # Static files
│ ├── css/
│ ├── js/
│ └── images/
└── views/ # Template files
├── layouts/
└── partials/
Routing
Basic Routing
javascript
const express = require('express')
const app = express()
// HTTP methods
app.get('/', (req, res) => {
res.send('GET request')
})
app.post('/', (req, res) => {
res.send('POST request')
})
app.put('/', (req, res) => {
res.send('PUT request')
})
app.delete('/', (req, res) => {
res.send('DELETE request')
})
// Route parameters
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.json({ userId, postId })
})
// Optional parameters
app.get('/posts/:year/:month?', (req, res) => {
const { year, month } = req.params
res.json({ year, month: month || 'all' })
})
// Wildcard routes
app.get('/files/*', (req, res) => {
const filePath = req.params[0]
res.send(`File path: ${filePath}`)
})
Route Patterns
javascript
// String patterns
app.get('/ab?cd', handler) // Matches acd, abcd
app.get('/ab+cd', handler) // Matches abcd, abbcd, abbbcd, etc.
app.get('/ab*cd', handler) // Matches abcd, abxcd, abRANDOMcd, etc.
app.get('/ab(cd)?e', handler) // Matches abe, abcde
// Regular expressions
app.get(/.*fly$/, handler) // Matches butterfly, dragonfly, etc.
app.get(/^\/users\/(\d+)$/, (req, res) => {
const userId = req.params[0]
res.send(`User ID: ${userId}`)
})
Router Module
javascript
// routes/users.js
const express = require('express')
const router = express.Router()
// Middleware specific to this router
router.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
// Define routes
router.get('/', (req, res) => {
res.send('Users home page')
})
router.get('/:id', (req, res) => {
res.send(`User ${req.params.id}`)
})
router.post('/', (req, res) => {
res.send('Create user')
})
router.put('/:id', (req, res) => {
res.send(`Update user ${req.params.id}`)
})
router.delete('/:id', (req, res) => {
res.send(`Delete user ${req.params.id}`)
})
module.exports = router
// server.js
const userRoutes = require('./routes/users')
app.use('/users', userRoutes)
Middleware
Built-in Middleware
javascript
const express = require('express')
const path = require('path')
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'))
app.use('/static', express.static(path.join(__dirname, 'public')))
// Parse cookies
const cookieParser = require('cookie-parser')
app.use(cookieParser())
Third-party Middleware
javascript
// Install middleware
// npm install morgan helmet cors compression
const morgan = require('morgan')
const helmet = require('helmet')
const cors = require('cors')
const compression = require('compression')
// Logging
app.use(morgan('combined'))
// Security headers
app.use(helmet())
// CORS
app.use(cors({
origin: ['http://localhost:3000', 'https://myapp.com'],
credentials: true
}))
// Compression
app.use(compression())
Custom Middleware
javascript
// middleware/logger.js
const logger = (req, res, next) => {
const timestamp = new Date().toISOString()
console.log(`${timestamp} - ${req.method} ${req.url}`)
next()
}
// middleware/auth.js
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (!token) {
return res.status(401).json({ error: 'Access token required' })
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' })
}
req.user = user
next()
})
}
// middleware/validation.js
const validateUser = (req, res, next) => {
const { name, email } = req.body
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
})
}
if (!email.includes('@')) {
return res.status(400).json({
error: 'Invalid email format'
})
}
next()
}
// Usage
app.use(logger)
app.use('/api/protected', authenticateToken)
app.post('/api/users', validateUser, createUser)
Error Handling Middleware
javascript
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error(err.stack)
// Mongoose validation error
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(e => e.message)
return res.status(400).json({
error: 'Validation Error',
details: errors
})
}
// JWT error
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({ error: 'Invalid token' })
}
// MongoDB duplicate key error
if (err.code === 11000) {
return res.status(400).json({
error: 'Duplicate field value'
})
}
// Default error
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
})
}
// 404 handler
const notFound = (req, res, next) => {
const error = new Error(`Not found - ${req.originalUrl}`)
error.status = 404
next(error)
}
// Usage (must be last)
app.use(notFound)
app.use(errorHandler)
module.exports = { errorHandler, notFound }
Request and Response
Request Object
javascript
app.get('/api/users', (req, res) => {
// URL parameters
console.log(req.params) // { id: '123' } from /users/:id
// Query parameters
console.log(req.query) // { page: '1', limit: '10' } from ?page=1&limit=10
// Request body
console.log(req.body) // JSON data from POST/PUT requests
// Headers
console.log(req.headers) // All request headers
console.log(req.get('Authorization')) // Specific header
// Cookies
console.log(req.cookies) // Parsed cookies
// Request info
console.log(req.method) // GET, POST, etc.
console.log(req.url) // /api/users?page=1
console.log(req.path) // /api/users
console.log(req.protocol) // http or https
console.log(req.ip) // Client IP address
res.json({ message: 'Request processed' })
})
Response Object
javascript
app.get('/api/users', (req, res) => {
// Set status code
res.status(200)
res.status(404).send('Not found')
// Send responses
res.send('Hello World') // String
res.json({ message: 'Success' }) // JSON
res.sendFile(path.join(__dirname, 'file.html')) // File
// Set headers
res.set('Content-Type', 'application/json')
res.set({
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
})
// Cookies
res.cookie('name', 'value', {
maxAge: 900000,
httpOnly: true,
secure: true
})
res.clearCookie('name')
// Redirects
res.redirect('/login')
res.redirect(301, '/new-url')
// Download
res.download('/path/to/file.pdf')
res.attachment('filename.pdf')
})
Database Integration
MongoDB with Mongoose
javascript
// npm install mongoose
const mongoose = require('mongoose')
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
})
// User model
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
maxlength: [50, 'Name cannot exceed 50 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Invalid email']
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [6, 'Password must be at least 6 characters']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
})
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next()
const bcrypt = require('bcryptjs')
this.password = await bcrypt.hash(this.password, 12)
next()
})
const User = mongoose.model('User', userSchema)
// User controller
const userController = {
// Get all users
getAllUsers: async (req, res) => {
try {
const page = parseInt(req.query.page) || 1
const limit = parseInt(req.query.limit) || 10
const skip = (page - 1) * limit
const users = await User.find()
.select('-password')
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 })
const total = await User.countDocuments()
res.json({
users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
})
} catch (error) {
res.status(500).json({ error: error.message })
}
},
// Get user by ID
getUserById: async (req, res) => {
try {
const user = await User.findById(req.params.id).select('-password')
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
} catch (error) {
res.status(500).json({ error: error.message })
}
},
// Create user
createUser: async (req, res) => {
try {
const user = new User(req.body)
await user.save()
const userResponse = user.toObject()
delete userResponse.password
res.status(201).json(userResponse)
} catch (error) {
if (error.name === 'ValidationError') {
const errors = Object.values(error.errors).map(e => e.message)
return res.status(400).json({ error: 'Validation Error', details: errors })
}
res.status(500).json({ error: error.message })
}
},
// Update user
updateUser: async (req, res) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
).select('-password')
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
} catch (error) {
res.status(500).json({ error: error.message })
}
},
// Delete user
deleteUser: async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json({ message: 'User deleted successfully' })
} catch (error) {
res.status(500).json({ error: error.message })
}
}
}
// Routes
app.get('/api/users', userController.getAllUsers)
app.get('/api/users/:id', userController.getUserById)
app.post('/api/users', userController.createUser)
app.put('/api/users/:id', userController.updateUser)
app.delete('/api/users/:id', userController.deleteUser)
Authentication and Authorization
JWT Authentication
javascript
// npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
// Auth controller
const authController = {
// Register
register: async (req, res) => {
try {
const { name, email, password } = req.body
// Check if user exists
const existingUser = await User.findOne({ email })
if (existingUser) {
return res.status(400).json({ error: 'User already exists' })
}
// Create user
const user = new User({ name, email, password })
await user.save()
// Generate token
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.status(201).json({
message: 'User created successfully',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
})
} catch (error) {
res.status(500).json({ error: error.message })
}
},
// Login
login: async (req, res) => {
try {
const { email, password } = req.body
// Find user
const user = await User.findOne({ email })
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' })
}
// Check password
const isValidPassword = await bcrypt.compare(password, user.password)
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' })
}
// Generate token
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({
message: 'Login successful',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
})
} catch (error) {
res.status(500).json({ error: error.message })
}
}
}
// Routes
app.post('/api/auth/register', authController.register)
app.post('/api/auth/login', authController.login)
Production Best Practices
Security Configuration
javascript
// npm install helmet express-rate-limit
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')
// Security headers
app.use(helmet())
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
})
app.use('/api/', limiter)
Environment Configuration
javascript
// config/config.js
const config = {
development: {
port: process.env.PORT || 3000,
database: {
url: process.env.DATABASE_URL || 'mongodb://localhost:27017/myapp-dev'
}
},
production: {
port: process.env.PORT || 8080,
database: {
url: process.env.DATABASE_URL
}
}
}
module.exports = config[process.env.NODE_ENV || 'development']
This Express.js tutorial covers web development from basics to production deployment. Practice building real applications to master these concepts.