DEV Community

Cover image for My first serious project in NodeJS: ToRead CLI
Leonardo Teteo
Leonardo Teteo

Posted on

My first serious project in NodeJS: ToRead CLI

As an avid reader, I always have a big list of articles, principally about development, that I intend to read. Development is a fast world and every day more articles pile up coming from newsletters, Twitter, etc. I always looked for a tool where I could put my readings. Some of the apps I tried to use was Pocket, Flipboard, Feedly and other less specialized such as Trello, Google Keep, etc. None of them really satisfied me, the features I wanted to have such as searching by tags and title, archive articles, etc. were offered by these services, but under subscription. As a developer I understand the costs related to an application, but it was not an application important enough to make me subscribe it. Then, I resorted to the greatest advantage of a developer: if you don't like the applications in the market, build your own!

The project is still in an early stage, the features I planned are not all developed yet, all contributions are welcome on Github! :D

Here I will explain a little bit about the structure of the code. This is my first "serious" project in NodeJS, before that I only wrote some scripts to learn and practice the language. It was also the first time I was able to decently unite NodeJS and TypeScript, a language I am also learning that I appreciate very much. Besides TypeScript, the project has the following main dependencies:

  • Babel
  • Jest
  • Rx-Http-Request
  • JSDOM
  • opn
  • Commander.js
  • RxJS
  • chalk

Some of them are very straight-forward and others I will explain my decision throughout the text. Two projects helped me a lot: Taskbook and TypeScript Babel Starter. The first one was the inspiration for this project and some dependencies and design decisions were made based on it. The second was very helpful to me to understand the structure of the project and how to configure Babel to do the job. Many thanks for both!

The project so far has been divided in 5 .ts files each having a separate role. I am trying to divide the responsibilities as much as possible to facilitate expansion and understandability. The first file is index.ts, the main entrance of the application. Using Commander.js I describe all commands in this file, for example the command to list all articles:

Commander
    .command('list')
    .alias('ls')
    .description('List all articles')
    .action(() => {
        Actions.getArticles();
    });

Some of the commands, of course, are more complex and have arguments, but the structure is basically the same and all lead to a method in the Actions class, which leads us to the next file: actions.ts

The actions.ts has the static class Actions, which, as the name implies, implements all the actions of applications such as get the articles, open an article, save an article, etc. For example, above we have Actions.getArticles(), which we can see in detail below:

static storage:Storage = new Storage();

static getArticles() : void{
        let articles:Article[] = this.storage.getArticles();
        articles.forEach(a => {
            Display.printArticle(a, PresentationMode.LIST);            
        });
    }

Generally a method in the Actions class figures classes from the other three files that compose the application: article.ts, storage.ts and display.ts, all of them have very straightforward names. First, the easiest one, article.ts just contains the interface representing an article:

export interface Article{
    id?:number,
    title:string,
    url:string,
    description?:string,
    tags?:string[],
}

The storage.ts is where the Storage class stays, this class is responsible to write the data in a JSON file, my intention was to do something very lightweight, also inspired on the Taskbook project I mentioned. Below a snippet of the class:

    prepareDB(){
        if(!fs.existsSync("file.json")){
            let file : FileStructure = {articles: [], index: 0}
            fs.writeFileSync("file.json", JSON.stringify(file));
        }
    }

    getArticles(): Article[] {
        this.prepareDB();

        let file:FileStructure = JSON.parse(fs.readFileSync("file.json", "utf8"));
        return file.articles;
    }

prepareDB() is always called to create the JSON file if it doesn't exist. And the the rest of the class has methods to do CRUD, for example the getArticles() method above. The entire Storage class is basically depended upon fs library and the JSON constant. Not a single fancy outside dependency is necessary, really, although I plan to improve it, put cryptography if necessary, among other things.

Finally, the display.ts contains the Display class, responsible for everything related to printing on the screen. It uses chalk to get it colorful. As a simple example here is the method that prints an error message:

static printOpenErrorMessage(){
        let message = 'The article was not found. Verify the ID of the article.';
        console.info(chalk`{red.bold ${message}}`);
    }

As I've said before, separation of concerns was the main goal in the infrastructure and sometimes I think that I separated way too much, but I'm good with the way it is going right now. As for the classes and methods itself, I tried to write the code with as less dependencies as possible and as simple as possible, even more so when I'm still learning. Now is a great time to explain some of the dependencies that are still lacking explanation. RxJS and JSDOM, for example, are used when saving a new article in the code below:

static saveArticle(url: string, description: string, tags?: string) : void{

        RxHR.get(url).subscribe(
            (data:any) => {
                if (data.response.statusCode === 200) {
                    let window = (new JSDOM(data.body)).window;
                    let title = window.document.title;
                    let article:Article = {
                        title: title, 
                        url: url,
                        description: description,
                        tags: tags ? tags.split(',') : []
                    };

                    Actions.storage.saveArticle(article);

                    Display.printSaveArticleMessage(data.response.statusCode);
                    Display.printArticle(article, PresentationMode.ONE);
                } else {
                    Display.printSaveArticleMessage(data.response.statusCode);
                }
            },
            (err:any) => console.error(err) // Show error in console
        );
    }

As depicted above, I use RxJS, RxHR and JDOM to make a request to the URL given by the user, get the title of the page and store the article with these information. For me it was the only time it was necessary to RxJS in the entire application, but other opportunities may arise.

Finally, on the testing end I'm using Jest, which I discovered while developing the application and I found if very straightforward in the way the tests and conducted. Maybe it is more functional than what I'm used to in Java, but it still reminds me the way JUnit is used, so it was a smooth sailing using it. An example of test is below:

test('write', () => {    
    let storage = new Storage();
    storage.saveArticle({title: "Teste", url: "http://www.example.com", description: "Description test"})
    expect(fs.existsSync("file.json")).toBe(true);
    fs.unlinkSync("file.json");
});

It has been a great experience to develop this project and I'm looking forward to the opinions of everybody on how I can improve it. Since it was developed as practice in mind I really didn't think about publishing it on NPM, but who knows what the future holds... What do you guys think? Let me know everything!

Top comments (8)

Collapse
 
rattanakchea profile image
Rattanak Chea

Is this purely CLI app? Are there any UIs in plan?

Collapse
 
leoat12 profile image
Leonardo Teteo

I made a CLI app because of two main reasons:
1) The project that inspired me, Taskbook, was a CLI.
2) A CLI was the easiest way to practice NodeJS and TypeScript without worrying too much about UI.
I thought about doing it as a web app, the API wouldn't be a problem, but the UI would take a long time, principally because I'm still green in web design.
I can do this in the future though, if possible using serverless, which is another topic I have great interest.

Collapse
 
rattanakchea profile image
Rattanak Chea

Thanks for more info. Who is the target user you have in mind for initial stage, and later stage?

Thread Thread
 
leoat12 profile image
Leonardo Teteo

Initially the target user is developers since a CLI is much more friendly to a developer or IT professionals. But with a future web application, it will probably expand to anybody who reads a lot, not only IT articles, but any article.

Collapse
 
rivanmota profile image
Rivan

Great article Leo 😃
Good job.

Collapse
 
leoat12 profile image
Leonardo Teteo

Thank you! I was doubting that it could be the same person. hahaha

Collapse
 
athif23 profile image
At Indo

I am interested with this. Because, I actually want to make something like this a while ago.

Collapse
 
leoat12 profile image
Leonardo Teteo

This is an interesting case indeed. It looks like a todo list, that's why I put the imagem I put, but it was kind of different as well. Feel free to suggest changes and improvements, it can become something really cool! :D