Skip to content

Node.js Express Framework

Express (or Express.js) is a minimal, flexible, and highly popular web application framework for Node.js. It provides a thin layer of fundamental web application features, without obscuring Node.js features, making it ideal for building robust APIs and web applications quickly.

Core Features of Express:

  • Routing: Defines how the application responds to client requests based on the URL (path) and HTTP method (GET, POST, PUT, DELETE, etc.).
  • Middleware: Provides a powerful mechanism to execute functions sequentially during the request-response cycle. Middleware can perform tasks like logging, parsing request bodies, authentication, authorization, error handling, and more.
  • Request/Response Helpers: Adds utility methods and properties to Node.js’s native request (req) and response (res) objects, simplifying common web development tasks.
  • Templating Engine Integration: Easily integrates with various templating engines (like EJS, Pug, Handlebars) to render dynamic HTML pages.
  • Focus on Performance: Express is designed to be lightweight and fast.

First, ensure you have a Node.js project initialized with a package.json file (npm init -y). Then, install Express as a project dependency:

$ npm install express

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

While Express handles the core web framework features, you often need additional middleware for specific tasks. Some common ones include:

  • cors: Enables Cross-Origin Resource Sharing.
  • helmet: Helps secure your Express apps by setting various HTTP headers.
  • morgan: HTTP request logger middleware.
  • dotenv: Loads environment variables from a .env file.
  • multer: Middleware for handling multipart/form-data, primarily used for file uploads.

Install them as needed, e.g.: $ npm install cors morgan

Note: Basic body parsing (JSON, URL-encoded) is now built into Express, so you often don’t need the separate body-parser package anymore.

Here’s a minimal Express application that starts a server listening on port 3000 and responds to requests to the homepage (/).

// Import the express library
const express = require('express');
// Create an Express application instance
const app = express();
// Define the port the server will listen on
const port = 3000;
// Define a route handler for GET requests to the root path ('/')
app.get('/', (req, res) => {
// req: request object, res: response object
res.send('Hello World from Express!');
});
// Start the server and listen for connections on the specified port
app.listen(port, () => {
console.log(`Example Express app listening at http://localhost:${port}`);
});

Save this code in a file (e.g., app.js) and run it:

$ node app.js

You’ll see the following output in your terminal:

Example Express app listening at http://localhost:3000

Open your web browser and navigate to http://localhost:3000/. You should see the text “Hello World from Express!”. If you navigate to any other path (e.g., http://localhost:3000/other), Express will automatically respond with a 404 Not Found.

Express route handlers receive two primary objects:

  • Request Object (req): Represents the incoming HTTP request. It contains information like:
    • req.params: Route parameters (e.g., /users/:userId).
    • req.query: URL query parameters (e.g., /search?term=node).
    • req.body: Parsed request body (requires body-parsing middleware like express.json() or express.urlencoded()).
    • req.headers: Incoming request headers.
    • req.method: HTTP method (GET, POST, etc.).
    • req.path: Request path.
    • req.ip: Client’s IP address.
  • Response Object (res): Represents the HTTP response that the Express app sends back. It provides methods for sending the response, such as:
    • res.send(): Sends various types of responses (HTML, text, JSON automatically stringified).
    • res.json(): Sends a JSON response.
    • res.status(): Sets the HTTP status code.
    • res.sendFile(): Sends a file.
    • res.render(): Renders a view template.
    • res.redirect(): Redirects the request.
    • res.set(): Sets response headers.

Routing determines how an application responds to specific endpoints (a combination of URI path and HTTP method).

The basic structure is app.METHOD(PATH, HANDLER), where METHOD is an HTTP method in lowercase (e.g., get, post, put, delete), PATH is the URL path (can include route parameters and patterns), and HANDLER is the function executed when the route is matched.

Let’s extend our example to handle different methods and paths:

