Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Terry Beard
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Terry Beard

Using require.resolve() To Calculate Module-Relative File Paths In Node.js

By
Published in Comments (2)

Over the weekend, I started looking into Express.js - the [seemingly] oldest and most popular framework for building web applications with Node.js. I've been using Node.js for a few years; but, I've never actually built a full-on web application. As such, I thought it would be fun to read up on it. And, while I was reading up on it, I came across something I had never seen before - a StrongLoop article that was using require.resolve() to calculate module-relative file paths. Historically, I've used the module-local variable, "__dirname", to calculate module-relative paths; so, I wanted to take a minute and look at how require.resolve() works.

In Node.js, the module-loader - require() - can use module-relative paths. So, for example, if you have a module in some arbitrary folder in the file system and that module needs to load one of its own libraries, you can safely refer to that library using a module-relative path:

require( "../lib/thinger" );

Outside of the module-loader, however, file paths often needs to be either absolute or relative to the root execution context of the node application. So, for example, when you read-in the contents of a file, you need to provide the fs.readFileSync() method with a non-local path. Historically, I've used the "__dirname" variable as a way to calculate that non-local path:

fs.readFileSync( path.join( __dirname, "local-file.txt" ) );

This produces a path that takes the absolute location of the module-directory and appends the given filename to it. This approach works fine; but, there is something very alluring about being able to more naturally calculate a module-relative file path. This is why the require.resolve() method caught my eye. require.resolve() uses the same machinery that require uses to load a module - which means it can use module-relative paths; but, rather than loading the module, it simply returns the resolved file path to said module. This module-relative cum absolute file path can then be used to, for example, read-in the content of said "module".

To see this in action, I've created a module that does nothing but locate and read-in the content of a file in the same directory. But, in order to ensure that we don't accidentally create root-relative paths, my app module loads my demo module from a sub-directory:

// We want to request a module in a sub-directory so that module-relative paths aren't
// accidentally ALSO RELATIVE to the root of the application.
require( "./sub/test" );

Then, from within that sub-directory demo module, I use __dirname and require.resolve() to generate module-relative paths:

// Require the core node modules.
var chalk = require( "chalk" );
var fileSystem = require( "fs" );
var path = require( "path" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// Traditionally, I have used the "__dirname" as a way to calculate module-relative
// paths since "__dirname" provides the absolute path to the current module directory.
// That way, any relative-path appended to the "__dirname" generates a module-relative
// path to the given location.
console.log( chalk.red.bold( "Using __dirname:" ) );
console.log( path.join( __dirname, "data.txt" ) );
console.log( chalk.dim( fileSystem.readFileSync( path.join( __dirname, "data.txt" ) ) ) );

console.log( "" );

// The require.resolve() method uses the same mechanics for locating a module (ie, the
// thing you normally "require"); but, instead of loading it, .resolve() returns the
// calculated path to the given module. And, since require() can use module-relative
// paths, so can require.resolve().
console.log( chalk.red.bold( "Using require.resolve():" ) );
console.log( require.resolve( "./data.txt" ) );
console.log( chalk.dim( fileSystem.readFileSync( require.resolve( "./data.txt" ) ) ) );

As you can see, I'm just locating and reading-in a file using the two different approaches. And, when we run this demo from within the root node app, we get the following terminal output:

Using require.resolve() to locate an existing file.

As you can see, both approaches calculated the same absolute file path given the module-relative path. And, both approaches were able to read-in the contents of the text file.

At first, it might seem like this is a pure win on generating module-relative file paths. But, require.resolve() has some significant caveats. For starters, it's interacting with the file system. Because require.resolve() uses the same module-resolution algorithm that require() uses, it's actually locating the module. Which means that it's looking at the file system to make sure the module exists. This has processing overhead to it (presumably more than the __dirname approach). And, it also brings us to the second caveat: it only works with existing files. Meaning, you can't use require.resolve() to generate a module-relative path to which you want to save a new file.

To see what I mean, I'm going to update my demo module to include a superfluous call to resolve a module-relative file path for a non-existent file:

// Require the core node modules.
var chalk = require( "chalk" );
var fileSystem = require( "fs" );
var path = require( "path" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// Executing a no-op call to resolve a module-local file path to a non-existent file
// in order to demonstrate that this is doing more than just calculating file paths.
require.resolve( "./my-new-file.txt" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// Traditionally, I have used the "__dirname" as a way to calculate module-relative
// paths since "__dirname" provides the absolute path to the current module directory.
// That way, any relative-path appended to the "__dirname" generates a module-relative
// path to the given location.
console.log( chalk.red.bold( "Using __dirname:" ) );
console.log( path.join( __dirname, "data.txt" ) );
console.log( chalk.dim( fileSystem.readFileSync( path.join( __dirname, "data.txt" ) ) ) );

console.log( "" );

// The require.resolve() method uses the same mechanics for locating a module (ie, the
// thing you normally "require"); but, instead of loading it, .resolve() returns the
// calculated path to the given module. And, since require() can use module-relative
// paths, so can require.resolve().
console.log( chalk.red.bold( "Using require.resolve():" ) );
console.log( require.resolve( "./data.txt" ) );
console.log( chalk.dim( fileSystem.readFileSync( require.resolve( "./data.txt" ) ) ) );

As you can see, I'm trying to resolve the module-relative file path for the non-existent file, "my-new-file.txt". And, when we re-run this demo, we get the following terminal output:

Using require.resolve() to locate a non-existing file.

As you can see, the whole demo fails because we're trying to resolve the file-path for a non-existent file. The module-resolution algorithm comes bubbling to the surface.

Clearly, the require.resolve() method is doing far more than just generating file paths - it's actually interacting with the file system. As such, it has greater processing overhead and some limitations in the way it can be used. But, it also provides a more natural way of "calculating" module-relative paths. So, while I don't think I would use this approach for many situations, it could be nice for executing one-time reads of things like configuration files.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel