Créez un outil CLI Task Manager avec Node.js et MongoDB

Apprenez à créer un outil CLI (Command Line Interface) de gestionnaire de tâches avec Node.js et MongoDB. Ce didacticiel couvrira les bases de la création d'un outil CLI, du stockage des données dans MongoDB et de la mise en œuvre des opérations CRUD de base.

Bonjour à tous 👋 Dans ce tutoriel, vous apprendrez à créer un outil simple Task Manager CLI (Command Line Interface). Cela signifie que vous pouvez utiliser des commandes pour créer, afficher, mettre à jour ou supprimer vos tâches.

Nous allons construire cet outil CLI en utilisant NodeJS . Nous utiliserons également MongoDB comme base de données pour stocker toutes nos tâches. Enfin, nous utiliserons quelques packages utiles de npm :

  • commander: Cela nous aide à construire l'outil CLI.
  • chalk: Cela rend les messages dans le terminal colorés et faciles à lire.
  • inquirer: Cela nous permet de demander des informations à l'utilisateur.
  • ora: Cela permet au terminal d'afficher de belles animations de rotation.

Avant de plonger dans le vif du sujet, je veux que vous sachiez que vous pouvez trouver le code complet de ce projet sur GitHub. Si vous n'êtes pas sûr de quelque chose dans le code, vous pouvez toujours vous y référer à la version finale.

Table des matières:

  • Configuration du projet
    1. Comment créer le package.jsonfichier
    2. Comment installer les dépendances
    3. Comment convertir les modules CommonJS en modules ES
    4. Comment créer la structure des dossiers
  • Comment se connecter à la base de données
    1. Comment obtenir une chaîne de connexion MongoDB
    2. Code de connexion à la base de données
  • Comment créer un modèle de mangouste
  • Travailler sur les opérations CRUD
    1. Comment créer des Todos
    2. Comment lire des Todos
    3. Comment supprimer des Todos
    4. Comment mettre à jour des Todos
  • Comment écrire le point d'entrée CLI à l'aide de Commander
  • Comment tester l'outil CLI
  • Conclusion

Configuration du projet

Bienvenue dans la première section de ce manuel ! Ici, nous allons mettre en place notre projet.

Cela implique quelques étapes simples : créer un nouveau répertoire, configurer le package.jsonfichier et installer les packages npm nécessaires tels que chalk, Inquirer, Commander et d'autres dont nous parlerons bientôt. Nous organiserons également le projet en créant des dossiers.

Avant de plonger dans le vif du sujet, assurons-nous que NodeJS est installé sur votre système. Vous pouvez obtenir la dernière version LTS sur ce site Web : https://nodejs.org/en .

Pour vérifier si Node est correctement installé, tapez cette commande : node --version. Si vous voyez un numéro de version, vous êtes prêt ! Sinon, vous devez résoudre les erreurs.

Une fois NodeJS opérationnel, créez un nouveau dossier nommé « todo ». Vous pouvez utiliser votre éditeur de code préféré (je préfère Visual Studio Code) ou suivre ces étapes dans votre terminal :

  1. Créez un nouveau dossier :mkdir todo
  2. Allez dans le dossier :cd todo
  3. Ouvrez-le dans votre éditeur de code :code .

Comment créer le package.jsonfichier

La première étape consiste à configurer le package.jsonfichier. Mais ne vous inquiétez pas de le faire manuellement. Vous pouvez gagner du temps en utilisant cette commande :

npm init --yes

Une fois cette étape réalisée, passons à l'étape suivante et récupérons toutes les choses nécessaires à notre projet.

Comment installer les dépendances

Pour construire ce projet, nous aurons besoin de quelques packages. Exécutez simplement cette commande simple pour les obtenir tous :

npm i commander inquirer chalk ora mongoose nanoid dotenv

Comment convertir des modules CommonJS en modules ES

Avant de continuer, apportons une petite modification au package.jsonfichier. Supprimez cette ligne : "main": "index.js", et ajoutez ces deux lignes à la place :

"exports": "./index.js",
 "type": "module",

Avec ces changements, nous convertissons notre projet de modules CommonJS en modules ES. Cela signifie que nous utiliserons importà la place de require()pour introduire des modules, et exportà la place de module.exportspour partager des éléments entre fichiers.

Comment créer la structure des dossiers

Maintenant, organisons notre projet en mettant en place une structure de dossiers intelligente. Cela signifie que nous créerons des dossiers pour contenir soigneusement nos fichiers JavaScript. Cette étape est vraiment importante. Cela rend les choses faciles à gérer et se développe en douceur.

Nous créons 3 dossiers et 2 fichiers dans le dossier principal :

Premier dossier : commands . Dans ce dossier, vous allez créer 4 fichiers. Les noms des fichiers et la description du code qu'ils contiendront sont mentionnés ci-dessous :

  • addTask.js: Code pour créer une nouvelle tâche.
  • deleteTask.js: Code pour supprimer une tâche.
  • readTask.js: Code pour afficher toutes les tâches.
  • updateTask.js: Code de mise à jour d'une tâche.

Deuxième dossier : db . Dans ce dossier, ajoutez un fichier nommé connectDB.js. Ce fichier contiendra le code pour se connecter à la base de données MongoDB et se déconnecter en cas de besoin.

Troisième dossier : schema . À l’intérieur, créez un fichier nommé TodoSchema.js. Ce fichier stocke le schéma et le modèle Mongoose. Fondamentalement, un plan pour nos tâches, voilà à quoi ressembleront nos tâches.

Premier fichier : .env . Créez ce fichier dans le répertoire racine/dossier principal du projet. C'est ici que vous placerez votre chaîne de connexion MongoDB.

Deuxième fichier : Créez le index.jsfichier dans le répertoire racine lui-même qui servira de point d'entrée à notre projet. C'est comme la façade du projet – là où tout commence.

Une fois que nous avons terminé, les dossiers de votre projet devraient ressembler à ceci :

