Skip to main content

Express.js Basics

In the previous chapters, you learned how to create a basic HTTP server using Node’s built-in http module. While powerful, it requires significant boilerplate code for real-world applications. This is where Express.js comes in.

The Problem with Raw HTTP

Using the built-in http module, even simple tasks become verbose:
// Without Express - parsing JSON body manually
const http = require('http');

http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/users') {
    let body = '';
    req.on('data', chunk => { body += chunk; });
    req.on('end', () => {
      const user = JSON.parse(body);
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(user));
    });
  }
}).listen(3000);
This gets even more complex when you add routing, error handling, authentication, and other features.

Why 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’s the de facto standard for Node.js web development.

What Express Solves

ProblemExpress Solution
Complex routing logicSimple, declarative routing
Parsing request bodiesBuilt-in middleware
Managing middlewareComposable middleware chain
Error handlingCentralized error handlers
Serving static filesOne-line configuration

Express by the Numbers

  • 29+ million weekly downloads on NPM
  • Powers thousands of production applications
  • Extensive ecosystem of middleware packages
  • Battle-tested since 2010

Installation

npm install express

Creating a Basic Server

const express = require('express');
const app = express();
const PORT = 5000;

app.get('/', (req, res) => {
  res.send('Hello from Express!');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Routing

Express makes routing much easier than the raw http module.
// GET request
app.get('/about', (req, res) => {
  res.send('About Page');
});

// POST request
app.post('/contact', (req, res) => {
  res.send('Contact Form Submitted');
});

// Dynamic Parameters
app.get('/users/:id', (req, res) => {
  res.send(`User ID: ${req.params.id}`);
});

Middleware

Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle (next).

Creating Custom Middleware

const logger = (req, res, next) => {
  console.log(`${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl}`);
  next(); // Call next middleware
};

// Initialize middleware
app.use(logger);

Built-in Middleware

Express has built-in middleware for parsing body data.
// Parse JSON bodies
app.use(express.json());

// Parse URL-encoded bodies (forms)
app.use(express.urlencoded({ extended: false }));

Serving Static Files

To serve static files such as images, CSS files, and JavaScript files, use the express.static built-in middleware function.
const path = require('path');

// Set 'public' folder as static folder
app.use(express.static(path.join(__dirname, 'public')));

Summary

  • Express.js simplifies server creation and routing
  • Use app.get(), app.post(), etc., for routing
  • Middleware executes code between request and response
  • express.json() parses JSON request bodies
  • express.static() serves static assets

Express Router

Organize routes into separate modules: routes/users.js
const express = require('express');
const router = express.Router();

// All routes here are prefixed with /api/users
router.get('/', (req, res) => {
  res.json([{ id: 1, name: 'John' }]);
});

router.get('/:id', (req, res) => {
  res.json({ id: req.params.id, name: 'John' });
});

router.post('/', (req, res) => {
  res.status(201).json({ message: 'User created' });
});

module.exports = router;
app.js
const express = require('express');
const userRoutes = require('./routes/users');

const app = express();

app.use(express.json());
app.use('/api/users', userRoutes);

app.listen(3000);

Error Handling Middleware

// Custom error class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

// Route that throws error
app.get('/error', (req, res, next) => {
  next(new AppError('Something went wrong', 500));
});

// Async error wrapper
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// Usage with async routes
app.get('/users', asyncHandler(async (req, res) => {
  const users = await User.find(); // If this throws, error handler catches it
  res.json(users);
}));

// Error handling middleware (must be last)
app.use((err, req, res, next) => {
  console.error(err.stack);
  
  const statusCode = err.statusCode || 500;
  const message = err.isOperational ? err.message : 'Internal Server Error';
  
  res.status(statusCode).json({
    success: false,
    error: message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
});

Request Object Deep Dive

app.post('/api/data', (req, res) => {
  // URL parameters (/users/:id)
  console.log(req.params.id);
  
  // Query string (/users?sort=name&order=desc)
  console.log(req.query.sort);    // 'name'
  console.log(req.query.order);   // 'desc'
  
  // Request body (POST/PUT data)
  console.log(req.body);
  
  // Headers
  console.log(req.headers['content-type']);
  console.log(req.get('Authorization'));
  
  // Cookies (requires cookie-parser)
  console.log(req.cookies.session);
  
  // Full URL info
  console.log(req.originalUrl);   // /api/data?sort=name
  console.log(req.path);          // /api/data
  console.log(req.hostname);      // localhost
  console.log(req.ip);            // Client IP
  console.log(req.protocol);      // http or https
  console.log(req.method);        // POST
});

Response Object Methods

app.get('/response-examples', (req, res) => {
  // Send string
  res.send('Hello World');
  
  // Send JSON
  res.json({ message: 'Hello' });
  
  // Send with status
  res.status(201).json({ created: true });
  
  // Redirect
  res.redirect('/other-route');
  res.redirect(301, '/permanent-redirect');
  
  // Render template (with template engine)
  res.render('index', { title: 'Home' });
  
  // Download file
  res.download('./file.pdf');
  
  // Send file
  res.sendFile('/absolute/path/to/file.html');
  
  // Set headers
  res.set('Content-Type', 'text/html');
  res.set({
    'X-Custom-Header': 'value',
    'Cache-Control': 'no-cache'
  });
  
  // Set cookie
  res.cookie('session', 'abc123', {
    httpOnly: true,
    secure: true,
    maxAge: 3600000
  });
  
  // Clear cookie
  res.clearCookie('session');
});

Third-Party Middleware

const express = require('express');
const helmet = require('helmet');       // Security headers
const cors = require('cors');           // Cross-origin requests
const morgan = require('morgan');       // Logging
const compression = require('compression'); // Gzip
const rateLimit = require('express-rate-limit'); // Rate limiting

const app = express();

// Security headers
app.use(helmet());

// CORS
app.use(cors({
  origin: 'https://example.com',
  methods: ['GET', 'POST'],
  credentials: true
}));

// Logging
app.use(morgan('dev'));  // or 'combined' for production

// Compression
app.use(compression());

// 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);

// Body parsers
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

Application Structure Best Practice

project/
├── src/
│   ├── config/
│   │   └── db.js
│   ├── controllers/
│   │   └── userController.js
│   ├── middleware/
│   │   ├── auth.js
│   │   └── errorHandler.js
│   ├── models/
│   │   └── User.js
│   ├── routes/
│   │   ├── index.js
│   │   └── userRoutes.js
│   ├── services/
│   │   └── userService.js
│   ├── utils/
│   │   └── helpers.js
│   └── app.js
├── tests/
├── .env
├── .gitignore
├── package.json
└── server.js