Node.js & Express

Introduction to Express.js

20 min Lesson 7 of 40

What is Express.js?

Express.js (or simply Express) is a fast, unopinionated, minimalist web framework for Node.js. It provides a robust set of features for building web and mobile applications, making it easier to handle HTTP requests, routing, middleware, and more. Express is the most popular Node.js framework and is the foundation for many other frameworks.

Why Use Express?

  • Simplicity: Express simplifies the process of building web servers compared to raw Node.js HTTP module
  • Flexibility: Unopinionated design allows you to structure your application however you want
  • Robust routing: Powerful routing system for handling different URL paths
  • Middleware support: Extensive ecosystem of middleware for adding functionality
  • Performance: Minimal overhead, built on top of Node.js for speed
  • Community: Large community with extensive documentation and resources
Note: Express doesn't force you to use any specific database, template engine, or architecture. You have complete freedom to choose your tools and organize your code.

Installing Express

Before installing Express, make sure you have Node.js and NPM installed on your system. Then follow these steps:

Step 1: Create Project Directory

mkdir my-express-app cd my-express-app

Step 2: Initialize NPM

npm init -y

Step 3: Install Express

npm install express

This command installs Express and adds it to your package.json dependencies:

{ "dependencies": { "express": "^4.18.2" } }
Tip: For development, also install Nodemon to automatically restart your server when files change: npm install --save-dev nodemon

Creating Your First Express Server

Let's create a simple Express server. Create a file named app.js:

// Import Express const express = require('express'); // Create an Express application const app = express(); // Define the port const PORT = 3000; // Create a simple route app.get('/', (req, res) => { res.send('Hello World!'); }); // Start the server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });

Running Your Server

Run your server with Node:

node app.js

Or if you installed Nodemon, add this script to your package.json:

"scripts": { "start": "node app.js", "dev": "nodemon app.js" }

Then run:

npm run dev

Open your browser and navigate to http://localhost:3000. You should see "Hello World!" displayed.

Understanding the Code

Let's break down what's happening in our first Express server:

1. Importing Express

const express = require('express');

This line imports the Express module using Node.js's require() function.

2. Creating an Application Instance

const app = express();

This creates an Express application object. The app object has methods for routing HTTP requests, configuring middleware, rendering HTML views, and more.

3. Defining a Route

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

This defines a route handler for GET requests to the root path ('/'). When a request comes in, Express calls the callback function with two arguments:

  • req (request): Contains information about the incoming request
  • res (response): Provides methods to send a response back to the client

4. Starting the Server

app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });

This tells Express to start listening for requests on the specified port. The callback function runs once the server successfully starts.

The Request Object (req)

The request object represents the HTTP request and contains properties for the request query string, parameters, body, headers, and more.

Common Request Properties

req.params

Contains route parameters (named URL segments):