Image montrant la structure des dossiers du projet

Structure des dossiers du projet Comment se connecter à la base de données

Maintenant que vous avez mis en place le projet avec succès, il est temps de passer à la partie passionnante.

Comment obtenir une chaîne de connexion MongoDB

Pour garder une trace de toutes nos tâches, nous avons besoin d’un endroit pour les stocker. C'est là qu'intervient MongoDB Atlas. C'est comme un service spécial qui gère les bases de données pour nous. La meilleure partie? Vous pouvez commencer à l’utiliser gratuitement (aucune carte de crédit requise).

Pour vous y connecter, tout ce dont vous avez besoin est ce qu'on appelle une chaîne de connexion. Si MongoDB Atlas est nouveau pour vous, ne vous inquiétez pas. Consultez cet article facile à suivre : Tutoriel MongoDB Atlas - Comment démarrer . Il vous donne juste assez d'informations pour commencer à utiliser Atlas. Une fois que vous aurez terminé, vous saurez comment obtenir ce dont vous avez besoin, y compris la chaîne de connexion.

Une fois que vous avez cette chaîne de connexion, créez un nouvel élément appelé « variable d'environnement ». C'est comme un code secret utilisé par votre projet. Ouvrez le .envfichier et créez une ligne comme celle-ci : MONGO_URI=. Après le =, entrez votre chaîne de connexion.

N'oubliez pas : remplacez <password>par votre mot de passe réel et <username>par le nom d'utilisateur de votre administrateur de base de données dans la chaîne de connexion. Ajoutez également todosbetween /?dans la chaîne. Lorsque vous avez terminé, votre .envfichier devrait ressembler à ceci :

MONGO_URI=mongodb+srv://<username>:<password>@cluster0.k5tmsld.mongodb.net/todos?retryWrites=true&w=majority

Code de connexion à la base de données

Passons maintenant au code qui connecte notre outil à la base de données MongoDB. Ouvrez le ./db/connectDB.jsfichier et écrivons du code pour établir cette connexion.

Tout d’abord, nous devons importer le dotenvpackage que nous avons récupéré plus tôt lors de la configuration du projet et appeler la config()méthode sur dotenv. Cela nous aide à charger les variables d'environnement à partir du .envfichier. Voici comment procéder :

import dotenv from 'dotenv'
dotenv.config()

Ensuite, nous souhaitons importer quelques packages supplémentaires que nous utiliserons ici. Ce sont mongoose, oraetchalk :

import mongoose from 'mongoose'
import ora from 'ora'
import chalk from 'chalk'

Remarque : mongoose est une bibliothèque de modélisation de données objet (ODM) pour MongoDB. Il fournit une abstraction de niveau supérieur facilitant l'ajout, la lecture, la mise à jour et la suppression d'éléments de la base de données MongoDB.

Passons maintenant à l’action réelle. Nous définirons ici deux fonctions : connectDB()et disconnectDB().

La connectDB()fonction contiendra le code pour aider à connecter notre application NodeJS à la base de données M0ngoDB à l'aide de mongoose. C'est comme un appel téléphonique qui les connecte. Si nous n'établissons pas d'abord une connexion, notre application ne pourra pas interagir avec la base de données et effectuer les différentes opérations CRUD.

La disconnectDB()fonction fait le contraire. C'est comme raccrocher le téléphone une fois que notre application a fini de parler à la base de données. Si nous ne nous déconnectons pas, c'est comme si nous maintenions l'appel même après avoir terminé.

Ne pas nous déconnecter de la base de données une fois que nous avons fini d'interagir avec elle pourrait entraîner des fuites de ressources. Cela peut entraîner un ralentissement ou un blocage de votre application au fil du temps.

Laissez-moi vous montrer le code des deux fonctions :

export async function connectDB(){
    try {
        const spinner = ora('Connecting to the database...').start()
        await mongoose.connect(process.env.MONGO_URI)
        spinner.stop()
        console.log(chalk.greenBright('Successfully connected to database!!!'))   
    } catch (error) {
        console.log(chalk.redBright('Error: '), error);
        process.exit(1) 
    }
}

export async function disconnectDB(){
    try {
        await mongoose.disconnect()
        console.log(chalk.greenBright('Disconnected from the database.'))
    } catch(err) {
        console.log(chalk.redBright('Error: '), error);
        process.exit(1) 
    }
}

Cela fait beaucoup de code à digérer en même temps, alors laissez-moi vous expliquer ceci :

Dans la connectDB()fonction, la ligne mongoose.connect(process.env.MONGO_URI)nous aide à nous connecter à la base de données à l'aide de la chaîne de connexion.

Vous vous souvenez du .envfichier ? Nous utilisons ses informations ici. Pour charger la MONGO_URIvariable, nous utilisons le dotenvpackage et appelons la config()fonction, puis nous pouvons y accéder en utilisant process.env.MONGO_URI.

Puisque mongoose.connect()renvoie une promesse, nous utilisons le awaitmot-clé qui le précède pour nous assurer de ne procéder que lorsque cette promesse renvoyée est résolue.

Il est possible de rencontrer des erreurs lors de l'exécution de ce code, nous avons donc enveloppé l'intégralité du code dans un try...catch()bloc pour nous assurer que toutes les erreurs qui apparaissent sont traitées correctement dans le catch()bloc.

Le orapackage nous aide à afficher un spinner pendant que nous nous connectons à la base de données. Une fois connecté avec succès, nous arrêtons le spinner et affichons un message de joie en vert en utilisant chalk.

Si vous remarquez, nous faisons la même chose dans la disconnectDB()fonction. Mais au lieu de nous connecter, nous nous déconnectons de la base de données en utilisant mongoose.disconnect(). Nous l'enveloppons dans un bloc try-catch similaire, et encore une fois nous affichons des messages colorés en utilisant chalk.

Nous utilisons exportavant ces fonctions pour permettre à d'autres parties du projet de les utiliser. N'oubliez pas d'ajouter ces deux lignes temporaires à la fin du fichier pour l'instant :

