apickfs

Modern and Minimal File system for Nodejs using promises and async await. No callback hell. No dependency hell.

Usage no npm install needed!

<script type="module">
  import apickfs from 'https://cdn.skypack.dev/apickfs';
</script>

README

ApickFS

Modern File Storage library for Nodejs v12.14.0 and above.

ApickFS is a free and open source File System library based on Node's fs.promises() that helps developers ditch callbacks and outdated depencencies.

apick is released under the MIT license. Current npm package version. Follow @vivmagarwal Follow @apickjs


🎓 Learning ApickFS

Full documentation for ApickFS lives on the website.


🧩 Why ApickFS:

  • Based on cutting edge fs.promises
  • Async Awiat based code
  • No outdated dependencies
  • Utilising latest features of Nodejs 12.14.0 Long Term released
  • Minimal base library
  • Easy to extend
  • Easy to read source code
  • Automatic JSON parsing
  • Auto Subdirectory creation

🎰 Available Methods:

Please click on the below method names to go to the documentation of that function

File opeations

Directory operations

Low level functions

Important Notes

  • ApickFS requires Node 12.14.0 LTS
  • Feel free to create a github issue if required.
  • It will be great if every Pull request is accompanied by a github issue.

Install and Include

Install the ApickFS file Storage.

npm i apickfs

Make sure that you are using Nodejs v12.14.0 or above.

Check your node version now:

node -v

In case you need to manage different verions of Node in your system, we strongy reccoment NVM

Including required methods in your file

const { writeFile, deleteFile, deleteDirectory, getDirectoryEntries } = require('apickfs');

You can just include the name of the method that you need. Incase you prefer to include the whole library you can do that too.

const apickFileStorage = require('apickfs');

In case you decide to include the whole library, you can use any apickFS method using a . (dot)

apickFileStorage.writeFile();
apickFileStorage.deleteFile();
apickFileStorage.deleteDirectory();
apickFileStorage.getDirectoryEntries();

Methods for file operations

fileExists()

Arguments: directoryPath , fileName
Result: a promise that resolves into true if file exists and false if it doesnot exist.
Example:

  { fileExists } = require('apickfs');

Inside an Async Function :

const {
  const my_file_exists = await fileExists(__dirname, 'my-file.json');
  console.log(my_file_exists);

Inside a normal Function :

fileExists(__dirname, 'my-file.json')
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Why differnt arguments for filename and directory path?
At several occassions we need to loop over several files in a particular directory. This approach make it a bit flexible in those use cases. And also we have tried to keep the API consistent.


fileExistsSync()

Arguments
directoryPath, fileName

Result
true if file exist and false if it doesnot exist.

Examples

const { fileExistsSync } = require('apickfs');
const file_exists = fileExistsSync(__dirname, 'my-file.txt');
console.log(file_exists);

readByLineNumbers()

Reads the particular line number from a file. In case it's valid JSON file, its automatically parsed to an Object or an Array. We can provide an array of line numbers to read multiple lines at a time. Arguments

const { readByLineNumbers } = require('apickfs');

Arguments

  1. directoryPath required, path to directory
  2. fileName required, file name
  3. lineNumbers required, the line number or the array of line numbers you want to read.

Result
apickFS result object
succss: ture/false
message: A descriptive message. data; An array of lines read from the file.

Examples

** Async Function **

const myData = await readByLineNumbers(__dirname, 'my-file.txt', 10);
console.log(myData);
const myData = await readByLineNumbers(__dirname, 'my-file.txt', [1, 3]);
console.log(myData);

** Normal Function **

readByLineNumbers(__dirname, 'some-file-name.txt', 10)
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** success response **

{
  success: true,
  message: 'SUCCESS: 1 lines successfully read from /Users/vivek/practice-apps/apickdb/my-file.txt.',
  data: [ { hello: 'world' } ]
}
{
  success: true,
  message: 'SUCCESS: 2 lines successfully read from /Users/vivek/practice-apps/apickdb/my-file.txt.',
  data: [ { hello: 'world' }, { hello: 'world' } ]
}

** fail response **

{
  success: false,
  message: 'Line number index 10 does not exist in /Users/vivek/practice-apps/apickdb/some-file-name.txt.'
}

getLinesCount()

Returns a promise that resolves into the total number of lines in the provided file.

Import

const path = require('path');
const { getLinesCount } = require('apickfs');

Arguments

  1. directoryName: required, string, directory path
  2. fileName: required, string, filename

Result
A promise that resolves into an integer of total number of lines in the provided file.

Examples

** Async Function **

const linesCount = await getLinesCount(path.join(__dirname, 'bigdata'), 'big.txt');
console.log(linesCount);

** Normal Function **

getLinesCount(path.join(__dirname, 'some-folder'), 'my-file.txt')
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/apickdb/bigdata/big.txt has total 163845 lines.',
  data: 163845
}