app.get('/users/:id', (req, res) => { console.log(req.params.id); // Accesses the :id parameter res.send(`User ID: ${req.params.id}`); });

req.query

Contains URL query string parameters:

// URL: /search?term=express&page=1 app.get('/search', (req, res) => { console.log(req.query.term); // 'express' console.log(req.query.page); // '1' res.send(`Searching for: ${req.query.term}`); });

req.body

Contains data sent in the request body (requires middleware):

app.use(express.json()); // Middleware to parse JSON app.post('/users', (req, res) => { console.log(req.body.name); console.log(req.body.email); res.send('User created'); });

req.headers

Contains HTTP request headers:

app.get('/info', (req, res) => { console.log(req.headers['user-agent']); console.log(req.headers['content-type']); res.send('Headers logged'); });

req.method

The HTTP method of the request (GET, POST, PUT, DELETE, etc.):

app.all('/test', (req, res) => { console.log(req.method); // GET, POST, PUT, etc. res.send(`Received ${req.method} request`); });

req.path

The path part of the request URL:

app.get('/about', (req, res) => { console.log(req.path); // '/about' res.send('About page'); });

req.ip

The remote IP address of the request:

app.get('/', (req, res) => { console.log(req.ip); // Client IP address res.send('IP logged'); });

The Response Object (res)

The response object represents the HTTP response that Express sends when it receives a request. It provides methods for sending different types of responses.

Common Response Methods

res.send()

Sends a response of various types (automatically sets Content-Type):

app.get('/text', (req, res) => { res.send('Plain text response'); }); app.get('/html', (req, res) => { res.send('<h1>HTML Response</h1>'); }); app.get('/json', (req, res) => { res.send({ name: 'John', age: 30 }); });

res.json()

Sends a JSON response (explicitly sets Content-Type to application/json):

app.get('/api/user', (req, res) => { res.json({ id: 1, name: 'John Doe', email: 'john@example.com' }); });

res.status()

Sets the HTTP status code:

app.get('/not-found', (req, res) => { res.status(404).send('Page not found'); }); app.post('/users', (req, res) => { res.status(201).json({ message: 'User created' }); });

res.sendFile()

Sends a file as the response:

const path = require('path'); app.get('/download', (req, res) => { const filePath = path.join(__dirname, 'files', 'document.pdf'); res.sendFile(filePath); });

res.redirect()

Redirects to a different URL:

app.get('/old-page', (req, res) => { res.redirect('/new-page'); }); app.get('/external', (req, res) => { res.redirect('https://www.google.com'); });

res.render()

Renders a view template (requires a template engine):

app.get('/profile', (req, res) => { res.render('profile', { name: 'John', age: 30 }); });

res.set() or res.header()

Sets response headers:

app.get('/custom-header', (req, res) => { res.set('Content-Type', 'text/plain'); res.set('X-Custom-Header', 'My Value'); res.send('Response with custom headers'); });

res.cookie()

Sets cookies in the response:

app.get('/set-cookie', (req, res) => { res.cookie('username', 'john', { maxAge: 900000, httpOnly: true }); res.send('Cookie set'); });
Note: Many response methods (like res.send(), res.json(), res.redirect()) terminate the request-response cycle. Once you call one of these methods, you cannot send another response.

Method Chaining

Express response methods can be chained for cleaner code:

app.get('/users/:id', (req, res) => { res .status(200) .set('Content-Type', 'application/json') .json({ id: req.params.id, name: 'John Doe' }); });

Basic Error Handling

It's important to handle errors properly in your Express applications:

app.get('/file', (req, res) => { const filePath = path.join(__dirname, 'nonexistent.txt'); // Check if file exists before sending const fs = require('fs'); if (!fs.existsSync(filePath)) { return res.status(404).send('File not found'); } res.sendFile(filePath); });

Handling 404 Errors

For routes that don't match any defined routes, add a catch-all handler at the end:

// All your routes here... // 404 handler (must be defined last) app.use((req, res) => { res.status(404).send('<h1>404 - Page Not Found</h1>'); });

Complete Example

Here's a more complete Express application demonstrating various concepts:

const express = require('express'); const app = express(); const PORT = 3000; // Middleware to parse JSON bodies app.use(express.json()); // Middleware to parse URL-encoded bodies app.use(express.urlencoded({ extended: true })); // Home route app.get('/', (req, res) => { res.send('<h1>Welcome to Express!</h1>'); }); // About route app.get('/about', (req, res) => { res.json({ name: 'My Express App', version: '1.0.0', description: 'Learning Express.js' }); }); // Route with parameter app.get('/users/:id', (req, res) => { const userId = req.params.id; res.json({ userId, message: `Getting user ${userId}` }); }); // Route with query parameters app.get('/search', (req, res) => { const { q, page = 1 } = req.query; res.json({ query: q, page: parseInt(page), results: [] }); }); // POST route app.post('/users', (req, res) => { const { name, email } = req.body; res.status(201).json({ message: 'User created', user: { name, email } }); }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Route not found' }); }); // Start server app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); });
Tip: Use tools like Postman, Insomnia, or the Thunder Client VS Code extension to test POST, PUT, DELETE requests and send JSON data to your API endpoints.

Environment Variables

It's good practice to use environment variables for configuration like port numbers:

// Install dotenv: npm install dotenv require('dotenv').config(); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });

Create a .env file:

PORT=3000 NODE_ENV=development
Warning: Never commit your .env file to version control (add it to .gitignore). It may contain sensitive information like API keys and database credentials.
Exercise: Create a simple Express API server:
  1. Initialize a new Node.js project
  2. Install Express and Nodemon
  3. Create an app.js file with the following routes:
    • GET / - Returns welcome message
    • GET /api/books - Returns array of 3-5 book objects (with id, title, author)
    • GET /api/books/:id - Returns a specific book by ID
    • POST /api/books - Accepts JSON body with book data and returns success message
    • GET /api/search - Accepts query parameter ?title=... and returns matching books
  4. Test all routes using a browser (for GET) and Postman/Thunder Client (for POST)
  5. Add proper status codes (200, 201, 404) to responses
  6. Add a 404 handler for undefined routes
  7. Use environment variables for the port number