connectDB()
disconnectDB()

Maintenant, vous pouvez exécuter le connectDB.jsfichier à l'aide de la commande : node ./db/connectDB.jset attendez-vous à voir ceci dans la console :

GIF montrant les messages de sortie affichés dans le terminal lorsque le fichier connectDB.js est exécuté

Sortie vue sur le terminal lorsque connectDB.jsle fichier est exécuté. Il montre comment notre code se connecte avec succès à la base de données et s'en déconnecte, affichant les messages de console appropriés lorsque nous invoquons les méthodes connectDB()et .disconnectDB()

La connexion à la base de données est une grande étape, mais vous faites de grands progrès ! Avant de continuer, assurez-vous de supprimer les 2 lignes que vous avez ajoutées à la fin car elles ont été ajoutées uniquement pour vérifier si nos fonctions de connexion et de déconnexion fonctionnent comme prévu.

Comment créer un modèle de mangouste

Un modèle Mongoose est comme un outil qui nous aide à communiquer avec la base de données. Avec lui, nous pouvons facilement effectuer des choses comme ajouter, lire, mettre à jour et supprimer des tâches. C'est comme un assistant utile qui comprend comment communiquer avec la base de données.

Pour créer ce modèle, nous avons besoin de quelque chose appelé un schéma. Il définit essentiellement à quoi devrait ressembler chaque tâche. Considérez-le comme un plan ou un ensemble d'instructions qui guident la manière dont chaque tâche est créée, les informations qu'elle doit contenir et la manière dont ces informations sont organisées. C'est comme définir des règles sur la façon dont nos tâches sont stockées dans la base de données.

Nous allons construire ce schéma dans le ./schema/TodoSchema.jsfichier. Ouvrez-le et plongeons-y. Tout d’abord, nous avons besoin de deux outils spéciaux : mongooseet nanoid. Nous utiliserons nanoidpour créer des identifiants courts et uniques pour chaque tâche.

Tapez ces lignes pour importer les outils :

import mongoose from 'mongoose'
import {nanoid} from 'nanoid'

Maintenant, nous utilisons la mongoose.Schema()méthode pour créer notre schéma. Voici le code correspondant :

const TodoSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true
    },
    detail: {
        type: String,
        required: true,
        trim: true
    },
    status: {
        type: String,
        required: true,
        enum: ['completed', 'pending'],
        default: 'pending',
        trim: true
    },
    code: {
        type: String,
        required: true,
        default: 'code',
        trim: true
    }
}, {timestamps: true})

Toute tâche créée à l'aide de ce schéma aura les propriétés suivantes :

  • name: Ceci est un titre court pour la tâche. Le type: Stringsouligne qu'il ne peut s'agir que de texte (une chaîne). Le required: trueprécise que nous devons fournir cela lors de la création d'une tâche et le trim: trueprécise que tout espace supplémentaire au début ou à la fin du nom de la tâche sera supprimé avant de l'enregistrer dans la base de données.
  • detail: Ceci est une description de la tâche. Il possède exactement les mêmes propriétés que name.
  • status: Cela montre si la tâche est terminée ou non. La enum: ['completed', 'pending']propriété spécifie qu'il ne peut s'agir que completedde ou pending. La default: 'pending'propriété spécifie que si vous ne définissez pas la statuspropriété lors de la création de la tâche, elle est supposée être pending.
  • code: Il s'agit d'un identifiant court et unique pour la tâche. Nous lui donnons une valeur par défaut de code. Cette valeur n'est qu'un espace réservé et n'a aucune signification réelle en termes d'identification de la tâche. Ne vous inquiétez pas, nous le changerons bientôt.
  • Il {timestamps: true}s'agit d'une option de configuration qui ajoute automatiquement des champs d'horodatage comme createdAtet updatedAtaux tâches lorsqu'elles sont créées ou modifiées.

Nous avons réussi à définir notre schéma – mais vous vous demandez peut-être si la codepropriété était censée être unique pour chaque tâche. Actuellement, il stocke la même valeur, c'est-à-dire le « code », pour chaque tâche. Ne vous inquiétez pas, nous allons résoudre ce problème. Ajoutez ce code à la fin :

TodoSchema.pre('save', function(next){
    this.code = nanoid(10)
    next()
})

Ici, cela TodoSchema.pre('save', function(){....})nous aide à définir un crochet/une fonction de pré-enregistrement qui s'exécute à chaque fois avant qu'une tâche ne soit enregistrée dans la base de données.

À l'intérieur de la fonction, nous utilisons nanoid(10)pour créer un identifiant unique de 10 caractères pour la tâche et mettons cet identifiant généré dans le codechamp de la tâche (nous pouvons en fait accéder à n'importe quelle propriété/champ de la tâche en utilisant le thismot-clé).

La dernière ligne de code : next()indique essentiellement à l'ordinateur que nous avons terminé et qu'il peut enfin enregistrer le document maintenant. Avec cela, nous générons un identifiant unique pour chaque tâche créée à l'aide du nanoidpackage.

Enfin, nous allons créer un Todosmodèle en utilisant ce TodoSchemaplan et l'exporter. C'est ainsi:

const Todos = mongoose.model('Todos', TodoSchema)
export default Todos

Et voilà ! Nous avons construit notre schéma et notre modèle. Passons maintenant à la section suivante de ce didacticiel.

Comment travailler sur les opérations CRUD

Félicitations pour avoir suivi avec succès jusqu'ici. Jusqu'à présent, nous avons fait 3 choses :

  1. Nous avons mis en place le projet
  2. Nous nous sommes connectés à la base de données MongoDB, et
  3. Nous avons créé le modèle Mongoose

Ensuite, nous travaillerons sur les différentes opérations CRUD telles que la création, la lecture, la mise à jour et la suppression des tâches de notre base de données.

Comment créer des tâches

Commençons maintenant par créer des tâches dans notre projet. Au début, j'avais prévu un processus simple dans lequel vous ajoutez une tâche à la base de données à la fois. Cela signifie que lorsque vous demandez à l'outil de créer une tâche, il vous demande une fois les détails de la tâche, comme le nom et la description, puis enregistre la tâche.

Mais ensuite j’ai réalisé : que se passe-t-il si quelqu’un souhaite ajouter rapidement de nombreuses tâches ? Le faire un par un n’est pas cool. Il y a deux problèmes :

  1. Si vous avez, disons, 5 tâches en tête, vous devrez taper la commande create 5 fois, une pour chaque tâche.
  2. Après avoir saisi les détails de la tâche, vous attendez un peu car l'enregistrement des éléments dans la base de données peut prendre du temps, surtout si Internet est lent.

Ces problèmes ne sont pas amusants du tout ! Pour résoudre ce problème, nous avons besoin d'un moyen d'ajouter plusieurs tâches en une seule fois. Voici comment nous allons procéder :

Après avoir saisi le nom et la description de la tâche, nous vous demanderons si vous souhaitez ajouter d'autres tâches. Si vous saisissez oui, nous poursuivons le processus depuis le début (en vous demandant de saisir à nouveau le nom et la description de la tâche suivante). Mais si vous entrez non, le processus de pose de questions s'arrêtera et toutes les tâches saisies seront enregistrées ensemble dans la base de données. De cette façon, vous pouvez créer de nombreuses tâches sans avoir à les exécuter une par une. Il s’agit avant tout de rendre les choses fluides pour vous.

Nous allons écrire du code dans le ./commands/addTask.jsfichier. C'est là que la magie opère. Décomposons-le étape par étape :

Tout d’abord, nous importons les packages et fonctions nécessaires que nous avons créés précédemment. Vous pouvez ajouter ces lignes de code pour ce faire :

import inquirer from "inquirer";
import { connectDB, disconnectDB } from '../db/connectDB.js'
import Todos from "../schema/TodoSchema.js";
import ora from "ora";
import chalk from "chalk";

Maintenant, nous créons une fonction asynchrone appelée input()pour recueillir le nom et les détails de la tâche auprès de l'utilisateur. Voici comment ça se passe :

async function input(){
    const answers = await inquirer.prompt([
        { name: 'name', message: 'Enter name of the task:', type: 'input' },
        { name: 'detail', message: 'Enter the details of the task:', type: 'input' },
    ])

    return answers
}

En termes simples, input()permet inquirerde demander à l'utilisateur le nom et les détails de la tâche. Les réponses sont ensuite renvoyées sous forme d'objet.

Mais attendez, vous vous demandez peut-être ce qui inquirer.prompt()se passe. C'est une méthode du inquirerpackage qui pose des questions et attend des réponses. Vous fournissez un tableau d'objets de question, chacun contenant des détails tels que le message à afficher à l'utilisateur et le type de question. La fonction renvoie une promesse, nous attendons donc awaitles réponses de l'utilisateur qui sont renvoyées sous forme d'objet.

Voici { name: 'name', message: 'Enter name of the task:', type: 'input' }la première question qui sera posée à l’utilisateur. La messagepropriété contient la question qui sera affichée à l'utilisateur. Dans notre cas, il s'agit de : Enter the name of the task. L'utilisateur sera invité à saisir du texte (une chaîne), puisque cette question est de type: 'input'. Cela name: 'name'signifie que la réponse de l'utilisateur à cette question sera attribuée à une propriété nommée – namedans l'objet de la réponse.

L'objet suivant est la deuxième question qui sera posée à l'utilisateur. Dans ce cas, un message s'affichera dans le terminal : Enter the details of the tasket la réponse de l'utilisateur sera affectée à une propriété appelée detaildans l'objet de la réponse.

Pour voir comment fonctionne le code ci-dessus, vous pouvez ajouter ces 2 lignes de code à la fin du fichier :

const output = await input()
console.log(output)

Maintenant, enregistrez le fichier et exécutez le code à l'aide de la commande : node ./commands/addTask.js. Voici ce que vous verrez lorsque vous exécuterez le code :

Image du terminal montrant ce que la fonction `inquirer.prompt()` renvoie en sortie.

Sortie vue sur le terminal lorsque nous invoquons simplement la input()méthode et exécutons le code. Il montre comment inquirer.jsrenvoie les réponses de l'utilisateur après le processus de pose de questions.

Nous pouvons maintenant procéder au reste du code et vous pouvez supprimer les 2 dernières lignes que vous venez d'ajouter.

Créons maintenant une fonction nommée askQuestions()pour rassembler plusieurs tâches. Voici à quoi ça ressemble :

const askQuestions = async() => {

    const todoArray = []
    let loop = false

    do{
        const userRes = await input()
        todoArray.push(userRes)
        const confirmQ = await inquirer.prompt([{ name: 'confirm', message: 'Do you want to add more tasks?', type: 'confirm' }])
        if(confirmQ.confirm){
            loop = true
        } else {
            loop = false
        }
    } while(loop)

    return todoArray
}

Dans askQuestions(), nous mettons en place une boucle qui continue de demander des tâches jusqu'à ce que l'utilisateur décide d'arrêter. Nous rassemblons chaque tâche de l'utilisateur en appelant la input()fonction, et la réponse de l'utilisateur renvoyée est transmise au fichier todoArray.

Ensuite, nous demandons si l'utilisateur souhaite ajouter d'autres tâches à l'aide d'une question de confirmation. S'ils disent oui, nous passons loopà trueet la boucle continue – sinon, loopcela devient falseet la boucle se termine. Enfin, nous renvoyons le tableau des tâches, c'est-à-dire todoArray.

Vous pouvez tester cela en ajoutant ces lignes de code à la fin du fichier :

const output = await askQuestions()
console.log(output)

Lorsque vous exécutez le fichier en utilisant node ./commands/addTask.js, vous verrez un résultat similaire à celui que vous voyez ici :

