Node.js Web Development with Express.js

Express.js is a minimalist, flexible, and robust web framework for Node.js. It simplifies the process of building web applications and APIs by providing a comprehensive set of features for routing, middleware integration, and handling HTTP requests and responses. While Node.js includes a built-in http module for server creation, Express offers a higher-level abstraction, making development faster and more intuitive.

Getting Started with Express.js

Installation

To incorporate Express into your project, navigate to your project directory in the terminal and execute the following command:

npm install express

Creating a Basic Web Server

After installation, you can set up a fundamental web server:

// Import the Express module
const express = require('express');
// Instantiate an Express application
const app = express();
// Define the server port
const PORT = 3000;

// Start the server and listen for incoming requests
app.listen(PORT, () => {
 console.log(`Express server is operational at http://localhost:${PORT}`);
});

Handling HTTP Requests

Express provides methods to listen for specific HTTP request types, such as GET and POST.

Responding to GET Requests

The app.get() method configures a handler for GET requests:

app.get('/data', (request, response) => {
 // 'request' object contains details about the client's request
 // 'response' object is used to send data back to the client
 response.send('Received a GET request!');
});

Responding to POST Requests

Similarly, app.post() handles POST requests:

app.post('/submit', (request, response) => {
 response.send('Data submitted via POST!');
});

Sending Responses to the Client

The response.send() method (or response.json() for JSON) is used to send content back to the client.

app.get('/profile', (request, response) => {
 // Send a JSON object
 response.json({ username: 'JaneDoe', userId: 123, status: 'active' });
});

app.post('/register', (request, response) => {
 // Send plain text
 response.send('Registration successful!');
});

Extracting Dynamic URL Parameters

Express allows you to define routes with dynamic parameters, which can be accessed via request.params.

// Route defined with a dynamic parameter 'itemId'
app.get('/items/:itemId', (request, response) => {
 // 'request.params' will contain an object like { itemId: 'value_from_url' }
 const itemId = request.params.itemId;
 console.log(`Requested item ID: ${itemId}`);
 response.send(`Fetching details for item: ${itemId}`);
});

Serving Static Assets

Express makes it straightforward to serve static files like images, CSS, and JavaScript. The express.static() middleware function facilitates this.

Basic Static File Hosting

To serve files from a directory named public:

app.use(express.static('public'));

Now, files within the public directory are accessible directly, e.g., /image.png would serve public/image.png.

Hosting Multiple Static Directories

You can serve files from several directories by calling express.static() multiple times. Express will search for files in the order the directories are specified.

app.use(express.static('public')); // Searched first
app.use(express.static('assets')); // Searched second

Adding a Virtual Path Prefix

To prefix the URL for static assets, specify a path before the express.static() call:

app.use('/static-files', express.static('public'));

Now, files in the public directory would be accessed via URLs like /static-files/image.png.

Automating Server Restarts with Nodemon

During development, repeatedly restarting your Node.js server after every code change can be cumbersome. Nodemon is a utility that monitors for any changes in your source and automatically restarts your server.

Installing Nodemon

npm install -g nodemon

The -g flag installs Nodemon globally, making it available for any project.

Using Nodemon

Instead of node your_app_file.js, use Nodemon:

nodemon server.js

This command starts your application, and Nodemon will automatically detect file modifications and restart the server, streamlining your development workflow.

Express Routing

In Express, routing defines how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP method (GET, POST, etc.).

Basic Route Definition

Routes are typically attached directly to the Express application instance:

const app = express();

app.get('/', (request, response) => {
 response.send('Welcome to the homepage!');
});