** Example fail response **

{
  success: false,
  message: 'Error: /Users/vivek/practice-apps/apickdb/bigdataxxx doesnot exist.'
}

writeLines()

Write a line or several lines to the end of the file.

Import

const { writeLines } = require('apickfs');

Arguments

  1. directoryPath: required, string, path to the directory.
  2. fileName: required, string, filename with extension.
  3. LinesofData: line of data or an array of lines of data.

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Async Function **

Example 1.

let writestatus = await writeLines(path.join(__dirname, 'some-folder'), 'my-file.txt', [
  'Hello Javascript',
  { Hello: 'node' }
]);
console.log(writestatus);

Example 2.

let writestatus = await writeLines(path.join(__dirname), 'my-file.txt', 'Hello Javascript');
console.log(writestatus);

Example 3.

let writestatus = await writeLines(path.join(__dirname), 'my-file.txt', { hello: 'hello' });
console.log(writestatus);

** Normal Function **

writeLines(path.join(__dirname), 'my-file.txt', { hello: 'hello' })
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

{
  success: true,
  message: 'SUCCESS: 2 lines successfully written to my-file.txt.'
}

** Example fail response **

{
  success: false,
  message: 'Error: /Users/vivek/practice-apps/brad-cli/apickdb/my-file.txtxx doesnot exist.'
}

deleteFile()

Deletes the file if it exists.

Import

const { deleteFile } = require('apickfs');

Arguments

  1. directoryPath required, string, path of the directory.
  2. fileName required, string, name of the file.

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Async Function **

let delstat = await deleteFile((__dirname, 'some-folder'), 'my-file.txt');
console.log(delstat);

** Normal Function **

deleteFile(__dirname, 'todelete.txt')
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

  {
    success: true,
    message: 'FILE deleted: /Users/vivek/practice-apps/brad-cli/apickdb/todelete.txt'
  }

** Example fail response **

  { success: false, message: 'Directory does not exist: some-folder' }

openFile()

Open file just returns an apickResultObject with success: true if the file exists. It created on if it doesnot exists. It never overwrites an existing file.

On the other hand writeFile() creates the file if it doesnot exist, but overwrites it in case it exists.


Unlike node's native open method, apickFS.openFile() doesnot return a fileHandle. We have special methods to like writeLine to open and add data to files. In most of the cases, you will be using openFile() most of the times.

Import

const { openFile } = require('apickfs');

Arguments

  1. directoryPath: required, string, path to the directory of the file.
  2. fileName: required, string, name of the file.

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Async Function **

let openstat = await openFile(path.join(__dirname), 'my-file1.txt');
console.log(openstat);

** Normal Function **

openFile(path.join(__dirname), 'my-file1.txt')
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/brad-cli/apickdb/my-file.txt exists.'
}
{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/brad-cli/apickdb/my-file1.txt created.'
}

** Example fail response **

{
  success: false,
  message: 'ERROR: Both directoryPath and filename are required parameters.'
}

writeFile()