Image du terminal montrant le tableau de tâches/todos renvoyé par la fonction `askQuestions()`

Sortie vue sur le terminal lorsque nous invoquons la askQuestions()méthode et exécutons le code. Il affiche le tableau de tâches renvoyées par la méthode lorsque l'utilisateur ne souhaite pas continuer à ajouter d'autres tâches.

Nous y sommes presque! Avant de continuer, n'oubliez pas de supprimer les 2 dernières lignes que vous venez d'ajouter. Après avoir fait cela, passons à autre chose.

Jusqu’à présent, nous avons réussi à collecter toutes les tâches que l’utilisateur souhaite créer.

Maintenant, définissons la dernière pièce du puzzle : la addTask()fonction. Cette fonction rassemble le tout et complète le processus de création de tâches. Voici le code complet :

export default async function addTask() {
    try {
        // calling askQuestions() to get array of todo's
        const userResponse = await askQuestions()

        // connecting to the database
        await connectDB()

        // Displaying a spinner with the following text message using ora 
        let spinner = ora('Creating the todos...').start()

        // looping over every todo in the userResponse array
        // and saving each todo in the database
        for(let i=0; i<userResponse.length; i++){
            const response = userResponse[i]
            await Todos.create(response)
        }

        // Stopping the spinner and displaying the success message
        spinner.stop()
        console.log(
            chalk.greenBright('Created the todos!')
        )

        // disconnecting the database
        await disconnectDB()
    } catch (error) {
        // Error Handling
        console.log('Something went wrong, Error: ', error)
        process.exit(1)
    }
}

La addTask()fonction commence par appeler la askQuestions()fonction pour rassembler le tableau de tâches et l'attribuer à la userResponsevariable. Ensuite, il se connecte à la base de données à l'aide de connectDB(), affiche une double flèche utilisant orapour montrer le processus de création de tâche, parcourt chaque tâche du tableau et l'enregistre dans la base de données à l'aide de Todos.create(response).

Une fois que toutes les tâches sont enregistrées, le spinner s'arrête, un message de réussite s'affiche, puis il se déconnecte de la base de données à l'aide de disconnectDB().

L'intégralité du code est enveloppée dans un try...catchbloc pour gérer correctement les erreurs potentielles.

Avec ce code, vous avez terminé le processus de création de tâches. Bon travail! Il s’agissait probablement du morceau de code le plus complexe de tout le projet. Les opérations futures telles que les tâches de lecture, de suppression et de mise à jour seront assez simples et faciles en comparaison. Cela dit, passons à l'exécution de l'opération de lecture.

Comment lire Todos

Nous allons maintenant explorer comment lire les tâches de la base de données MongoDB. Le processus est simple et je vais vous guider à travers l'intégralité du code du ./commands/readTask.jsfichier :

Commençons par importer les packages et fonctions nécessaires au début du fichier :

// Importing packages and functions
import { connectDB, disconnectDB } from '../db/connectDB.js'
import Todos from '../schema/TodoSchema.js'
import chalk from 'chalk'
import ora from 'ora'

Maintenant, définissons une fonction asynchrone nommée readTask()qui encapsule la logique de lecture des tâches. La fonction entière est enveloppée dans un bloc try...catch pour gérer les erreurs potentielles :

export default async function readTask(){
    try {
        // connecting to the database
        await connectDB()

        // starting the spinner
        const spinner = ora('Fetching all todos...').start()

        // fetching all the todos from the database 
        const todos = await Todos.find({})

        // stopping the spinner
        spinner.stop()

        // check if todos exist or not
        if(todos.length === 0){
            console.log(chalk.blueBright('You do not have any tasks yet!'))
        } else {
            todos.forEach(todo => {
                console.log(
                    chalk.cyanBright('Todo Code: ') + todo.code + '\n' + 
                    chalk.blueBright('Name: ') + todo.name + '\n' + 
                    chalk.yellowBright('Description: ') + todo.detail + '\n'
                )
            })
        }

        // disconnect from the database
        await disconnectDB()
    } catch (error) {
        // Error Handling
        console.log('Something went wrong, Error: ', error)
        process.exit(1)
    }
}

readTask()

Maintenant, décomposons le code étape par étape :

  1. Nous établissons une connexion à la base de données MongoDB en utilisant await connectDB().
  2. Nous démarrons un spinner en utilisant orapour indiquer que nous récupérons toutes les tâches.
  3. Nous récupérons toutes les tâches de la base de données en utilisant Todos.find({}). Une fois le processus terminé, la todosvariable contiendra soit un tableau vide (si aucune tâche n'existe dans la base de données), soit un tableau de tâches.
  4. Une fois la récupération terminée, nous arrêtons le spinner en utilisant spinner.stop().
  5. On vérifie s'il y a des tâches en vérifiant si todos.lengthest égal à 0. Si c'est le cas, on affiche un message en bleu disant "Vous n'avez pas encore de tâches !". S'il y a des tâches dans le tableau (ce qui signifie que la longueur du tableau n'est pas égale à 0), nous parcourons chaque tâche dans le tableau et imprimons son code, son nom et sa description en utilisant pour le formatage des couleurs chalk.
  6. Enfin, nous nous déconnectons de la base de données en utilisant await disconnectDB().

Dans la dernière ligne de code, nous appelons la readTask()fonction. Ceci est uniquement à des fins de test et vous pouvez supprimer cette ligne comme indiqué.

Pour exécuter le code, utilisez la commande : node ./commands/readTask.js. Lorsque vous exécutez ceci, vous verrez quelque chose de similaire au résultat affiché ici :

Remarque : j'avais déjà créé des tâches aléatoires. Ainsi, lorsque j'exécute le readTask.jsfichier, je vois ceci dans mon terminal :

GIF montrant la sortie affichée dans le terminal suite à l'exécution du fichier `readTask.js`

Sortie vue sur le terminal lorsque readTask.jsle fichier est exécuté. Il montre comment le code lit avec succès toutes les tâches de la base de données et les imprime dans le terminal.