const express = require('express');
const app = express();
const port = 3000;
// GET request to the homepage
app.get('/', (req, res) => {
console.log('GET /');
res.send('Hello from GET /');
});
// POST request to the homepage
app.post('/', (req, res) => {
console.log('POST /');
// Note: To access req.body, you need middleware like express.json()
console.log('Request Body:', req.body);
res.send('Hello from POST /');
});
// GET request for listing users
app.get('/users', (req, res) => {
console.log('GET /users');
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]); // Send JSON data
});
// GET request with a route parameter :userId
app.get('/users/:userId', (req, res) => {
const userId = req.params.userId;
console.log(`GET /users/${userId}`);
res.send(`Details for user ${userId}`);
});
// DELETE request for a specific user
app.delete('/users/:userId', (req, res) => {
const userId = req.params.userId;
console.log(`DELETE /users/${userId}`);
res.status(200).send(`User ${userId} deleted successfully.`); // Send status and message
});
// Catch-all for any other GET request (basic 404)
app.get('*', (req, res) => {
res.status(404).send('Resource not found');
});
app.listen(port, () => {
console.log(`Express app with routing listening at http://localhost:${port}`);
});

Save and run this code. You can now test different requests:

  • Browser/curl GET http://localhost:3000/ -> Hello from GET /
  • Browser/curl GET http://localhost:3000/users -> JSON array of users
  • Browser/curl GET http://localhost:3000/users/123 -> Details for user 123
  • curl -X POST http://localhost:3000/ -> Hello from POST /
  • curl -X DELETE http://localhost:3000/users/456 -> User 456 deleted successfully.
  • Browser/curl GET http://localhost:3000/nonexistent -> Resource not found (404 status)

Middleware functions have access to the request (req), response (res), and the next function in the application’s request-response cycle. The next function passes control to the next middleware function. If the current middleware doesn’t end the cycle (by sending a response), it must call next().

Middleware can be used for:

  • Executing any code.
  • Making changes to the request and response objects.
  • Ending the request-response cycle.
  • Calling the next middleware in the stack.

Types of Middleware:

  • Application-level: Bound to app using app.use() or app.METHOD().
  • Router-level: Bound to an instance of express.Router().
  • Error-handling: Have 4 arguments (err, req, res, next). Defined last.
  • Built-in: e.g., express.json(), express.urlencoded(), express.static().
  • Third-party: Installed via npm (e.g., cors, morgan).
Example: Adding Built-in and Custom Middleware
const express = require('express');
const app = express();
const port = 3000;
// Built-in middleware to parse JSON bodies
app.use(express.json());
// Built-in middleware to parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Custom application-level middleware: Logger
app.use((req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - ${req.method} ${req.originalUrl}`);
next(); // Pass control to the next middleware/route handler
});
// Route using the parsed body
app.post('/data', (req, res) => {
console.log('Received data:', req.body);
res.json({ message: 'Data received successfully', data: req.body });
});
app.get('/', (req, res) => {
res.send('Homepage with logging!');
});
// Error-handling middleware (defined last)
app.use((err, req, res, next) => {
console.error('Error caught:', err.stack);
res.status(500).send('Something broke!');
});
app.listen(port, () => {
console.log(`Express app with middleware listening at http://localhost:${port}`);
});

Now, when you make requests, you’ll see log messages, and POST requests to /data with a JSON body will correctly parse req.body.

Use the express.static built-in middleware to serve static assets like HTML, CSS, JavaScript files, and images.

Create a directory named public in your project root. Place static files inside it (e.g., public/index.html, public/css/style.css, public/images/logo.png).

Add the middleware to your app.js (usually near the top):

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

Now, if you run the app, requests for files within the public directory will be served automatically:

  • http://localhost:3000/index.html -> Serves public/index.html
  • http://localhost:3000/css/style.css -> Serves public/css/style.css
  • http://localhost:3000/images/logo.png -> Serves public/images/logo.png

You can mount the static directory on a specific path prefix: app.use('/static', express.static('public'));. Then files would be accessed via http://localhost:3000/static/index.html.

Let’s create a simple HTML form and handle its submission.

Create public/form.html:

<!DOCTYPE html>
<html>
<head><title>Form Example</title></head>
<body>
<h2>GET Form</h2>
<form action="/process-get" method="GET">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit GET">
</form>
<hr>
<h2>POST Form</h2>
<form action="/process-post" method="POST">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit POST">
</form>
</body>
</html>

Update app.js to handle these form submissions:

