Getting Started

Installation & Setup

# Create a new directory and initialize npm
mkdir my-express-app
cd my-express-app
npm init -y

# Install Express
npm install express

# Install nodemon for development (optional)
npm install --save-dev nodemon

# Basic package.json scripts
"scripts": {
  "start": "node app.js",
  "dev": "nodemon app.js"
}
Note: Nodemon automatically restarts your application when file changes are detected, which is helpful during development.

Basic Express Server

// app.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 running at http://localhost:${port}`);
});
Note: The order of middleware and routes is important in Express. They are executed in the order they are defined.

Routing

Basic Routing

// GET method route
app.get('/', (req, res) => {
  res.send('GET request to homepage');
});

// POST method route
app.post('/', (req, res) => {
  res.send('POST request to homepage');
});

// PUT method route
app.put('/user', (req, res) => {
  res.send('PUT request to /user');
});

// DELETE method route
app.delete('/user', (req, res) => {
  res.send('DELETE request to /user');
});

// ALL method route (matches all HTTP verbs)
app.all('/secret', (req, res) => {
  res.send('Accessing the secret section');
});

Route Parameters

// Route with parameters
app.get('/users/:userId', (req, res) => {
  res.send(`User ID: ${req.params.userId}`);
});

// Multiple route parameters
app.get('/users/:userId/books/:bookId', (req, res) => {
  res.send(`User ${req.params.userId}, Book ${req.params.bookId}`);
});

// Route with regex pattern
app.get('/product/:id(\\d+)', (req, res) => {
  // Only matches if id is a number
  res.send(`Product ID: ${req.params.id}`);
});

// Optional parameter
app.get('/archive/:year?/:month?/:day?', (req, res) => {
  const { year, month, day } = req.params;
  if (year) res.send(`Archive for: ${year}-${month || 'XX'}-${day || 'XX'}`);
  else res.send('Full archive');
});

// Query string parameters
app.get('/search', (req, res) => {
  const { q, page } = req.query;
  res.send(`Search for: ${q}, Page: ${page || 1}`);
});

Route Handlers

// Single callback function
app.get('/example', (req, res) => {
  res.send('Hello from example!');
});

// Multiple callback functions
app.get('/example', (req, res, next) => {
  console.log('First callback');
  next();
}, (req, res) => {
  res.send('Hello from second callback!');
});

// Array of callback functions
const cb1 = (req, res, next) => {
  console.log('CB1');
  next();
};

const cb2 = (req, res, next) => {
  console.log('CB2');
  next();
};

const cb3 = (req, res) => {
  res.send('Hello from CB3!');
};

app.get('/example', [cb1, cb2, cb3]);

// Combination of functions and arrays
app.get('/example', [cb1, cb2], (req, res) => {
  res.send('Combination of arrays and functions');
});

Router Object

// birds.js - Router module
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('Birds home page');
});

router.get('/about', (req, res) => {
  res.send('About birds');
});

// Route with parameter
router.get('/:birdId', (req, res) => {
  res.send(`Bird ID: ${req.params.birdId}`);
});

module.exports = router;

// app.js - Using the router
const birds = require('./birds');

// Mount the router
app.use('/birds', birds);

Middleware

Application Middleware

// Application-level middleware (no mount path)
app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

// Application-level middleware (with mount path)
app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method);
  next();
});

// Multiple middleware functions
app.use('/user/:id', (req, res, next) => {
  console.log('Request URL:', req.originalUrl);
  next();
}, (req, res, next) => {
  console.log('Request Type:', req.method);
  next();
});

// Built-in middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
app.use(express.static('public')); // Serve static files

// Third-party middleware
const cookieParser = require('cookie-parser');
app.use(cookieParser());

const helmet = require('helmet');
app.use(helmet());

const cors = require('cors');
app.use(cors());

Router Middleware

// Router-level middleware
const router = express.Router();

// Middleware for all requests to this router
router.use((req, res, next) => {
  console.log('Router middleware executed');
  next();
});

