Month 2, Week 2

The Node.js Ecosystem & Advanced Express.js

Building the Engine of a Backend Application

Module 1: The Node.js Engine Room

Understanding the Event Loop, Modules, and Packages

The Event Loop: The Heart of Node.js

Node.js is fast because it is non-blocking. How does it manage this? With the Event Loop.

Analogy: A busy restaurant kitchen with one very fast chef (the main thread) and many waiters (the Node.js APIs).

  1. A waiter takes an order (e.g., `fs.readFile`).
  2. The waiter gives the order to the kitchen staff (Node's C++ APIs / OS) and immediately returns to take another customer's order. The chef is never blocked.
  3. When the food is ready (the file is read), the kitchen staff places it on the counter (the Callback Queue).
  4. The Event Loop is a constantly running process that checks: "Is the chef free? Is there food on the counter?" If yes, it gives the food (the callback) to the chef to serve (execute).

Module Systems: Organizing Your Code

We need a way to split our code into logical, reusable files. Node.js has two primary module systems.

CommonJS (CJS): The Traditional Standard

This is the original module system in Node.js, using `require` and `module.exports`.


                        // logger.js
                        function log(message) {
                          console.log(`[LOG]: ${message}`);
                        }
                        module.exports = { log };

                        // app.js
                        const logger = require('./logger.js');
                        logger.log('Server started.');
                    

ES Modules (ESM): The Modern Standard

This is the standard used in modern JavaScript and browsers, using `import` and `export`.


                        // logger.mjs (note the file extension)
                        export function log(message) {
                          console.log(`[LOG]: ${message}`);
                        }

                        // app.mjs
                        import { log } from './logger.mjs';
                        log('Server started.');
                     

You must either use the `.mjs` extension or set `"type": "module"` in your `package.json` to enable ESM.

NPM (Node Package Manager)

NPM is the world's largest software registry. It's how we download and manage third-party tools (packages) for our projects.

  • `npm init -y`: Creates a `package.json` file to manage your project.
  • `npm install express`: Downloads the `express` package into `node_modules` and adds it to your dependencies.
  • `npm install -D nodemon`: Installs a package as a "dev dependency" (a tool for development, not for production).

Mid-Lecture Knowledge Check

Module 2: Advanced Express.js

Building a Scalable Server Architecture

Routing Deep Dive: `express.Router`

As your app grows, putting all your routes in one file becomes messy. `express.Router` is a mini Express application, allowing you to group related routes into their own files.

`routes/users.js`


                        const express = require('express');
                        const router = express.Router();

                        // This route is actually /users/
                        router.get('/', (req, res) => {
                          res.send('List of all users');
                        });

                        // This route is actually /users/:id
                        router.get('/:id', (req, res) => {
                          res.send(`Details for user ${req.params.id}`);
                        });
                        
                        module.exports = router;
                    

Mounting a Router

In your main server file, you import the router and tell Express to use it for a specific path.

`app.js`


                        const express = require('express');
                        const app = express();
                        const userRoutes = require('./routes/users.js');

                        // Any request starting with /users will be handled by userRoutes
                        app.use('/users', userRoutes);

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

This creates a clean, scalable file structure.

Middleware: The Assembly Line of Express

Middleware functions are the heart of Express. They are functions that have access to the request object (`req`), the response object (`res`), and the `next` function in the application’s request-response cycle.

Analogy: An assembly line. Each middleware is a station that can inspect the request, modify it, or end the cycle, before passing it to the next station with `next()`.

Custom Logging Middleware

Let's write a simple middleware that logs every incoming request.


                        const requestLogger = (req, res, next) => {
                          console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
                          
                          // VERY IMPORTANT: Call next() to pass control to the next middleware
                          next();
                        };

                        // Use it in your app
                        app.use(requestLogger);

                        // Now, every request will be logged to the console!
                        app.get('/', (req, res) => {
                            res.send('Hello World!');
                        });
                    

Third-Party Middleware

You don't have to write everything yourself. The Express ecosystem is rich with powerful third-party middleware.

  • `cors`: Enables Cross-Origin Resource Sharing.
  • `helmet`: Adds various HTTP headers to secure your app.
  • `morgan`: A professional-grade HTTP request logger.
  • `express.json()`: A built-in middleware to parse incoming JSON payloads. You will use this in almost every API.

                        const express = require('express');
                        const cors = require('cors');
                        const app = express();

                        app.use(cors()); // Allow requests from other origins
                        app.use(express.json()); // Alows us to read `req.body`
                    

Structured Error Handling

By default, an error in Express might crash your server. A professional application needs a centralized way to catch and handle errors.

Express has a special type of middleware for this. It has four arguments: `(err, req, res, next)`. It MUST be defined last, after all other `app.use()` and route calls.


                        // ... all your routes and other middleware ...

                        // The Error Handler
                        app.use((err, req, res, next) => {
                          console.error(err.stack); // Log the error for the developer
                          
                          // Send a generic, safe response to the client
                          res.status(500).send('Something broke!');
                        });
                     

In-Class Practical Exercise

The Mini Blog API

Your task is to structure a simple Express API with separate route files and custom middleware.

  1. Set up a new Node project and install `express`.
  2. Create a main `app.js` file.
  3. Create a `routes` folder with `posts.js` inside.
  4. In `posts.js`, create a router for `/` (all posts) and `/:id` (a single post).
  5. In `app.js`, create a custom `timestamp` middleware that adds a `requestTime` property to the `req` object.
  6. Mount the posts router at `/posts`.
  7. Add a centralized error handler at the end of `app.js`.
  8. Run the server and test your routes with a browser or tool like Postman.
Architecture Plan:

                            /my-blog-api
                            ├── node_modules/
                            ├── routes/
                            │   └── posts.js   (Contains the express.Router)
                            ├── app.js         (Main server file)
                            └── package.json
                         
Middleware (`app.js`):

                            const timestamp = (req, res, next) => {
                              req.requestTime = new Date().toISOString();
                              next();
                            };
                            app.use(timestamp);
                         
Router (`routes/posts.js`):

                            router.get('/', (req, res) => {
                                res.send(`All posts requested at: ${req.requestTime}`);
                            });
                         

Final Knowledge Check