Use the core fs
module to perform local filesystem operations.
Note: Each method also has a sync version, which should only be used on the first event loop tick, to avoid blocking the entire process and creating compounding latency across concurrent requests.
Note: Path arguments can be relative to process.cwd()
or absolute.
Note: Core APIs use callbacks by default. Since callbacks are a known anti-pattern, all the guides use promises (via songbird
) instead. Further, examples are assumed to be contained within the context an async
function where await
is available.
let msg = 'Hello, World!'
await fs.promise.writeFile('helloworld.txt', msg)
console.log('File created')
Using Streams:
let msg = 'Hello, World!'
let stream = fs.createWriteStream('helloworld.txt')
stream.write(msg);
stream.end();
let data = await fs.promise.readFile('helloworld.txt', 'utf8')
console.log(data)
Here we set the encoding to utf8
so the file content returns as a string.
Using Stream:
let readStream = fs.createReadStream('helloworld.txt')
stream.on('data', 'utf8', data => console.log(data))
stream.on('end', () => stream.close())
Updating files is easy because the above methods for creating files automatically overwrite any content if executed with an existing file path. Other methods for updating files include:
Append contents to the end of a file with fs.appendFile
:
let msgToAppend = '\nHow are you?'
await fs.promise.appendFile('helloworld.txt', msgToAppend)
console.log('File appended')
let oldPath = 'helloworld.txt'
let newPath = 'heyworld.txt'
await fs.promise.rename(oldPath, newPath)
console.log(`${oldPath} renamed ${newPath}`)
await fs.promise.unlink('helloworld.txt')
console.log('File deleted')
fs.stat
Returns a Stats
object with meta data including size and last accessed and modified dates. Stats
also includes helper methods (e.g., .isFile()
and .isDirectory()
):
let stat = await fs.promise.stat('foo/bar')
if (stat.isDirectory()) {
// do work
}
fs.watch
fs.watch
(and fs.watchFile
) tracks changes made in a given file or directory, and returns a rename
or change
event:
fs.watch('path/to/file/or/dir', (event, filename) => {
if (event === 'rename') {
console.log('File was renamed or deleted')
} else if (event === 'change') {
console.log('File was changed')
}
})
However, fs.watch
is well known to be unreliable across different platforms. For example, the filename
callback parameter is actually not reported on OSX. Furthermore, often simple changes are reported as rename
events.
rimraf
rimraf
is a useful package that allows non-empty directories to be deleted, while fs.unlink
does not. It also allows recursive deletion, so be careful not to accidentally erase the file you're working on.
let rimraf = require('rimraf')
// In an async function...
await rimraf.promise('path/to/directory')
console.log('Directory deleted')
chokidar
chokidar
is a package that wraps fs.watch
and fs.watchFile
and effectively resolves their unreliability:
let chokidar = require('chokidar')
let watcher = chokidar.watch('.', {ignored: 'node_modules'})
watcher.on('all', (event, path, stat) => {
console.log(event, path);
if (event === 'change') {
console.log(stat)
}
})
This example assigns a watcher to the entire current directory ('.'
), to register all events, ignoring node_modules/
.
add
, addDir
, change
events each return the same stats
object as fs.stat
above.
Additional files can easily be added to the watcher (in cases when not all files are already being watched) with:
watcher.add('addition.txt')
And removed:
watcher.unwatch('someFile.js')
fs-extra
fs-extra
wraps fs
to provide additional methods Can be used in place of fs
:
let fs = require('fs-extra')
Supported methods: copy
, copySync
, createOutputStream
, emptyDir
, emptyDirSync
, ensureFile
, ensureFileSync
, ensureDir
, ensureDirSync
, mkdirs
, mkdirsSync
, move
, outputFile
, outputFileSync
, outputJson
, outputJsonSync
, readJson
, readJsonSync
, remove
, removeSync
, writeJson
, writeJsonSync