// Middleware for specific path
router.use('/admin', (req, res, next) => {
  // Check authentication
  if (req.headers.authorization) {
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
});

// Error-handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

// 404 handler (should be last)
app.use((req, res, next) => {
  res.status(404).send('Sorry, cannot find that!');
});

// Custom middleware example
const requestTime = (req, res, next) => {
  req.requestTime = Date.now();
  next();
};

app.use(requestTime);

app.get('/', (req, res) => {
  res.send(`Requested at: ${req.requestTime}`);
});

Request & Response

Request Object

// Common request properties
app.get('/user', (req, res) => {
  console.log(req.baseUrl); // Base URL
  console.log(req.body); // Request body (needs body-parser)
  console.log(req.cookies); // Cookies (needs cookie-parser)
  console.log(req.hostname); // Hostname
  console.log(req.ip); // Client IP
  console.log(req.method); // HTTP method
  console.log(req.params); // Route parameters
  console.log(req.path); // Path portion of URL
  console.log(req.protocol); // Request protocol
  console.log(req.query); // Query string parameters
  console.log(req.secure); // Boolean for HTTPS
  console.log(req.url); // URL
  console.log(req.xhr); // Boolean for XMLHttpRequest

  res.send('Check console for request details');
});

// Accessing headers
app.get('/headers', (req, res) => {
  console.log(req.headers); // All headers
  console.log(req.get('Content-Type')); // Specific header
  res.send('Headers logged');
});

Response Object

// Sending responses
app.get('/send', (req, res) => {
  res.send('Hello World!'); // Send string
  res.send({ message: 'Hello' }); // Send JSON
  res.send('<p>Some html</p>'); // Send HTML
  res.send(Buffer.from('hello')); // Send buffer
});

// Setting status codes
app.get('/status', (req, res) => {
  res.status(201).send('Created'); // Set status and send
  res.sendStatus(200); // Send just the status code
});

// Setting headers
app.get('/headers', (req, res) => {
  res.set('Content-Type', 'text/plain');
  res.set({
    'Content-Type': 'text/plain',
    'X-Custom-Header': 'value'
  });
  res.send('Headers set');
});

// Redirects
app.get('/redirect', (req, res) => {
  res.redirect('/new-path'); // Default 302
  res.redirect(301, '/permanent'); // 301 redirect
  res.redirect('https://example.com'); // External redirect
});

// Sending files
app.get('/file', (req, res) => {
  res.sendFile('/absolute/path/to/file.pdf');
});

// JSON responses
app.get('/json', (req, res) => {
  res.json({ user: 'tobi' }); // Content-Type: application/json
  res.jsonp({ user: 'tobi' }); // JSON with padding for JSONP
});

Templates & Views

Template Engines

// Set EJS as template engine
// npm install ejs
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// Render a view
app.get('/', (req, res) => {
  res.render('index', {
    title: 'Home Page',
    message: 'Hello EJS!',
    users: ['Alice', 'Bob', 'Charlie']
  });
});

// Using Pug (formerly Jade)
// npm install pug
app.set('view engine', 'pug');

// Using Handlebars
// npm install express-handlebars
const exphbs = require('express-handlebars');
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');

// Using Mustache
// npm install mustache-express
const mustacheExpress = require('mustache-express');
app.engine('mustache', mustacheExpress());
app.set('view engine', 'mustache');

// Serve static files
app.use(express.static('public'));

// Multiple static directories
app.use(express.static('public'));
app.use(express.static('uploads'));

// Virtual path prefix
app.use('/static', express.static('public'));

EJS Templates

<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>
  <h1><%= message %></h1>
  
  <!-- JavaScript execution -->
  <% const date = new Date(); %>
  <p>Current date: <%= date.toLocaleDateString() %></p>
  
  <!-- Conditionals -->
  <% if (users.length > 0) { %>
    <ul>
      <% users.forEach(function(user) { %>
        <li><%= user %></li>
      <% }); %>
    </ul>
  <% } else { %>
    <p>No users found</p>
  <% } %>
  
  <!-- Includes -->
  <%- include('partials/footer') %>
</body>
</html>

<!-- views/partials/footer.ejs -->
<footer>
  <p>© 2023 My App</p>
</footer>

// Route to render the template
app.get('/', (req, res) => {
  res.render('index', {
    title: 'Home Page',
    message: 'Welcome to my app!',
    users: ['Alice', 'Bob', 'Charlie']
  });
});

Error Handling

Basic Error Handling

// Basic error-handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

// Catching errors in routes
app.get('/error', (req, res, next) => {
  try {
    // Some operation that might fail
    throw new Error('Something went wrong!');
  } catch (err) {
    next(err); // Pass to error handler
  }
});

// Async error handling
app.get('/async-error', async (req, res, next) => {
  try {
    const data = await someAsyncOperation();
    res.json(data);
  } catch (err) {
    next(err);
  }
});

// 404 handler (should be last route)
app.use((req, res, next) => {
  res.status(404).send('Sorry, cannot find that!');
});

// Custom error class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

Advanced Error Handling

// Global error handling middleware
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    // Development: send detailed error
    res.status(err.statusCode).json({
      status: err.status,
      error: err,
      message: err.message,
      stack: err.stack
    });
  } else {
    // Production: send minimal information
    const errorDetails = {
      status: err.status,
      message: err.isOperational ? err.message : 'Something went wrong!'
    };

    // Log error for monitoring
    console.error('ERROR 💥', err);

    res.status(err.statusCode).json(errorDetails);
  }
});