Avant de continuer, n'oubliez pas de supprimer la dernière ligne de code du readTask.jsfichier car nous n'en aurons plus besoin à l'avenir.

Avec ce code, vous avez implémenté avec succès la fonctionnalité de lecture pour votre outil CLI de gestionnaire de tâches. Bon travail! Dans les sections à venir, nous explorerons comment supprimer et mettre à jour des tâches.

Comment supprimer des tâches

Cette section du didacticiel couvre le processus simple de suppression de tâches de la base de données. La logique est simple : les utilisateurs saisissent le code Todo de la tâche qu'ils souhaitent supprimer, et nous supprimons cette tâche de la base de données.

Examinons le code pour que cela se produise dans le ./commands/deleteTask.jsfichier.

La première étape consiste à importer les packages et fonctions nécessaires au début du fichier, notamment Inquirer, TodosModel, ConnectDB(), DisconnectDB(), Ora et Chalk.

// Importing packages and functions
import inquirer from "inquirer";
import Todos from '../schema/TodoSchema.js'
import {connectDB, disconnectDB} from '../db/connectDB.js'
import ora from "ora";
import chalk from "chalk";

Ensuite, nous définirons une fonction asynchrone appelée getTaskCode(). Le rôle de cette fonction est de demander à l'utilisateur de saisir le code de la tâche qu'il souhaite supprimer à l'aide de inquirer. La fonction coupe ensuite le code saisi par l'utilisateur à l'aide de la trim()méthode et renvoie le code coupé. Le processus de découpage est nécessaire pour supprimer les espaces de début ou de fin que le code pourrait contenir.

Voici le code de la getTaskCode()fonction :

export async function getTaskCode(){
    try {
        // Prompting the user to enter the todo code
        const answers = await inquirer.prompt([
            {name: 'code', 'message': 'Enter the code of the todo: ', type: 'input'},
        ])

        // Trimming user's response so that the todo code does not contain any starting or trailing white spaces
        answers.code = answers.code.trim()

        return answers
    } catch (error) {
        console.log('Something went wrong...\n', error)
    }
}

Nous allons maintenant définir la fonction principale nommée deleteTask(). Le code complet est ci-dessous :

export default async function deleteTask(){
    try {
        // Obtaining the todo code provided by user
        const userCode = await getTaskCode()

        // Connecting to the database
        await connectDB()

        // Starting the spinner
        const spinner = ora('Finding and Deleting the todo...').start()

        // Deleting the task
        const response = await Todos.deleteOne({code: userCode.code})

        // Stopping the spinner
        spinner.stop()

        // Checking the delete operation
        if(response.deletedCount === 0){
            console.log(chalk.redBright('Could not find any todo matching the provided name. Deletion failed.'))
        } else {
            console.log(chalk.greenBright('Deleted Task Successfully'))
        }

        // Disconnecting from the database
        await disconnectDB()
    } catch (error) {
        // Error Handling
        console.log('Something went wrong, Error: ', error)
        process.exit(1)
    }
}

Décomposons ce code étape par étape :

  1. On obtient l'objet de réponse qui inclut le code todo saisi par l'utilisateur en appelant la getTaskCode()fonction définie ci-dessus. Nous affectons ensuite cet objet à la userCodevariable.
  2. Nous nous connectons à la base de données en utilisant await connectDB().
  3. Nous démarrons un spinner en utilisant orapour indiquer que nous recherchons et supprimons la tâche.
  4. Nous utilisons Todos.deleteOne({ code: userCode.code })pour rechercher et supprimer la tâche avec un code correspondant. La réponse indiquera si un document a été supprimé ou non.
  5. Une fois l’opération terminée, nous arrêtons le spinner en utilisant spinner.stop().
  6. Nous utilisons une condition if...else pour vérifier la deletedCountpropriété dans la réponse. S'il vaut 0, nous imprimons un message indiquant que la tâche avec le code fourni n'a pas été trouvée et que la suppression a échoué. Si deletedCountest supérieur à 0, nous imprimons un message de réussite.
  7. Nous nous déconnectons de la base de données en utilisant await disconnectDB().

Si j'appelle la fonction : deleteTask()puis que j'exécute le code à l'aide node /commands/deleteTask.jsde la commande, je vois ceci dans ma console :

GIF montrant la sortie affichée dans le terminal suite à l'exécution du fichier `deleteTask.js`

Sortie vue sur le terminal lorsque deleteTask.jsle fichier est exécuté. Il montre comment le code supprime avec succès une seule tâche de la base de données.

Comme vous pouvez le voir dans le GIF ci-dessus, le code vous demandera de saisir un code Todo pour la tâche que vous souhaitez supprimer. Lors de la suppression, vous recevrez un message de confirmation dans la console. Lorsque nous lisons toutes nos tâches après le processus de suppression, nous ne voyons pas la tâche supprimée. Cela implique que notre code réussit à faire ce qu’il est censé faire !

Comment mettre à jour Todos

Dans cette section, nous examinerons le code pour mettre à jour une tâche spécifique. La mise à jour d'une tâche est un peu plus complexe que les opérations précédentes. Le processus se déroule comme suit :

  1. Inviter l'utilisateur à saisir le code de la tâche à mettre à jour.
  2. Connectez-vous à la base de données.
  3. Recherchez la tâche dont la propriété code correspond à l'entrée de l'utilisateur.
  4. Si la tâche n'existe pas, affichez un message indiquant l'échec de la recherche d'une tâche correspondante.
  5. Si la tâche existe, invitez l'utilisateur à mettre à jour le et namela tâche.descriptionstatus
  6. Si l'utilisateur définit la propriété d'état d'une tâche sur « terminé », cette tâche est supprimée. S'il est défini sur « en attente », le nom et la description de la tâche sont mis à jour dans la base de données.
  7. Afficher un message de réussite dans la console après l'opération de mise à jour.