app.post('/register-user', (request, response) => {
 response.send('User registration endpoint.');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Modular Routing

For larger applications, it's beneficial to organize routes into separate modules.

Creating a Route Module

Define routes on an instance of express.Router():

// routes/userRoutes.js
const express = require('express');
const router = express.Router(); // Create a new router object

// Define user-related routes
router.get('/users', (req, res) => {
 res.send('List of all users.');
});

router.post('/users', (req, res) => {
 res.send('Create a new user.');
});

// Export the router module
module.exports = router;

Registering a Route Module

In your main application file, import and use the router:

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

// Register the router middleware
app.use(userRoutes);

app.listen(3000, () => console.log('Server online.'));

Adding a Base Path to a Route Module

You can mount a router at a specific path, effectively adding a prefix to all routes defined within that module:

// app.js
const express = require('express');
const app = express();
const apiRoutes = require('./routes/apiRoutes'); // Assuming apiRoutes.js exists

// All routes in apiRoutes will now be prefixed with '/api'
app.use('/api', apiRoutes);

app.listen(3000, () => console.log('Server running on port 3000'));

Express 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. The next() function is crucial for passing control to the next middleware or route handler.

Defining a Simple Middleware Function

const loggerMiddleware = (req, res, next) => {
 console.log(`Request received at: ${new Date().toISOString()} for URL: ${req.originalUrl}`);
 next(); // Pass control to the next middleware or route handler
};

Global Middleware

A global middleware is executed for every incoming request. You define it using app.use().

const app = express();

// This middleware will run for every request
app.use(loggerMiddleware);

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

app.listen(3000);

Multiple Global Middleware Functions

You can define multiple global middleware functions. They will execute in the order they are declared.

app.use((req, res, next) => {
 console.log('First global middleware executed.');
 next();
});

app.use((req, res, next) => {
 console.log('Second global middleware executed.');
 next();
});

app.get('/test', (req, res) => {
 res.send('Test route accessed.'); // Both middleware will run before this handler
});

Route-Specific (Local) Middleware

Middleware can also be applied to specific routes or groups of routes.

// Define a route-specific middleware
const authChecker = (req, res, next) => {
 if (req.query.token === 'secret') { // Simple authentication example
   next();
 } else {
   res.status(401).send('Unauthorized');
 }
};

// This middleware 'authChecker' only applies to the '/secure' route
app.get('/secure', authChecker, (req, res) => {
 res.send('Access granted to secure data!');
});

// Multiple local middleware functions can be chained or passed as an array
app.get('/admin', [authChecker, (req, res, next) => { console.log('Admin access.'); next(); }], (req, res) => {
 res.send('Admin panel.');
});

Types of Middleware

Application-Level Middleware

These are bound to the app instance using app.use(), app.get(), app.post(), etc.

// Global application-level middleware
app.use((req, res, next) => next());

// Route-specific application-level middleware
app.get('/page', mySpecificMiddleware, (req, res) => res.send('Page content.'));

Router-Level Middleware

These are bound to an instance of express.Router() using router.use(), router.get(), etc. Their usage is identical to application-level middleware, but they are confined to a router instance.

const router = express.Router();

// Router-level middleware, applies to all routes within this router
router.use((req, res, next) => {
 console.log('Router middleware: Time:', Date.now());
 next();
});

router.get('/dashboard', (req, res) => res.send('User Dashboard'));

app.use('/users', router); // Mount the router

Error-Handling Middleware

Express error-handling middleware functions are defined with four arguments: (err, req, res, next). They are specifically designed to catch and process errors that occur during the request-response cycle.

app.get('/error-test', (req, res, next) => {
 // Simulate an error
 const error = new Error('Something went wrong on the server!');
 next(error); // Pass the error to the error-handling middleware
});

// Error-handling middleware - must be defined last
app.use((error, req, res, next) => {
 console.error('An error occurred:', error.message);
 res.status(500).send(`Server Error: ${error.message}`);
});

Built-in Express Middleware

Express provides several built-in middleware functions:

  • express.static: For serving static assets (discussed earlier).
  • express.json(): Parses incoming requests with JSON payloads.
  • express.urlencoded({ extended: true/false }): Parses incoming requests with URL-encoded payloads.
const app = express();

// Enable JSON body parsing for API requests
app.use(express.json());

// Enable URL-encoded body parsing for form submissions
app.use(express.urlencoded({ extended: true }));

app.post('/api/data', (req, res) => {
 // req.body will now contain the parsed JSON or URL-encoded data
 console.log('Received body:', req.body);
 res.json({ message: 'Data received', data: req.body });
});

Custom Body Parsing Middleware (Illustrative Example)

While express.json() and express.urlencoded() handle most common body parsing needs, understanding how to build custom body parsing middleware illustrates the core concepts. This example shows how to manually parse a simple URL-encoded body.

// customBodyParser.js
const querystring = require('querystring');

function simpleBodyParser(req, res, next) {
 let rawBody = '';

 // Collect data chunks as they arrive
 req.on('data', (chunk) => {
   rawBody += chunk.toString();
 });

 // When all data is received
 req.on('end', () => {
   if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
     // Parse the raw body into an object
     req.body = querystring.parse(rawBody);
   } else if (req.headers['content-type'] === 'application/json') {
     try {
       req.body = JSON.parse(rawBody);
     } catch (e) {
       console.error('Failed to parse JSON body:', e.message);
       req.body = {}; // Fallback to empty object or handle error
     }
   } else {
     req.body = {}; // Default to empty object if content-type is not handled
   }
   next(); // Pass control
 });
}

module.exports = simpleBodyParser;

To use it:

// app.js
const app = express();
const myCustomParser = require('./customBodyParser');

app.use(myCustomParser); // Register the custom middleware

app.post('/submit-form', (req, res) => {
 console.log('Parsed form data:', req.body);
 res.send('Form data processed!');
});

Building RESTful APIs with Express

Express is an excellent choice for creating API endpoints. This section outlines how to structure and implement common API operations.

API Server Setup

Start with a basic Express server and integrate body parsing middleware:

const express = require('express');
const app = express();
const API_PORT = 4000;

app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded

app.listen(API_PORT, () => {
 console.log(`API server running on http://localhost:${API_PORT}`);
});

Creating an API Router Module

Organize API routes into a dedicated module, e.g., routes/api.js:

// routes/api.js
const express = require('express');
const apiRouter = express.Router();

// Define API endpoints
apiRouter.get('/status', (req, res) => {
 res.json({ message: 'API is healthy', timestamp: new Date() });
});

module.exports = apiRouter;

Then, in your main app.js:

// app.js
// ... (server setup and middleware)
const apiRoutes = require('./routes/api');
app.use('/api/v1', apiRoutes); // Mount API routes with a version prefix
// ... (app.listen)

Implementing GET API Endpoints

To handle GET requests, access query parameters via req.query:

apiRouter.get('/items', (req, res) => {
 const filterParams = req.query; // e.g., ?category=electronics&limit=10
 // In a real app, you'd fetch items based on filterParams from a database
 const sampleItems = [
   { id: 1, name: 'Laptop', category: 'electronics' },
   { id: 2, name: 'Keyboard', category: 'electronics' }
 ];
 res.json({
   success: true,
   message: 'Items retrieved successfully',
   data: sampleItems.filter(item =>
     !filterParams.category || item.category === filterParams.category
   )
 });
});

Implementing POST API Endpoints

For POST requests, access the request body via req.body (assuming body parsing middleware is configured):

apiRouter.post('/items', (req, res) => {
 const newItem = req.body; // Expecting { name: '...', category: '...' }
 if (!newItem || !newItem.name) {
   return res.status(400).json({ success: false, message: 'Invalid item data provided.' });
 }
 // In a real app, you'd save newItem to a database
 const createdItem = { id: Date.now(), ...newItem }; // Simulate ID generation
 res.status(201).json({
   success: true,
   message: 'Item created successfully',
   data: createdItem
 });
});

CORS (Cross-Origin Resource Sharing)

Web browsers enforce a same-origin policy, preventing web pages from making requests to a different domain than the one from which the web page was served. CORS is a browser security feature that relaxes this policy, allowing controlled access to resources from different origins.

Solving Cross-Origin Issues with the cors Middleware

The easiest way to enable CORS in Express is by using the cors npm package.

  • Install it: npm install cors
  • Import it: const cors = require('cors');
  • Use it before defining your routes: app.use(cors());
const express = require('express');
const app = express();
const cors = require('cors'); // Import cors middleware

app.use(cors()); // Enable CORS for all routes

app.get('/public-data', (req, res) => {
 res.json({ message: 'This data is accessible cross-origin.' });
});

app.listen(3000);

CORS Response Headers

  • Access-Control-Allow-Origin: Specifies which origins are allowed to access the resource. E.g., Access-Control-Allow-Origin: http://example.com or * for any origin.
  • Access-Control-Allow-Methods: Indicates which HTTP methods are allowed when accessing the resource. E.g., Access-Control-Allow-Methods: GET, POST, PUT.
  • Access-Control-Allow-Headers: Specifies which HTTP headers can be used in the actual request. E.g., Access-Control-Allow-Headers: Content-Type, X-Auth-Token.

The cors middleware handles these headers automatically based on its configuration.

CORS Request Types: Simple vs. Preflight

  • Simple Requests: These are direct HTTP requests that meet certain criteria (e.g., GET, POST, HEAD with specific headers). The browser sends the request directly, and the server responds with CORS headers.
  • Preflight Requests: For more complex requests (e.g., PUT, DELETE, custom headers, or non-simple Content-Type), the browser first sends an OPTIONS request (the "preflight" request) to the server. The server responds with which methods and headers are allowed. If the preflight request is successful, the browser then sends the actual request.

JSONP (JSON with Padding)

JSONP is an older technique for making cross-domain GET requests, primarily used when CORS is not an option or for supporting legacy browsers. It works by injecting a <script> tag into the DOM, which can bypass the same-origin policy because script tags can load content from any domain.

Important Considerations for JSONP

  • JSONP only supports GET requests.
  • It's not a true AJAX request as it doesn't use XMLHttpRequest.
  • If you use both CORS and JSONP, ensure your JSONP endpoint is defined *before* the CORS middleware to prevent CORS from intercepting and misinterpreting the JSONP request.
const express = require('express');
const app = express();
const cors = require('cors');

// 1. Define JSONP endpoint BEFORE CORS middleware
app.get('/api/jsonp-data', (req, res) => {
 const callbackName = req.query.callback; // Client provides a callback function name
 const responseData = { message: 'Data from JSONP endpoint', source: 'server' };

 if (callbackName) {
   // Construct the JSONP response: callbackName(JSON_DATA)
   res.send(`${callbackName}(${JSON.stringify(responseData)})`);
 } else {
   // If no callback, respond as regular JSON
   res.json(responseData);
 }
});

// 2. Configure CORS middleware (applies to all subsequent routes)
app.use(cors());

// Subsequent routes will be CORS-enabled
app.get('/api/standard-data', (req, res) => {
 res.json({ message: 'Standard CORS data.' });
});

app.listen(3000);

Client-Side JSONP Request with jQuery

jQuery provides a simple way to make JSONP requests:

// In your client-side JavaScript
$(document).ready(function() {
 $('#fetchJsonpBtn').on('click', function() {
   $.ajax({
     url: 'http://localhost:3000/api/jsonp-data',
     dataType: 'jsonp', // This tells jQuery to make a JSONP request
     success: function(response) {
       console.log('JSONP data received:', response);
       $('#jsonpResult').text(JSON.stringify(response));
     },
     error: function(xhr, status, error) {
       console.error('JSONP request failed:', status, error);
       $('#jsonpResult').text('Error fetching JSONP data.');
     }
   });
 });
});

Tags: Node.js Express.js web development API Development middleware

Posted on Sun, 31 May 2026 20:28:21 +0000 by Illusion