The Node process and the event loop
In a nutshell, the 'process' variable is the system process that runs your app. It also gives you partial control of its lower-level behavior. When you use the 'node' command to run a JavaScript file, a new system process spawns.
- The Node.js runtime initializes your application as a system process
- The main module executes (such as 'index.js' or 'main.js')
- The event loop starts to queue tasks (= blocks of code) to execute
- Your application starts to do its job
After the application starts, we have
- a system process that describes and manages your application as a whole
- an event loop to take care of scheduling and executing tasks
Let's now take a closer look at these two components. Our focus will be on the system process and how it plays together with the event loop.
Environment- vs. process-variables
Environment variables
Environment variables are part of the 'process.env' property. They get injected once the process starts and are available system-wide. Let's take the following example:
# In a terminal, type
echo $USER
echo $TERMINAL
// In a Javascript file, type
console.log(process.env.USER);
console.log(process.env.TERMINAL);
Both of the above commands will print the same value to the console. When starting a Node process, you can inject your own environment variables. We'll take a closer look at how to do this later in this article.
Process variables
Process variables are part of the 'process' object itself. They include, among others, the following set of information:
- The Node.js version, executable paths, and main module path
- Process id (pid) and arguments (argv)
- Functions like 'nextTick', 'on', and 'kill' give you high-level control over the process' behavior
Process variables are not shared with other processes or the operating system.
While it is possible and tempting to use 'process' to declare global variables, you should stick with locally scoped variables. Or use a dedicated 'const.js' file from where to import global constants.
Common use cases
Now that we have a broad understanding of the object itself, let's see what we can do with the Node process.
Scheduling event loop iteration callbacks
After a block of code (= a task) executes, the event loop waits for new tasks to run. Right between, the 'nextTick' event fires. Imagine it like a lifecycle hook that runs when code execution finishes.
Check out this great article on dev.to that visualizes this behavior in detail
Let's look at an example. In the code below, we schedule a synchronous operation to run after the server responds to a client.
Note that this method will still cause the application to block after sending a response! 'nextTick' must be handled with caution as it alters the behavior of the event loop. Using an asynchronous operation using promises or callbacks is often a better choice.
const http = require('http');
const fs = require('fs')
const server = http.createServer((req, res) => {
// Schedule sync task to write a log file after request is completed
process.nextTick(() => {
fs.appendFileSync('log', `${req.method} ${req.url}\n`, { flag: 'a' });
});
// Respond to the client
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
});
server.listen(3000);
Reading and writing environment variables
This feature allows you to read and write environment variables. It comes in handy for applications running in several different contexts. A prominent example is differentiating whether your app runs in development or production.
The dotenv module is particularly useful when working with environment variables and '.env' files
You can read and write environment variables by accessing the 'process.env' property. It is considered good practice to give your environment variables unique, uppercase names.
// Setting environment variables
process.env.APP_PRODUCTION_MODE = false;
process.env.APP_DEVELOPMENT_MODE = true;
// Reading environment variables
const USER = process.env.USER;
const LANG = process.env.LANG
Catching unexpected errors
The 'process' variable provides an event that allows you to catch all unhandled errors. It is good practice to handle errors where they occur. Consider this method as a last-resort option to catch unexpected exceptions.
// Handle all otherwise uncaught errors
process.on('uncaughtException', (error) => {
console.log(error)
});
throw new Error("An error occured");
Parsing process arguments (argv)
When running a Javascript file with 'node', you can pass extra arguments into it. These are appended to the 'process.argv' - array (= Command Line Argument) and can be read out and parsed by your program.
There are node modules such as commander or yargs that make working with process arguments much easier
This method is helpful to give you more control over your application's settings. For example, specifying a port as an optional argument.
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");
})
server.listen(process.argv[2] || 3000);
Let's try it out:
// Start the server on port 2000 instead of 3000:
node index 2000
Further reading and sources
Curious to read more? Then check out the official Node.js docs on the process object. You could also go ahead, open your favorite text editor, and play around. See how the process object behaves and what other methods are available.