Commençons à coder ! La première chose à faire est d’importer tous les packages et fonctions dont nous aurons besoin pour effectuer ce travail.

// Importing packages and functions
import {connectDB, disconnectDB} from '../db/connectDB.js'
import { getTaskCode } from './deleteTask.js'
import inquirer from 'inquirer'
import Todos from '../schema/TodoSchema.js'
import ora from 'ora'
import chalk from 'chalk'

Avant de commencer à travailler sur notre updateTask()fonction, nous allons créer une petite fonction dans le même fichier nommée askUpdateQ(). Le rôle de cette fonction est d'inviter l'utilisateur à saisir les valeurs mises à jour de la tâche, telles que le nom, la description et l'état de la tâche. À la fin, cette fonction renverra l'objet de réponse.

Voici le code correspondant :

async function askUpdateQ(todo){
    try {
        // Prompting the user to update the todo data
        const update = await inquirer.prompt([
            {name: 'name', message: 'Update the name?', type: 'input', default: todo.name},
            {name: 'detail', message: 'Update the Description?', type: 'input', default: todo.detail},
            {name: 'status', message: 'Update the status', type: 'list', choices: ['pending', 'completed'], default: todo.status}
        ])

        return update
    } catch (error) {
        console.log('Something went wrong... \n', error)
    }
}

