Adding Live Reloading to Your Development Workflow
In web development, efficiency is key. While nodemon simplifies backend development by automatically restarting your server on file changes, it does not address frontend updates like HTML, CSS, or client-side JavaScript changes. This assignment introduces live reloading, which automatically refreshes your browser whenever changes are made. By leveraging WebSockets, your browser and server stay in sync, creating a smoother development experience.
Assignment Instructions
1. Add Global Template Variables with Middleware
Instead of manually passing the NODE_ENV variable to every single route, we can use Express middleware to make it globally available to all templates. Middleware functions run between the incoming request and your route handlers, allowing you to set up data that every template needs.
Add the following middleware to your server.js file after your existing middleware setup but before your routes:
/**
* Global template variables middleware
*
* Makes common variables available to all EJS templates without having to pass
* them individually from each route handler
*/
app.use((req, res, next) => {
// Make NODE_ENV available to all templates
res.locals.NODE_ENV = NODE_ENV.toLowerCase() || 'production';
// Continue to the next middleware or route handler
next();
});
The res.locals object is a special Express feature that stores data for the current request and makes it automatically available to all templates. Any property you add to res.locals becomes a variable that your EJS templates can access directly, just like variables you pass to res.render(). This is perfect for data that every template needs, like environment information, user details, or global settings.
This middleware runs for every request before reaching your routes. It sets up the NODE_ENV variable once, and then all your templates can use it without any additional work in your route handlers. We will explore the res.locals concept more deeply in upcoming assignments, as it becomes very useful for storing user information, navigation data, and other global template needs.
Do not use res.locals to store sensitive information like passwords or API keys, as this data is accessible in the browser and should be kept secure.
2. Update the head for WebSocket Integration
Open your header.ejs partial file and modify the head section to include the following WebSocket logic when the application is in development mode:
<% if (NODE_ENV.includes('dev')) { %>
<script>
// Get the current hostname and port from the browser
const host = window.location.hostname;
const currentPort = window.location.port;
// Calculate WebSocket port (current port + 1)
const wsPort = currentPort ? (parseInt(currentPort) + 1) : 3001;
// Create WebSocket connection using dynamic hostname and port
const ws = new WebSocket(`ws://${host}:${wsPort}`);
ws.onclose = () => {
setTimeout(() => location.reload(), 2000);
};
</script>
<% } %>
Notice how we can now use NODE_ENV directly in the template without passing it from each route. This script opens a WebSocket connection to a port one higher than the main application port. If the connection closes, the page reloads after a 2-second delay, simulating live reloading when our application is in development mode.
3. Add a WebSocket Server to server.js
We now need to set up a WebSocket server for live reloading in development mode. While WebSockets are typically used for real-time communication in applications like chat apps or live notifications, we are leveraging this technology to automatically refresh the browser when changes are detected. This allows real-time updates without needing to refresh the page manually. To get started, install the ws package as a development dependency. Open your terminal and run the following command:
pnpm add -D ws
Then, update your server.js file to start the WebSocket server in development mode. Add the following code before the code that starts your Express server near the bottom of the file:
// When in development mode, start a WebSocket server for live reloading
if (NODE_ENV.includes('dev')) {
const ws = await import('ws');
try {
const wsPort = parseInt(PORT) + 1;
const wsServer = new ws.WebSocketServer({ port: wsPort });
wsServer.on('listening', () => {
console.log(`WebSocket server is running on port ${wsPort}`);
});
wsServer.on('error', (error) => {
console.error('WebSocket server error:', error);
});
} catch (error) {
console.error('Failed to start WebSocket server:', error);
}
}
This creates a WebSocket server on a port one higher than the Express application's main port only when the application is in development mode. If you are interested in learning more about WebSockets, the following video provides a great introduction:
4. Test Your Changes
Start your application with pnpm run dev, then modify your EJS, CSS, or JavaScript files. Verify that the browser automatically reloads when changes are saved. If the page does not refresh or you encounter errors, ensure:
- The global middleware is properly added to
server.jsbefore your routes. - The WebSocket script in
header.ejsis correct. - The
server.jsWebSocket logic is functioning properly.
By using middleware to set global template variables, you have eliminated the need to pass NODE_ENV to every single route. This approach scales beautifully as your application grows, and you can easily add other global variables like user information, site settings, or navigation data without touching existing routes.
Key Concepts Summary
In this assignment, you implemented live reloading for your Express application and learned about global template variables using middleware. You configured WebSocket logic in header.ejs, set up a WebSocket server in development mode, and used res.locals to make data available across all templates. Together with nodemon, this workflow dramatically improves productivity by eliminating manual browser refreshes and reducing repetitive code in your routes.
The middleware pattern you just implemented is fundamental to Express development. As your applications become more complex, you will find res.locals invaluable for storing user authentication status, permissions, site-wide configuration, and other data that multiple templates need to access.