Node.js: Code Outside the Browser / Node.js Internals and How it Works

Events in Practice

βœ… Prerequsite

πŸ“₯ Grab the starter files for this section and open the project folder.

Download the Repo & Open starter Folder : 2-how-node-works
https://github.com/codevedadev/complete-node-bootcamp

If you already have it open, skip this step.


πŸ“„ Create File: events.js


πŸš€ Custom Event

πŸ”Ή Step 1: Import the built-in events module

const EventEmitter = require('events');
  • events is a built-in Node module β€” no npm install needed.

  • By convention, the class is imported and named EventEmitter (capitalized, since it's a class).

πŸ”Ή Step 2: Create an emitter instance

const myEmitter = new EventEmitter();
  • βš™οΈ myEmitter is now an object that can emit events and have listeners attached to it.

πŸ”Ή Step 3: Set up a listener for a custom event

myEmitter.on('newSale', () => {
  console.log('There was a new sale!');
});
  • πŸ‘‚ .on(eventName, callback) β€” sets up a listener.

  • 🏷️ 'newSale' is a made-up event name (could be anything β€” newOrder, newCustomer, etc.).

  • ⏳ The callback only runs once the event is actually emitted.

πŸ”Ή Step 4: Add a second listener for the same event

myEmitter.on('newSale', () => {
  console.log('Customer name: Jonas');
});
  • πŸ‘₯ You can attach as many listeners as you want to the same event.

  • βœ… Both will run when the event fires.

πŸ”Ή Step 5: Emit the event

myEmitter.emit('newSale');
  • πŸ“€ .emit(eventName) triggers the event β€” like clicking a button.

  • ⚠️ Must come after the listeners are set up in the code, otherwise they won't catch it.

▢️ Run it: node events.js

πŸ–₯️ Output:

  • πŸ‘₯ Both listeners fire, in the order they were declared in the code.

  • πŸ‘€ This is the Observer Pattern: myEmitter is the subject/emitter; the two .on() listeners are the observers, watching and reacting.

πŸ”Ή Step 6: Pass data with the emitted event

Update the listener to accept an argument:

myEmitter.on('newSale', (stock) => {
  console.log(`There are now ${stock} items left in stock.`);
});

Update the emit call to pass a value:

myEmitter.emit('newSale', 9);

πŸ–₯️ Output:

There are now 9 items left in stock.
  • πŸ“¦ Any extra arguments passed to .emit() get forwarded to the listener's callback as parameters.

  • πŸ”„ Listeners for the same event fire synchronously, in declaration order β€” confirmed by seeing all three console logs print in sequence.


πŸ’‘ HTTP Server

πŸ”Ή Step 1: Add a second example lower in the same file β€” HTTP server events

Import the http module at the top of the file:

Create the server:

πŸ”Ή Step 2: Listen for the request event

server.on('request', (req, res) => {
  console.log('Request received');
  res.end('Request received');
});
  • πŸ‘‚ server is itself an EventEmitter instance (inherited via http.Server extending EventEmitter).

  • 🌐 .on('request', ...) fires every time a request hits the server.

  • πŸ“€ res.end(...) sends the response back to the client.

πŸ”Ή Step 3: Add a second listener for the same request event

  • πŸ‘₯ Confirms you can attach multiple listeners to the same built-in event too, same as the custom newSale example.

πŸ”Ή Step 4: Listen for the close event

  • πŸ”’ Fires when the server shuts down.

πŸ”Ή Step 5: Start the server

  • πŸš€ .listen(port, host, callback) β€” starts the server on port 8000 at 127.0.0.1 (localhost).

  • βœ… The callback is optional but useful for confirming startup.

  • πŸ”„ The process stays running (doesn't exit) because the event loop is still waiting for incoming I/O β€” this ties back to earlier event-loop concepts.

▢️ Run it: node events.js

πŸ–₯️ Output:

πŸ”Ή Step 6: Test by making a request

🌐 Open 127.0.0.1:8000 in the browser.

πŸ–₯️ Console output:

  • πŸ‘€ Notice each listener fires twice.

πŸ”Ή Step 7: Investigate the duplicate logs

Add logging for the request URL:

server.on('request', (req, res) => {
  console.log(req.url);
});

πŸ–₯️ Result: one log shows / and the other shows /favicon.ico.

  • 🌐 Browsers automatically send a separate request for /favicon.ico (trying to fetch a tab icon) alongside your actual page request.

  • πŸ’‘ That's why the request event fires twice per page load β€” it's genuinely two separate requests, not a bug.


βœ… Key Takeaway

  • πŸ‘¨β€πŸ’» For custom events you create yourself (like newSale), you must call .emit() yourself to trigger them.

  • βš™οΈ For built-in Node modules (http, fs, etc.), the module emits its own events internally whenever something relevant happens β€” you only need to attach .on() listeners to react, never .emit() them yourself.

const EventEmitter = require("events");
const http = require("http");

const myEmitter = new EventEmitter();

myEmitter.on("newSale", () => {
  console.log("There was a new sale");
});

myEmitter.on("newSale", () => {
  console.log("Customer name: Jonas");
});

myEmitter.on("newSale", (stock) => {
  console.log(`There are now ${stock} items left in stock`);
});

myEmitter.emit("newSale", 9);

//////////////////

const server = http.createServer();

server.on("request", (req, res) => {
  console.log("Request received!");
  console.log(req.url);
  res.end("Request received");
});

server.on("request", (req, res) => {
  console.log("Another request πŸ˜€");
});

server.on("close", () => {
  console.log("Server closed");
});

server.listen(8000, "127.0.0.1", () => {
  console.log("Waiting for requests...");
});