Node.js is an event-driven, JavaScript runtime which can
perform operations asynchronously in a such a way that is non-blocking in execution sense. These operations could include: FS/IO, HTTP operations etc. Node wraps the JavaScript language with extra rich features which enables the language perform operations that it can't do by default such as: reading and writing to files locally, running a full-fledged end-to-end network communication; as JavaScript's scope of operation is limited to the browser window. Hence, Node can allow us run JavaScript in environments other than the restricting interface of the browser.
In this article we will see how to search for existing files within a local directory via user-input in Node. At the end of this article, you should be familiar with the node and its
fs
and readline
modules and how to use its associated methods to find existing files in our directory.
Setup:
First off, we auto create a package.json
file that will help us manage our dependencies and other things such as versioning in the future.
So on your terminallocked in on the root of the project directory, type the code:
npm init –y
or yarn init -y
This generates the package file. So, we can now install npm packages such as nodemon which refreshes our node sessions each time we make and save a change to our files.
Finally, we would update the package.json file by adding a type: "module"
field to the file. This would enable us use the more modern and elegant ES2015(ES6) syntax in our node project(this is optional, as you could choose to use commonjs require
syntax). Exciting huh ?
Finally, we create an index.js file where we will write and test all our code.
To serve as our dummy files to be read, we would create a folder called blogs in our projects directory, there we will create three(3) text files - (text1.txt, text2.txt, text3.txt), and for each of these files we populate them with our preferred dummy data- Lorem Ipsum!
You could also use a more meaningful data for this, its your choice. Your folder structure, and package.json
file should be looking similar to mine below:
{
"name": "Nodescratch",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "2.0.2"
}
}
Ok, back to Node:
Nodejs has a couple of built-in core modules that can be used for various important operations, these includes: HTTP, Events, FS, etc. We would be working with the FS module (FS stands for File System; quite self-explanatory if you ask me ).
The FS module has some important helper methods that we can use to make manipulations on both local files on our machine and beyond. For this article, we would import and use three of these methods readFile
, readdir
, also we would import another
method from the readline module called readline
. This module allows us to read/accept data from the user via an interface:
import readline from "readline";
import { readFile, readdir } from "fs";
const { log, error } = await import('console') /* this is to avoid typing console.log or console.error redundantly */
To get users input, we would be using the readline
method which provides an interface for reading data from a readable stream, the most common being –
process.stdin
.
This operation takes some time to complete and hence traditionally
we would often use a callback function to handle the response once it’s done, but we would be making our codes more Promise-based by using the promisify
method of nodes community module called util
(I would definitely talk about that in depth in another article). So, lets how that is used in code:
import readline from "readline";
import { readFile, readdir } from "fs";
const { log, error } = await import('console')
import util from "util";
const rFile = util.promisify(readFile);
const rDir = util.promisify(readdir);
The readFile
asynchronously reads the entire contents of any file. This method takes two arguments, the relative path to the file and a callback function to handle response. But we would be using a rather modern approach by transforming this method to Promise-based method like the one above using promisify
.
Inclusively, the readdir
method can read the contents of a directory (folder) and just like the readFile
method, readdir
takes two arguments: a relative path to the
directory and a callback. Just like before, we would promisify
this method.
Doing this enables us to elegantly use these methods with the await
keyword in an async
function as depicted below:
import readline from "readline";
import { readFile, readdir } from "fs";
const { log, error } = await import('console')
import util from "util";
const rFile = util.promisify(readFile);
const rDir = util.promisify(readdir);
async function get() {
try {
const file = await rDir("./blogs");
setTimeout(() => log("Available files are:",file),3000);
} catch (e) { error(e.message);}
}
Another function we would define is the search function which would listen to our inputs, formats its and make search operations asynchronously based on this input.
But before we define this function, we need to create an interface that would let us key in inputs and also get outputs logged to the console. This function would be called later when we define an interface for accepting user input. Lets look at how the function is defined:
async function search(userInput) {
try {
const data = await rFile(`${userInput}`);
log(data.toString());
} catch (e) {
error(e.message);
}
}
Finally, we should note that the question
method of the readline
can be used to throw a query or question a user to perform an input operation. It should also have a callback function that grabs the input and runs an operation using the input as a payload; thus when called, rl.question()
will resume the input stream if it has been paused.
Note, the callback passed to rl.question
does not follow the typical pattern of accepting an ‘err & data’ as arguments, rather it’s called with the provided answer (user-input) as the only argument. Lets see how that’s done:
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("Search for a file..\n", (input) => {
const userInput = input.trim();
search(userInput); // the 'search function is called at this point
get(); // the 'get function is called after the search
log(`You searched for ${userInput}`);
rl.close();
});
rl.on("close", () => {
log("Searching....");
});
To execute this file and get our data using our input, open the Terminal, using the node runtime we would execute the module by typing - node index.js
.
We would get a prompt that tells us to Search for a file…
.
Enter the keywords - blogs/text1.txt
, which is the name of the dummy files we created earlier (Don’t forget to include the .txt extension).
And we should get a satisfactory result of -
You searched for blogs/text1.txt
Searching....
// data is output after some secs.
After a moment you should get your file served to you beautifully on the Terminal, hurray.
Summary:
Now, let’s have a summary of everything we’ve done so far. All we did was use a fancy syntax to get our users input via the readline
interface. A function is executed that uses the readFile
and readdir
method to search for and beautifully return our files content on the Terminal.
If you made it to the end of this article, Congrats! Now you have a task of making this even more better and elegant than I could.
Keep learning, keep discovering and keep sharing.
Ping my Twitter handle when you can- Patrick
Top comments (0)