Write file creates a new file if it doesnot exist. It takes in optional data as well. Incase the file already exists, it complelely overwrites it with the new data. In case new data is not provided, the existing file is replaced with an empty new file.


writeFile() acts differently from openFile() when the file already exists. Open file never overwrites data whereas writeFile file overwrites the content of the file.


Just like most of the apickFS functions, it too parses valid Objects and Arrays to JSON by default.


Import

const { writeFile } = require('apickfs');

Arguments

  1. directoryPath: required, string, path to the files directory.
  2. fileName: required, string, filename
  3. fileData: optional, String/Object/Array, the data for file.

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Async Function **

let writestat = await writeFile(path.join(__dirname), 'to-overwrite.txt', { hello: 'world' });
console.log(writestat);

** Normal Function **

writeFile(path.join(__dirname), 'to-overwrite.txt', { hello: 'world' })
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

{
  success: true,
  message: 'Success: /Users/vivek/practice-apps/brad-cli/apickdb/to-overwrite.txt successfully created/overwritten.'
}

Methods for directory ops

directoryExists()

Returns a promise that resolves to true if the directory exists else it resolves to false.

Import

const { directoryExists } = require('apickfs');

Arguments

  1. directoryPath: required, String, path to the directory.

Result
true/false

Examples

** Async Function **

let dirstat = await directoryExists(path.join(__dirname));
console.log(dirstat);

** Normal Function **

directoryExists(path.join(__dirname))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

** Example success response **

true

** Example fail response **

false

directoryExistsSync()

Returns true if the directory exists and false in case it doesnot exist.

Import

const { directoryExistsSync } = require('apickfs');

Arguments

  1. directoryPath: required, String, Path to the directory

Result
true/false

Examples

** Normal Function **

let dirStat = directoryExistsSync(path.join(__dirname, 'data'));
console.log(dirStat);

** Example success response **

true

** Example fail response **

false

getDirectoryEntries()

Recursively lists all the directories and files inside the given directory. Provides us output in several formats depending on the options provided.

Import

const { getDirectoryEntries } = require('apickfs');

Arguments

  1. directoryPath: required, String, path to the directory.
  2. options: optional, Object
    • maxLevel: integer, default:0(Unlimited)
    • listFiles: boolean, default:true
    • listDirectories: boolean, default:true
    • getFullPath:boolean, default:true
    • ignoreEmptyExtension: boolean, default:false
    • mustHaveExtensions: Array of String, default:[]
    • mustNotHaveExtensions: Array of String, default:[]
    • mustStartWith: Array of String, default:[]
    • mustNotStartWith: Array of String, default:[]
    • mustInclude: Array of String, default:[]
    • mustNotInclude: Array of String, default:[]
    • extraDetails: boolean, default:false

Result
Array of string or Array of Object depnding on the options provided.

Examples

Example to list just files

** Example Code **

let direntries = await getDirectoryEntries(__dirname, { listDirectories: false, getFullPath: false });
console.log(direntries);

** Example Response **

[
  '.DS_Store',
  'index.js',
  'index2.js',
  'my-file.txt',
  'to-overwrite.txt',
  '.data.db',
  'nestedarray.json',
  'nesteobj.json',
  'colors.json',
  'app.js',
  'post.json',
  'fileOrFolderExists.js',
  'big 4.txt',
  'big 2.txt',
  'big 3.txt',
  'big.txt',
  'my-file1.txt'
];

Example to get full path

** Example Code **

let direntries = await getDirectoryEntries(__dirname, { listDirectories: false });
console.log(direntries);

** Example Response **

[
  '/Users/vivek/practice-apps/brad-cli/apickdb/.DS_Store',
  '/Users/vivek/practice-apps/brad-cli/apickdb/index.js',
  '/Users/vivek/practice-apps/brad-cli/apickdb/index2.js',
  '/Users/vivek/practice-apps/brad-cli/apickdb/my-file.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/to-overwrite.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/.db-kittens/kittens/.data.db',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/nestedarray.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/nesteobj.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/colors.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/app.js',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/post.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/fileOrFolderExists.js',
  '/Users/vivek/practice-apps/brad-cli/apickdb/bigdata/big 4.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/bigdata/big 2.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/bigdata/big 3.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/bigdata/big.txt',
  '/Users/vivek/practice-apps/brad-cli/apickdb/my-file1.txt'
];

