Grokking Node.js
Vote

0% completed

​

Working with Built-in Modules

Loading a Built-in Module

Collecting System Facts with os

Naming the File with path

Saving the Report with fs

Serving the Report with http

More than 40 modules come with every installation of Node.js. They are called built-in modules or core modules, and you load them with require without installing anything. Four of them appear in almost every Node.js program: os reports details about the computer, path builds file paths, fs reads and writes files, and http runs a web server.

This lesson uses all four in one small project. The project collects a few facts about the computer, saves them to a text file, and serves that file to a browser. Each module does one step, and each step uses the result of the step before it.

The system report passes through four built-in modules: os collects it, path names its file, fs saves and reads it, and http sends it.
The system report passes through four built-in modules: os collects it, path names its file, fs saves and reads it, and http sends it.

The diagram shows the order of the four steps. Notice that the file sits between the two halves of the project: one script writes it, and the server reads it later.

Loading a Built-in Module

Each module is loaded once at the top of the file and stored in a variable with the same name:

const os = require('os'); const path = require('path'); const fs = require('fs'); const http = require('http');

You may also see the names written with a node: prefix, as in require('node:fs'). The prefix returns the same module. It tells a reader, and Node.js, that the name refers to a built-in module and not to an npm package with the same name. A few newer built-in modules, such as node:test, can only be loaded with the prefix; require('test') fails with MODULE_NOT_FOUND.

Collecting System Facts with os

The os module answers questions about the operating system and the hardware that the program is running on. Create a file named step1.js with four of its functions:

const os = require('os'); console.log('Operating System:', os.platform()); console.log('CPU Architecture:', os.arch()); console.log('Total Memory:', os.totalmem()); console.log('Free Memory:', os.freemem());

On a Windows laptop, it printed:

Operating System: win32 CPU Architecture: x64 Total Memory: 17058643968 Free Memory: 1094230016

os.platform() returns a short name for the operating system: win32 on Windows, including 64-bit Windows, linux on Linux and darwin on macOS. os.arch() names the processor type, such as x64 or arm64. Both memory functions return a number of bytes, and the free memory changes every time you run the script.

Programs use these values to adapt to the computer, for example to choose a folder that exists on that operating system, or to warn when free memory runs low. Byte counts are hard to read, so the report converts them to gigabytes and joins all four facts into one string:

const toGB = (bytes) => (bytes / 1024 ** 3).toFixed(1) + ' GB'; const report = [ `Platform: ${os.platform()}`, `Architecture: ${os.arch()}`, `Total memory: ${toGB(os.totalmem())}`, `Free memory: ${toGB(os.freemem())}`, ].join('\n');

Naming the File with path

The report needs a file name, and the obvious choice is a plain name such as 'system-report.txt'. That choice causes a problem. A relative file name is resolved against the folder that you ran the node command from, called the current working directory, not against the folder that holds the script.

A test makes the problem visible. A script that writes 'system-report.txt' was run with node report/report.js from the parent folder of report. The script printed saved, but the file appeared in the parent folder, next to the terminal, and not next to the script.

The path module builds a path that does not depend on where you run the command from. CommonJS gives every file the variable __dirname, which holds the full path of the folder that contains the file. path.join combines it with the file name:

const reportPath = path.join(__dirname, 'system-report.txt'); console.log(reportPath); console.log(path.basename(reportPath)); console.log(path.extname(reportPath));
C:\Users\sara\report\system-report.txt system-report.txt .txt

path.join puts the correct separator between the parts for the operating system, a backslash on Windows and a forward slash on Linux and macOS, so the same code works on both. It also resolves .. segments: path.join('reports', 'daily', '..', 'system-report.txt') returns reports\system-report.txt on Windows. path.basename returns the last part of a path, and path.extname returns the extension, including the dot. A program can use the extension to decide how to handle a file, for example path.extname('data.json') returns .json.

Saving the Report with fs

The fs module, short for file system, reads, writes and deletes files. fs.writeFile creates the file, or replaces its contents if it already exists. Put the pieces together in report.js:

const os = require('os'); const path = require('path'); const fs = require('fs'); const toGB = (bytes) => (bytes / 1024 ** 3).toFixed(1) + ' GB'; const report = [ `Platform: ${os.platform()}`, `Architecture: ${os.arch()}`, `Total memory: ${toGB(os.totalmem())}`, `Free memory: ${toGB(os.freemem())}`, ].join('\n'); const reportPath = path.join(__dirname, 'system-report.txt'); fs.writeFile(reportPath, report, (err) => { if (err) { console.log('Error writing file:', err.message); return; } console.log('Report saved to', path.basename(reportPath)); });
Report saved to system-report.txt

fs.writeFile does not wait for the disk. It starts the write and calls the callback when the write has finished. The callback's first argument is an error object if the write failed, for example because the folder does not exist or the program has no permission to write there, and null if it succeeded. Checking that argument first is the usual pattern for every Node.js callback.

Running the script from the parent folder now saves the file next to report.js, because the path no longer depends on the working directory. Reading and writing files has a full lesson of its own in this chapter, including the versions that wait and the versions that do not.

Serving the Report with http

The last step makes the report available in a browser. The http module creates a web server: a program that waits for requests on a network port and sends a response to each one. Create server.js next to the report:

const http = require('http'); const path = require('path'); const fs = require('fs'); const reportPath = path.join(__dirname, 'system-report.txt'); const server = http.createServer((req, res) => { fs.readFile(reportPath, 'utf8', (err, report) => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('The report could not be read.'); return; } res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(report); }); }); server.listen(3000, () => { console.log('Server is running on port 3000'); });

The events happen in this order. http.createServer registers the function that runs for every request, and server.listen(3000) starts waiting for connections on port 3000. When a request arrives, the function reads the report file. The 'utf8' argument makes fs.readFile return text; without it, the callback receives a Buffer, which holds the raw bytes. Once the text arrives, res.writeHead sets the status code and the content type, and res.end sends the text and finishes the response.

Start the server with node server.js, then open http://localhost:3000 in a browser or request it with curl from a second terminal:

curl -i http://localhost:3000/
HTTP/1.1 200 OK Content-Type: text/plain Date: Sun, 01 Nov 2026 17:45:43 GMT Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunked Platform: win32 Architecture: x64 Total memory: 15.9 GB Free memory: 1.0 GB

The code set only Content-Type; Node.js added the other headers itself. The server answers every path the same way, so http://localhost:3000/anything returns the same report. If the report file is missing, the server sends status 500 with the message The report could not be read. instead of crashing. The server keeps running until you stop it with Ctrl+C in its terminal.

While the server waits for a file to be read, it can accept other requests, because fs.readFile does not block the main thread. That is the non-blocking model from the first chapter, applied to a real server.

Reading Progress

0%


Vote for new content

On This Page

Loading a Built-in Module

Collecting System Facts with os

Naming the File with path

Saving the Report with fs

Serving the Report with http