Skip to content

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.

VitePress Development Guide