Reliably Accessing Relative Files in NodeJS
NodeJS can be confusing when accessing the filesystem via relative paths. Here is an overview of various ways of accessing the filesystem, and a simple solution to reliably load files anywhere in your project.
TL;DR
// Two types of relative paths:
process.cwd()
// => the directory from which the js file is executed
__dirname
// => the directory in which the js file is located
// To access files in nodejs:
const fs = require('fs')
fs.readFileSync("./foo/data.txt");
// => Relative paths use `process.cwd` meaning they are relative to where
// the script is executed from.
// To use __dirname:
const path = require('path')
fs.readFileSync(path.join(__dirname, './foo/data.txt'))
// => read the file relative to where this js file is located.
// But inside a deeply nested directory accessing another deeply nested
// directory is awkward. example:
fs.readFileSync(path.join(__dirname, '../../../foo/bar/baz/data.txt'))
// Suggested solution: save a global reference to the projects root dir
// inside the main entry point so all files can load files relative to it.
// inside your main entry poin file (eg. index.js)
global.ROOT_DIR = __dirname
// inside any other file you can now read relative to the project root:
fs.readFileSync(path.join(ROOT_DIR, './foo/data.txt'))
Current Working Directory
The Nodejs Filesystem module lets you access files by absolute or relative paths to load whatever data your application requires. Relative paths are relative to process.cwd()
, which is the directory from which
you executed the node
command.
Relative paths will be resolved relative to the current working directory as specified by process.cwd(). -- nodejs.org
Let's see what this means in practice. Here I create a directory ./foo
(inside your home
directory) which contains print_cwd.js
. As I'm using a vagrant box, my file is /home/vagrant/foo/print_cwd.js
:
./foo/print_cwd.js
console.log('The current working directory is ' + process.cwd())
Running this command from different directories:
cd /home/vagrant
node foo/print_cwd.js
# => The current working directory is /home/vagrant
cd /home/vagrant/foo
node print_cwd.js
# => The current working directory is /home/vagrant/foo
This isn't ideal as we can't gaurantee the commands will always be executed from the same diectory. What if node is executed from a cron job or other script? Let's look at some alternatives.
dirname
NodeJS gives us another utility for accessing the current directory: __dirname
.
This is a variable available in every module (it's not a global, see the explanation here). It contains the value of path.dirname(__filename)
, where __filename
.
Given this description, we can already guess how __dirname
differs
from process.cwd()
. Let's it out using the same format as above:
./foo/print_dirname.js
console.log('__dirname is: ' + __dirname)
Running this command from different directories:
cd /home/vagrant
node foo/print_dirname.js
# => __dirname is /home/vagrant/foo
cd /home/vagrant/foo
node print_dirname.js
# => __dirname is /home/vagrant/foo
__dirname
gives the directory in which the file is located, regardless of where it is executed from. This makes its behaviour more predictable than `process.cwd()`, but as your project grows and the directory structure becomes deeply nested, you will have to traverse many levels up and down. As an alternative you can keep track of the root directory of your project.
Root Directory
For large projects, you will likely adopt a structure with one or multiple data
directories for storing files which have to be read from many different scripts. In these cases you will want to read relative to the projects root directory rather than the traversing up and down deep paths.
./index.js
// save the root directory to a global variable to be accessed anywhere
global.ROOT_DIR = __dirname
./foo/bar/baz/some_other_file.js
const fs = require('fs')
const path = require('path')
// read files relative to root directory from any deeply nested script.
let json = fs.readFileSync(path.join(ROOT_DIR, './data/important.json'))
This won't work if your project's entry point runs a web server, but you also need to run scripts from time to time (for example cron tasks managing the database). In this case you won't be able to create a new web server in every script, so you can't load the entry point in every script. Instead you can move setup operations (connect to DB, setup some global contexts, read config files etc.) into a config directory. This would be the place to set the root directory too, as these files need to be loaded for both scripts and the server:
./config/index.js
const path = require('path')
// We are one directory down, so set the root dir to the parent dir:
global.ROOT_DIR = path.join(__dirname, '../')
// now run your other configuration steps
require('./database')
// ...
Summary
Here I have covered an overview of reading relative files in a NodeJS project. The topic came up while I was working on my first large NodeJS project which require multiple restructures. I'm following the Domain Driven Design (DD) process to structure this project. If you'd like me to write up more posts about working with large NodeJS projects then get in touch.
Thanks for reading.