// Async error wrapper (avoid try-catch blocks)
const catchAsync = fn => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

// Usage with async/await
app.get('/user/:id', catchAsync(async (req, res, next) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    return next(new AppError('No user found with that ID', 404));
  }
  res.status(200).json({
    status: 'success',
    data: { user }
  });
}));

// Handle unhandled rejections
process.on('unhandledRejection', err => {
  console.log('UNHANDLED REJECTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  server.close(() => {
    process.exit(1);
  });
});

// Handle uncaught exceptions
process.on('uncaughtException', err => {
  console.log('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  process.exit(1);
});

REST API Development

Basic REST API

// Simple REST API for a resource
const express = require('express');
const app = express();

// Middleware to parse JSON bodies
app.use(express.json());

// In-memory "database"
let users = [
  { id: 1, name: 'John Doe', email: 'john@example.com' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];

// GET all users
app.get('/api/users', (req, res) => {
  res.json({
    status: 'success',
    results: users.length,
    data: { users }
  });
});

// GET single user
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) {
    return res.status(404).json({
      status: 'fail',
      message: 'User not found'
    });
  }
  res.json({
    status: 'success',
    data: { user }
  });
});

// POST create new user
app.post('/api/users', (req, res) => {
  const newId = users[users.length - 1].id + 1;
  const newUser = { id: newId, ...req.body };
  users.push(newUser);

  res.status(201).json({
    status: 'success',
    data: { user: newUser }
  });
});

// PATCH update user
app.patch('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) {
    return res.status(404).json({ message: 'User not found' });
  }

  // Update user properties
  Object.assign(user, req.body);

  res.json({
    status: 'success',
    data: { user }
  });
});

// DELETE user
app.delete('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ message: 'User not found' });
  }

  users.splice(index, 1);

  res.status(204).json({
    status: 'success',
    data: null
  });
});

API Best Practices

// API versioning
app.use('/api/v1/users', userRouter);
app.use('/api/v2/users', userRouterV2);

// Rate limiting
// npm install express-rate-limit
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.'
});

app.use('/api', limiter);

// Data sanitization
// npm install express-mongo-sanitize
const mongoSanitize = require('express-mongo-sanitize');

app.use(mongoSanitize()); // Prevent NoSQL injection

// XSS protection
// npm install xss-clean
const xss = require('xss-clean');

app.use(xss()); // Prevent XSS attacks

// CORS
// npm install cors
const cors = require('cors');

app.use(cors()); // Enable all CORS requests

// Or configure CORS
app.use(cors({
  origin: 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// Helmet for security headers
// npm install helmet
const helmet = require('helmet');

app.use(helmet()); // Set various HTTP headers for security

// Compression
// npm install compression
const compression = require('compression');

app.use(compression()); // Compress responses

// Parameter pollution protection
// npm install hpp
const hpp = require('hpp');

app.use(hpp()); // Protect against HTTP Parameter Pollution attacks