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:
- Initialize a new Node.js project
- Install Express and Nodemon
- 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
- Test all routes using a browser (for GET) and Postman/Thunder Client (for POST)
- Add proper status codes (200, 201, 404) to responses
- Add a 404 handler for undefined routes
- Use environment variables for the port number