Express Routing in Node.js
Routing is the process of figuring out how an application reacts to a client request sent to a certain endpoint, which comprises a specific HTTP request type (such as GET, POST, PUT, or DELETE) and a URI (or path). By letting you create handlers for various URL and HTTP method combinations, Express simplifies this process.
Previously, managing alternative pathways with simply the native http
module required using if
statements to verify request.url
, which might result in a “giant mess” and a “massive callback function” as the program expanded. To manage these routing patterns, Express offers a more streamlined and manageable solution.
Code Example: Basic Express “Hello World” with Routing
const express = require('express'); // Import the express module
const app = express(); // Create an Express application instance
const port = process.env.PORT || 3000; // Define the port
// Route for the home page (HTTP GET request to '/')
app.get('/', (req, res) => { // Handles GET requests to the root path
res.send('Hello, World!'); // Sends "Hello, World!" as the response
});
// Route for a 'wiki' page
app.get('/wiki', (req, res) => { // Another GET route
res.send('This is wiki page.');
});
// Start the server to listen on the specified port
app.listen(port, () => { // Make the app listen on port 3000
console.log(`Server listening on http://localhost:${port}`); // Log server status
});
// Catch-all route for any unlisted URLs (404 Not Found) - placed last
app.use((req, res) => { // `app.use` can also be used for routing
res.send('404-Page Not Found'); // Send a 404 message
});
Code Output Example (after running node app.js
and visiting http://localhost:3000
):
Server listening on http://localhost:3000
(In browser at http://localhost:3000
): Hello, World!
Defining Routes for Different HTTP Methods
You can use methods that match HTTP verbs to set up route handlers in Express.
app.get()
: Data can be retrieved from the server using theapp.get()
method. Theapp.get()
callback function is called when a client sends an HTTP GET request to a certain path.- For instance, if you go to
http://localhost:3000/
in a browser, “Hello, World!” appears. The same would be true forhttp://localhost:3000/anime
, which would yield a JSON array of shows. You can send back different kinds of material, like as text, HTML, or JSON, using theres.send()
method. app.post()
: Form data and other data are sent to the server using this function. The request body must frequently be parsed into a useable format, usually an object accessible viareq.body
, using middleware like asbody-parser
before handling POST requests. Additionally, Express hasexpress.json()
for parsing incoming JSON.- This configuration enables the processing of data sent by a client (for example, through an API request or form submission) by your Express application.
- Other HTTP Methods:
App.put()
andapp.delete()
are two more HTTP methods that Express supports. They are used for updating and deleting, respectively. These techniques follow the guidelines for designing RESTful APIs. It is possible to useapp.all()
orapp.use()
for operations that apply to all HTTP methods for a given path. To apply different handlers for various HTTP methods, you can also chain route declarations for a single path.
Code Example: Basic CRUD API with Express HTTP Methods
const express = require('express');
const app = express();
const port = 3000;
// Example user data (in a real app, this would be a database)
let users = [ // Dummy data
{ id: 1, name: "John Doe", age: 23, email: "john@doe.com" },
{ id: 2, name: "Jane Smith", age: 28, email: "jane@smith.com" }
];
// GET all users
app.get('/api/users', (req, res) => { // Handles GET request to /api/users
res.json(users); // Sends users array as JSON response
});
// GET a specific user by ID (using a route parameter)
app.get('/api/users/:id', (req, res) => { // ':id' is a route parameter
const userId = parseInt(req.params.id); // Access parameter value via req.params
const user = users.find(u => u.id === userId);
if (user) {
res.json(user);
} else {
res.status(404).send('User not found');
}
});
// POST to create a new user
app.post('/api/users', (req, res) => { // Handles POST request to /api/users
// In a real application, you'd use middleware like body-parser to access req.body
// For this example, let's assume req.body.user is available
const newUser = req.body.user || { id: users.length + 1, name: "New User", age: 30, email: "new@user.com" };
users.push(newUser);
res.status(201).send('User has been added successfully'); // 201 Created status
});
// PUT to update a user by ID
app.put('/api/users/:id', (req, res) => { // Handles PUT request to /api/users/:id
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex !== -1) {
// Assume req.body contains updated user data
users[userIndex] = { ...users[userIndex], ...req.body.user };
res.send('User updated successfully');
} else {
res.status(404).send('User not found');
}
});
// DELETE a user by ID
app.delete('/api/users/:id', (req, res) => { // Handles DELETE request to /api/users/:id
const userId = parseInt(req.params.id);
const initialLength = users.length;
users = users.filter(u => u.id !== userId);
if (users.length < initialLength) {
res.send('User deleted successfully');
} else {
res.status(404).send('User not found');
}
});
app.listen(port, () => {
console.log(`API server listening on port ${port}`);
});
When a client sends a request to /api/users/10
, req.params.id
would contain the string “10”. The req.params
object holds all the named route parameters sent with the URL.
Route Parameters ()
In order to retrieve dynamic data from URLs, route parameters are an essential component. Route parameters are placeholders in the URL path that can be used in place of specifying a distinct route for each potential ID or name.
- Defining Routes with Parameters: In the route path, you create a route parameter by placing a colon (
:
) before its name (e.g.,'/profile/:id'
). Express is instructed to collect any value that comes in that URL segment. - Extracting Data with
req.params
: Thereq.params
object then provides access to the values of these parameters. An object calledreq.params
holds the additional parameters that were delivered with the URL.Req.params
would have properties likeuserId
andbookId
for a route specified as'/users/:userId/books/:bookId'
. - The server will display “Displaying profile for user with ID: 123” in response to a browser query that goes to
http://localhost:3000/profile/123
. This indicates thatreq.params.id
was successful in capturing123
from the URL. This would also retrieve “electronics” and “laptop-x” fromhttp://localhost:3000/products/electronics/laptop-x
.
Express is a potent tool for creating scalable and stable web apps and APIs because of its systematic approach to routing, which makes URLs dynamic and easy to understand.
Modular Express Applications
For larger applications, defining all routes in a single file can become unwieldy. Express allows you to organize your endpoints into separate route files using express.Router()
.
Code Example: Modular Express Routing routes/users.js
const express = require('express');
const router = express.Router(); // Create a new router instance
// Define routes on the router object
router.get('/', (req, res) => { // Handles GET to /api/users
res.json({ message: 'Get all users from user router' });
});
router.post('/', (req, res) => { // Handles POST to /api/users
res.json({ message: 'Create a new user from user router' });
});
module.exports = router; // Export the router
app.js
const express = require('express');
const app = express();
const usersRouter = require('./routes/users'); // Import the user router
app.use('/api/users', usersRouter); // Mount the router at a specific base path
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
This structure makes your application more modular, customizable, and reusable.
Serving Static Files
Express can easily serve static assets like HTML, CSS, JavaScript, and images from a designated directory. This is done using express.static()
middleware.
Code Example: Serving Static Files
const express = require('express');
const path = require('path'); // Node's built-in path module
const app = express();
const port = 3000;
// Define the path to your static files (e.g., a 'public' folder)
const publicDirectoryPath = path.join(__dirname, 'public'); // Joins path segments
// Serve static files from the 'public' directory
app.use(express.static(publicDirectoryPath)); // Middleware to serve static files
// Example: If you have an index.html in 'public', it will be served at '/'
// Any other files like 'styles.css', 'app.js', 'image.png' will be available at /styles.css, /app.js, /image.png
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
With this setup, any file placed in the public
directory (e.g., public/index.html
, public/css/styles.css
) can be accessed directly in the browser via http://localhost:3000/index.html
or http://localhost:3000/css/styles.css
.
Templating Engines with Routing
Express supports various templating engines (like EJS, Handlebars, Pug/Jade, Mustache) to render dynamic HTML pages. This allows you to embed data from your server-side code directly into HTML templates.
Code Example: Using EJS Template Engine with Express First, install EJS: npm install ejs
. app.js
const express = require('express');
const app = express();
app.set('view engine', 'ejs'); // Set EJS as the view engine
app.set('views', path.join(__dirname, 'views')); // Set the directory for view files
app.get('/', (req, res) => {
res.render('index', { // Render the 'index.ejs' file
title: 'My Dynamic Page',
message: 'Welcome to the world of EJS!'
});
});
app.listen(3000, () => {
console.log('Server listening on port 3000 for templating example.');
});
views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title> <!-- Access variables passed to render [80] -->
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
When you access http://localhost:3000/
, Express will render index.ejs
, replacing <%= title %>
and <%= message %>
with the values provided in the res.render()
call.
Middleware and Error Handling
Express is built on the concept of middleware: a stack of functions that have access to the req
(request), res
(response), and the next
middleware function in the application’s request-response cycle. Middleware can execute any code, modify the request and response objects, end the request-response cycle, and call the next middleware function.
The next()
callback is crucial; calling it without arguments passes control to the next matching route or middleware. Calling next(err)
passes an error to error-handling middleware.
Code Example: Middleware and Error Handling
const express = require('express');
const app = express();
// Custom logging middleware
app.use((req, res, next) => { // `app.use` registers middleware
console.log(`New request: ${req.method} ${req.url} at ${new Date().toISOString()}`); // Log request details
next(); // Pass control to the next middleware/route handler
});
// Route with a simulated error
app.get('/error-test', (req, res, next) => {
// Simulate an error
const error = new Error('Something went wrong on this route!'); // Create an error object
error.status = 500;
next(error); // Pass the error to the error handling middleware
});
// Default route
app.get('/', (req, res) => {
res.send('Welcome to the homepage!');
});
// Error handling middleware - must have 4 arguments (err, req, res, next)
app.use((err, req, res, next) => { // Defined after all other routes and logic
console.error(err.stack); // Log the error stack for debugging
res.status(err.status || 500).send(`Error: ${err.message || 'Internal Server Error'}`); // Send error response
});
// 404 Not Found handler - placed after all other routes and middleware
app.use((req, res, next) => { // This will only be reached if no other route or middleware handled the request
res.status(404).send('Resource not found'); // Send 404 for unhandled routes
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000 for middleware example.');
});
Express routing provides a robust and flexible way to build web applications, moving beyond simple request-URL comparisons to a powerful system of HTTP method handling, dynamic parameters, modular organization, static file serving, templating, and middleware integration. It acts like a sophisticated air traffic controller for your web server, directing incoming requests to the correct functions based on their destination, type, and even their contents, ensuring smooth and efficient operation.