Example to get just Json files

** Example Code **

let direntries = await getDirectoryEntries(__dirname, { listDirectories: false, mustHaveExtensions: ['json'] });
console.log(direntries);

** Example Response **

[
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/nestedarray.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/nesteobj.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/colors.json',
  '/Users/vivek/practice-apps/brad-cli/apickdb/data/post.json'
];

Get some Extra details

** Example Code **

(async () => {
  let direntries = await getDirectoryEntries(__dirname, {
    listDirectories: false,
    mustHaveExtensions: ['json'],
    extraDetails: true
  });
  console.log(direntries);

** Example Response **

[
  {
    type: 'file',
    level: 3,
    fullPath: '/Users/vivek/practice-apps/brad-cli/apickdb/data/nestedarray.json',
    extension: 'json',
    parent: '/Users/vivek/practice-apps/brad-cli/apickdb/data'
  },
  {
    type: 'file',
    level: 3,
    fullPath: '/Users/vivek/practice-apps/brad-cli/apickdb/data/nesteobj.json',
    extension: 'json',
    parent: '/Users/vivek/practice-apps/brad-cli/apickdb/data'
  },
  {
    type: 'file',
    level: 4,
    fullPath: '/Users/vivek/practice-apps/brad-cli/apickdb/data/colors.json',
    extension: 'json',
    parent: '/Users/vivek/practice-apps/brad-cli/apickdb/data'
  },
  {
    type: 'file',
    level: 4,
    fullPath: '/Users/vivek/practice-apps/brad-cli/apickdb/data/post.json',
    extension: 'json',
    parent: '/Users/vivek/practice-apps/brad-cli/apickdb/data'
  }
];

Get whose name starts with '.' or 'big'

** Example Code **

let direntries = await getDirectoryEntries(__dirname, {
  listDirectories: false,
  getFullPath: false,
  mustStartWith: ['.', 'big']
});
console.log(direntries);

** Example Response **

['.DS_Store', '.data.db', 'big 4.txt', 'big 2.txt', 'big 3.txt', 'big.txt'];

Example to get just directories

** Example Code **

let direntries = await getDirectoryEntries(__dirname, {
  listFiles: false,
  getFullPath: false
});
console.log(direntries);

** Example Response **

['t2', '.db-kittens', 'kittens', 'data', '.data', 'bigdata'];

deleteDirectory()

Recursively deletes a directory and its contents. In case you want to stop it from deleting if the directory has some content the pass false as second argument.

Import

const { deleteDirectory } = require('apickfs');

Arguments

  1. directoryPath: required, string
  2. recursive: optional, boolean, default=true

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Example Code **

let dirstat = await deleteDirectory(path.join(__dirname, 't2'));
console.log(dirstat);

** Example Response **

{
  success: true,
  message: 'Directory deleted: /Users/vivek/practice-apps/brad-cli/apickdb/t2 recursively.'
}

openDirectory()

If the directory exists, it returns apickFsResultObject with success set to true. else creates one. openDirectory never overwrites existing directory. Unlike node's native open it doesnot return any handles.


This method is useful where you want to create a directory by never want to overwrite it.

Import

const { openDirectory } = require('apickfs');

Arguments

  1. directoryPath, required, String, path of the directory to open

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Example Code **

let dirstat = await openDirectory(path.join(__dirname, 't2'));
console.log(dirstat);

** Example Response **

{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/brad-cli/apickdb/t2 already exists.'
}

writeDirectory()

If directory exist, it overwrites it with an empty new directory. If directory doesnot exist, it creates one.

writeDirectory is uselful when you want to make sure that you are starting from an empty foleder. For rest of the cases openDirectory is the right choice.

Import

const { writeDirectory } = require('apickfs');

Arguments

  1. directoryPath: required, String, path to the directory

Result
apickFS result object
succss: ture/false
message: A descriptive message.

Examples

** Example Code **

let dirstat = await writeDirectory(path.join(__dirname, 't2'));
console.log(dirstat);

** Example Response **

{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/brad-cli/apickdb/t2 updated/overwritten.'
}

Low level Methods

Not to be used most of the times. We have specific mehtods of files and directories.

Parameter Combinations result Description
writeStorage(directoryPath) Success, Messge Creates directory
writeStorage(directoryPath , fieName) Success, Messge Creates or Truncates file
writeStorage(directoryPath , fieName, data) Success, Messge Creates file. Adds or Overwrites file content with data.
removeStorage(directoryPath) Success, Messge Removes directory
removeStorage(directoryPath , fieName) Success, Messge removes file
removeStorage(directoryPath,null,true) Success, Messge Removes directory recursively
removeStorageRecursively(directoryPath) Success, Messge Removes directory recursively
fileOrFolderExists(<directory/file> path) true/false Checks if a directory or a file exists
storageHander(<directory/file> path) true/false , file/directory Checks if a directory or a file exists and its type.

fileOrFolderExists()

check if a file or a directory exists:

apickFileStorage
  .fileOrFolderExists(path.join(__dirname, 'boo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

It returns true or false

false

check if a file or a directory exists:

apickFileStorage
  .fileOrFolderExists(path.join(__dirname, 'boo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

It takes just one argument and returns true or false

false

Example with a file:

apickFileStorage
  .fileOrFolderExists(path.join(__dirname, 'data', 'text.txt'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });
true

readStorage()

readStorage can be used with three different set of arguments. When just one argument is passed it is treated as a directory path. the second argument is used as file name. and the third file is used as data for file.

second and third arguments are optional.

removeStorage()

Removing data.json in the foo directory:

apickFileStorage
  .removeStorage(path.join(__dirname, 'foo'), 'data.json')
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Removing the boo directory (if it is empty):

apickFileStorage
  .removeStorage(path.join(__dirname, 'boo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

There are two way to remove boo if it's not empty (recursively)

Removing the boo directory (if it is empty):

Way 1 : provide a directory path, keep filename null and pass true for allowing recursive deleting

apickFileStorage
  .removeStorage(path.join(__dirname, 'boo'), null, true)
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Way 2 :

apickFileStorage
  .removeStorageRecursively(path.join(__dirname, 'boo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

removeStorageRecursively()

removeStorageRecursively is same as removeStorage with third argument for recursive option set to true.

apickFileStorage
  .removeStorageRecursively(path.join(__dirname, 'boo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

storageHandler()

check if a file or a directory exists and also get its type if it exists.

apickFileStorage
  .storageHander(path.join(__dirname, 'data', 'text.txt'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });
{ exists: true, type: 'file' }

Example with a folder :

apickFileStorage
  .storageHander(path.join(__dirname, 'data'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });
{ exists: true, type: 'directory' }

writeStorage()

writeStorage() - the selection of this name has a purpose. Its made up of two words = Write and Storage. Write stands for both creating and editing whereas Store stands for both files and directories.

Creating a directory called foo in our current directory :

Inside an async function :

(async () => {
  try {
    status = await apickFileStorage.writeStorage(path.join(__dirname, 'foo'));
    console.log(status);
  } catch (err) {
    console.log(err);
  }
})();

Outside an async function :

apickFileStorage
  .writeStorage(path.join(__dirname, 'fboo'))
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Run any one of the above and if all is good you must get a response object similar to it :

{
  success: true,
  message: 'SUCCESS: /Users/vivek/practice-apps/temp/fspromises/foo created.'
}

You have a sucess value to check if the opration was success and you have a message that can be logged or shown to the user.


You may use any one of async/await or then/catch approach.

Again, for most of the use cases, we have specific methods for files and directories.