const express = require('express');
const app = express();
const port = 3000;
// Middleware for static files and body parsing
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// --- Routes ---
app.get('/', (req, res) => {
res.redirect('/form.html'); // Redirect root to the form
});
// Handle GET request from form
app.get('/process-get', (req, res) => {
// Data is in the query string for GET requests
const response = {
first_name: req.query.first_name,
last_name: req.query.last_name
};
console.log('GET Request Query:', response);
res.json(response);
});
// Handle POST request from form
app.post('/process-post', (req, res) => {
// Data is in the request body for POST requests (needs urlencoded middleware)
const response = {
first_name: req.body.first_name,
last_name: req.body.last_name
};
console.log('POST Request Body:', response);
res.json(response);
});
// --- Server Start ---
app.listen(port, () => {
console.log(`Express form example listening at http://localhost:${port}`);
});

Run app.js and navigate to http://localhost:3000/. You’ll see the form. Submitting either form will send the data to the corresponding route, which will parse it (from req.query for GET, req.body for POST) and return it as JSON.

Handling file uploads requires multipart/form-data encoding, which Express’s built-in parsers don’t handle. The multer middleware is commonly used for this.

Install multer: $ npm install multer

Create public/upload.html:

<!DOCTYPE html>
<html>
<head><title>File Upload</title></head>
<body>
<h3>File Upload:</h3>
<form action="/upload-file" method="POST" enctype="multipart/form-data">
Select file: <input type="file" name="myFile"><br>
<input type="submit" value="Upload File">
</form>
</body>
</html>

Update app.js:

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// Ensure 'uploads' directory exists
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)){
fs.mkdirSync(uploadDir);
}
// Configure Multer
// Store files in the 'uploads/' directory with their original names
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir); // Use the absolute path
},
filename: function (req, file, cb) {
// Use original name - caution: potential filename collisions/security issues
// Consider adding unique IDs or timestamps for production
cb(null, file.originalname);
}
});
const upload = multer({ storage: storage });
// Middleware
app.use(express.static('public'));
// Note: Don't use express.json() or express.urlencoded() on the upload route if using multer
// Routes
app.get('/', (req, res) => {
res.redirect('/upload.html');
});
// Handle file upload POST request
// 'myFile' must match the 'name' attribute of the <input type="file"> tag
app.post('/upload-file', upload.single('myFile'), (req, res, next) => {
// req.file is the 'myFile' file
// req.body will hold the text fields, if there were any
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
console.log('File uploaded successfully:');
console.log('Original Name:', req.file.originalname);
console.log('Stored Path:', req.file.path); // Absolute path where stored
console.log('Size:', req.file.size);
console.log('MIME Type:', req.file.mimetype);
const response = {
message: 'File uploaded successfully!',
filename: req.file.originalname,
path: req.file.path,
size: req.file.size
};
res.json(response);
}, (error, req, res, next) => {
// Handle potential multer errors
res.status(400).send({ error: error.message });
});
// Server Start
app.listen(port, () => {
console.log(`Express file upload example listening at http://localhost:${port}`);
});

Run the app, go to http://localhost:3000/, select a file, and upload it. The file will be saved in the uploads directory (created if it doesn’t exist) in your project root, and you’ll receive a JSON response with file details.

To parse and manage HTTP cookies, you typically need the cookie-parser middleware.

Install: $ npm install cookie-parser

Example:

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 3000;
// Use cookie-parser middleware
app.use(cookieParser());
app.get('/', (req, res) => {
// Access cookies from the request object
console.log('Cookies received: ', req.cookies);
// Set a cookie
res.cookie('myCookie', 'someValue', { maxAge: 900000, httpOnly: true }); // Expires in 15 mins
res.cookie('anotherCookie', 'moreData');
res.send('Hello! Check your browser cookies and server console.');
});
app.get('/clear', (req, res) => {
// Clear a cookie
res.clearCookie('myCookie');
res.send('myCookie cleared.');
});
app.listen(port, () => {
console.log(`Express cookie example listening at http://localhost:${port}`);
});

Run this app, visit http://localhost:3000/. Check your browser’s developer tools (Application/Storage tab) to see the cookies being set. The server console will log any cookies sent by the browser on subsequent requests.