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.comor*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 anOPTIONSrequest (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.');
}
});
});
});