Deux choses sont à noter ici :

  1. todoest l'objet de tâche d'origine (la tâche que l'utilisateur souhaite mettre à jour). Ceci sera transmis à la askUpdateQ()fonction par la updateTask()fonction.
  2. Chaque objet de question dans le tableau transmis inquirer.prompt()contient une propriété par défaut définie sur les valeurs d'origine de la tâche. Cela garantit que si l'utilisateur saute une question, la valeur par défaut reste inchangée.

Cela dit, regardons maintenant le code de la updateTask()fonction :

export default async function updateTask(){
    try {
        // Obtaining the task code entered by user by calling getTaskCode() method
        const userCode = await getTaskCode()

        // Connecting to the database
        await connectDB()

        // Starting the spinner
        const spinner = ora('Finding the todo...').start()

        // Finding the todo which the user wants to update
        const todo = await Todos.findOne({code: userCode.code})

        // Stopping the spinner
        spinner.stop()

        // Checking if the todo exists or not
        if(!todo){
            console.log(chalk.redBright('Could not find a Todo with the code you provided.'))
        } else{
            console.log(chalk.blueBright('Type the updated properties. Press Enter if you don\'t want to update the data.'))

            // Get the user's response of the updated data by calling askUpdateQ() method
            const update = await askUpdateQ(todo)

            // If user marked status as completed, we delete the todo else we update the data
            if(update.status === 'completed'){
                // Changing spinner text and starting it again
                spinner.text = 'Deleting the todo...'
                spinner.start()

                // Deleting the todo
                await Todos.deleteOne({_id : todo._id})

                // Stopping the spinner and display the success message
                spinner.stop()
                console.log(chalk.greenBright('Deleted the todo.'))
            } else {
                // Update the todo
                spinner.text = 'Updating the todo'
                spinner.start()
                await Todos.updateOne({_id: todo._id}, update, {runValidators: true})
                spinner.stop()
                console.log(chalk.greenBright('Updated the todo.'))
            }
        }
        // Disconnecting from the database
        await disconnectDB()
    } catch (error) {
        // Error Handling
        console.log('Something went wrong, Error: ', error)
        process.exit(1)
    }
}

Voici une ventilation du code ci-dessus :

  1. Obtenez le code de la tâche que l'utilisateur souhaite mettre à jour. Pour cela, nous utilisons la getTaskCode()fonction définie dans le ./commands/deleteTask.jsfichier. Nous appelons simplement la fonction et attribuons l'objet de réponse renvoyé à la userCodevariable.
  2. Connectez-vous à la base de données en utilisant await connectDB().
  3. Démarrez une roulette pour indiquer que le code trouve la tâche.
  4. Utilisez Todos.findOne({ code: userCode.code })pour rechercher la tâche que l'utilisateur souhaite mettre à jour et l'attribuer à la todovariable. Nous faisons cela parce que nous aurons besoin des valeurs originales de la tâche.
  5. Arrêtez la roulette.
  6. Si aucune tâche correspondante n'est trouvée, affichez un message chalkindiquant que la tâche n'a pas été trouvée.
  7. Si la tâche est trouvée, invitez l'utilisateur à saisir les propriétés mises à jour en appelant la askUpdateQ()fonction et en transmettant l' todoobjet (tâche d'origine) dans la fonction. Attribuez l'objet renvoyé à updatela variable.
  8. Si l'utilisateur marque le statut comme « terminé », la tâche est supprimée de la base de données à l'aide de deleteOne(). Si elle est marquée comme « en attente », le nom et la description de la tâche sont mis à jour à l’aide de updateOne().

    updateOne()La méthode prend en compte 3 paramètres : l'objet de requête, l'objet de mise à jour et l'objet Options. Voici {_id: todo._id}l'objet de requête. Mongoose recherche dans toute la collection une tâche dont idla propriété correspond à todo_.id. En trouvant la tâche, il remplace la tâche par l'objet de mise à jour, c'est updatedans notre cas. Le troisième paramètre, { runValidators: true }, garantit que Mongoose valide leupdates'opposer aux règles du schéma avant de l'exécuter. Si la validation échoue, la mise à jour sera rejetée et vous recevrez une erreur. Si la validation réussit, le document sera mis à jour avec succès dans la base de données.

    Dans le cas des opérations de suppression et de mise à jour, nous modifions le texte du spinner en utilisant spinner.textet le démarrons avant d'effectuer l'opération et une fois l'opération terminée, nous arrêtons le spinner.
  9. Affichez les messages de réussite appropriés dans la console en fonction de l'opération effectuée.
  10. Déconnectez-vous de la base de données en utilisant await disconnectDB().

Si j'appelle la updateTask()fonction et exécute le code à l'aide de la commande :node ./commands/updateTask.js , je vois quelque chose comme ceci dans ma console :

GIF montrant la sortie affichée dans le terminal suite à l'exécution du fichier `updateTask.js`

Sortie vue sur le terminal lorsque updateTask.jsle fichier est exécuté. Il montre comment le code récupère avec succès la tâche d'origine et la remplace avec succès par les valeurs mises à jour fournies par l'utilisateur.

Avec cela, vous avez implémenté avec succès toutes les opérations CRUD. Maintenant, utilisons la commanderbibliothèque pour tout rassembler et créer un outil CLI entièrement fonctionnel.

Comment écrire le point d'entrée CLI à l'aide de Commander

Dans les dernières étapes de notre projet, nous allons exploiter la puissance de la commanderbibliothèque pour créer une interface CLI conviviale. Avec commander, nous pouvons définir soigneusement différentes commandes – telles que lire, ajouter, mettre à jour et supprimer – de manière organisée et intuitive.

Notre code résidera dans le index.jsfichier, qui sert de point d'entrée à notre application. Ci-dessous le code complet :

#!/usr/bin/env node

// Importing the required functions for each command
import addTask from './commands/addTask.js'
import deleteTask from './commands/deleteTask.js'
import readTask from './commands/readTask.js'
import updateTask from './commands/updateTask.js'

// Importing the Command class from Commander.js library
import { Command } from 'commander'

// Creating an instance of the Command class
const program = new Command()

// Setting the name and description of the CLI tool
program
.name('todo')
.description('Your terminal task manager!')
.version('1.0.0')

// Defining a command called 'add'
program
.command('add')
.description('Create a new todo.')
.action(addTask)

// Defining a command called 'read'
program
.command('read')
.description('Reads all the todos.')
.action(readTask)

// Defining a command called 'update'
program
.command('update')
.description('Updates a todo.')
.action(updateTask)

// Defining a command called 'delete'
program
.command('delete')
.description('Deletes a todo.')
.action(deleteTask)

// Parsing the command-line arguments and executing the corresponding actions
program.parse()
  1. La toute première ligne, #!/usr/bin/env node, est un « shebang ». Il informe le système d'exécuter le script à l'aide de l'interpréteur Node.js. Cela nous permet d'exécuter le script directement à partir de la ligne de commande sans taper explicitement nodeavant le nom du fichier du script.
  2. Ensuite, nous importons toutes les fonctions requises qui contiennent la logique de chaque commande.
  3. La ligne import { Command } from 'commander'importe la Commandclasse depuis la bibliothèque Commander.js. La ligne suivante, const program = new Command(), crée une instance de la Commandclasse. Cette instance est essentielle pour définir et gérer les commandes de notre outil CLI.
  4. Nous définissons ensuite les informations de l'outil CLI. Les méthodes .name(), .description()et .version()définissent le nom, la description et la version de notre outil CLI. Ces détails sont affichés lorsque les utilisateurs appellent l'outil avec des indicateurs spécifiques tels que --helpou --version.
  5. Ensuite, nous définissons les différentes commandes. Chaque program.command()bloc définit une commande pour notre outil CLI. Dans chaque bloc, le nom de la commande est défini à l'aide d'un argument de chaîne (par exemple, 'add', 'read'). La .description()méthode fournit la description de la commande, tandis que la .action()méthode associe une fonction (par exemple, addTask, readTask) à une commande spécifique. Lorsqu'un utilisateur saisit une commande dans le terminal, cette fonction associée est exécutée.
  6. La program.parse()ligne est essentielle pour analyser les arguments de ligne de commande fournis par l'utilisateur. Sur la base de la commande saisie, Commander.js exécutera la fonction d'action associée.

Comment tester l'outil CLI

Nous sommes maintenant presque sur la ligne finale de la création de notre outil CLI Task Manager ! Avant de pouvoir installer et utiliser l'outil, il nous suffit d'apporter un petit ajustement au package.jsonfichier. Ajoutez simplement l'entrée suivante au fichier JSON :

"bin": {
  "todo": "index.js"
}

Si vous avez déjà créé un outil CLI et souhaitez que les utilisateurs y accèdent à partir de la ligne de commande d'une manière similaire aux commandes telles que nodeou npm, alors la propriété "bin" entre en jeu.

La propriété "bin" dans le package.jsonfichier vous permet de spécifier des commandes qui deviennent globalement accessibles une fois votre package installé. En termes plus simples, il vous permet de créer des raccourcis pour exécuter des scripts ou des fonctions spécifiques à partir de la ligne de commande.

Le code fourni demande à Node.js d'exécuter le script défini dans index.jschaque fois que quelqu'un entre tododans le terminal. Essentiellement, cela transforme votre script en un outil de ligne de commande accessible mondialement.

La dernière étape avant de pouvoir commencer à utiliser l'outil est de l'installer globalement sur votre système ! Exécutez la commande suivante pour ce faire :

npm i -g .

Conclusion

Félicitations si vous avez suivi jusqu'ici ! Vous êtes maintenant entièrement équipé pour commencer à utiliser votre outil CLI Task Manager.

Voici les commandes que vous pouvez utiliser pour faire fonctionner l'outil :

  • todo add  – Créer une nouvelle tâche
  • todo read– Lisez toutes vos tâches en attente
  • todo update– Mettre à jour une tâche spécifique
  • todo delete– Supprimer une tâche

Vous pouvez également utiliser ces 2 options à l'aide de l'outil :

  • todo --versionou todo -V– Pour connaître le numéro de version de cet outil
  • todo --helpou todo -h– Pour afficher l'aide de la commande

Et cela conclut ce manuel. J'espère que vous l'avez trouvé agréable et instructif.

Voici le lien vers le référentiel : Task Manager CLI Tool Repo .

À la prochaine! 👋 ❤️ ✨

Source : https://www.freecodecamp.org

#node #nodejs #mongodb

1.35 GEEK