A primeira coisa que deve vir na sua mente é: por que diabos eu iria querer criar a minha própria console application?
Muitas vezes você precisa de um software para realizar ou automatizar alguma tarefa e não tem a necessidade de criar uma interface gráfica para isso, poderia ser tudo resolvido no terminal de linha de comando. Pense no trabalho que dá criar qualquer aplicação web, com suas páginas em HTML, ou mesmo aplicações desktop com suas IDEs super pesadas. E nem vou falar dos apps mobile…
No tutorial de hoje eu vou te ensinar os fundamentos de como criar aplicações de console interativas, ou seja, que os usuários podem utilizar com o teclado e o terminal de linha de comando mas que não possuem interface gráfica.
Dito isso, nossa primeira missão é entender, tecnicamente, como configurar nosso ambiente (caso nunca tenha programado Node antes) e como interagir com o usuário via linha de comando.
Vamos lá!
#1 – Configurando o Projeto
Para este projeto optarei por utilizar a linguagem de programação JavaScript, uma das mais versáteis da atualidade, e como farei uma aplicação de console (sem interface gráfica, apenas interação via terminal) utilizarei a tecnologia Node.js que me permite usar JS em aplicações que não estejam no browser.
Caso você não possua o Node.js instalado na sua máquina, o vídeo abaixo ensina a instalar.
Com o Node.js instalado, você pode criar uma pasta no seu computador com o nome de console-app e abrindo o terminal de linha de comando do seu sistema operacional (cmd no Windows), navegue até a pasta que criou e digite o seguinte comando para inicializar o seu projeto.
1 2 3 |
npm init -y |
Com o projeto inicializado, agora crie um arquivo index.js na raiz da sua aplicação e carregue o pacote nativo readline da seguinte forma (não precisa instalar, é nativo).
1 2 3 4 5 6 7 8 |
//index.js const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) |
Todo mundo que mexe com JavaScript conhece o console.log, que é uma forma de enviar mensagens para o usuário no console, mas poucos conhecem o readline, que serve pro usuário conseguir enviar mensagens para o programa. Seu uso é bem simples: começamos configurando as fontes de input (entrada) e output (saída) que usaremos, sendo que escolhi aqui process.stdin (standard input do process, ou teclado) e process.stdout (standard output do process, ou terminal). Assim, conseguiremos usar o módulo readline para interagir com usuário fazendo perguntas e aguardando respostas, típico de aplicações console.
#2 – Criando um menu interativo
A próxima missão é começarmos a implementar as funcionalidades da nossa aplicação. Tipicamente temos dois tipos de experiências em aplicações console: listas de opções (menus) e perguntas/respostas.
Abaixo um exemplo de função de menu, que explico a seguir. Repare como usei um recurso de setTimeout para dar uma experiência mais fluida para o usuário, para ele ter tempo de raciocinar o que está acontecendo em cada parte da interação. UX é algo importante em todo tipo de aplicação, mesmo nas de console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function menu() { setTimeout(() => { console.clear(); console.log("1 - Opção 1"); console.log("2 - Opção 2"); rl.question("Escolha sua opção: ", (answer) => { switch (answer) { case "1": doSomething1(); break; case "2": doSomething2(); break; default: { console.log('Opção inválida!'); menu(); } } }) }, 1000) } menu(); |
Nossa função de menu começa limpando o console com clear, algo importante para que o usuário sempre tenha na tela apenas o que importa a ele no momento. As opções em si são impressas com console.log normalmente e a cada uma temos um rótulo numerado, que será a forma como o usuário vai selecionar a opção que deseja. Por fim, usamos o rl (instância do readline já configurada) para questionar ele qual opção deseja.
A função question do readline tem dois parâmetros: o primeiro, que é a instrução/pergunta, e o segundo, que é o callback que vai processar a resposta do usuário (answer). Como o input é o teclado, essa answer sempre será uma string, então convém validar dados, verificar tipos, etc caso seja uma entrada sensível. Com base nessa respostas você tomará caminhos diferentes, que geralmente definimos em outras funções e estruturamos a árvore de decisão com switch/case.
Também sempre é interessante ter uma opção default, caso o usuário digite algo inesperado. As funções doSomething1 e 2 podem ser qualquer coisa que você imaginar, de acordo com a utilidade da sua aplicação, crie elas como quiser/precisar. E se você achou feio com callbacks, calma, tem opção com Promise também, que mostrarei a seguir.
Se rodar o index.js você já terá o menu funcionando, mas se escolher qualquer opção terá um erro até que implemente a mesma.
#3 – Criando um “formulário”
Geralmente alguma das opções do menu possui o intuito de levar o usuário para outra tela, a fim de cadastrar algum conjunto de informações, como faríamos em um formulário web/desktop. No entanto, como não temos interface gráfica aqui, logo não temos também campos de formulário, o que devemos emular com perguntas e respostas usando console.log e readline.question.
Antes de fazer a função de cadastro, vamos criar uma função auxiliar e uma estrutura para guardar nossos dados.
1 2 3 4 5 6 7 8 9 |
function preMenu() { rl.question(`Pressione qualquer tecla para continuar...`, () => { menu(); }) } const customers = []; |
A função preMenu será chamada sempre ao término de alguma “tela” interna, para o usuário poder confirmar que deseja voltar ao menu. Já o array é autoexplicativo, servirá para guardar os clientes cadastrados no sistema, o que faremos a seguir.
Vamos supor que a primeira opção do menu, com a função doSomething1, seja para essa finalidade de cadastro de clientes. Para implementar ela, podemos fazer utilizando callbacks aninhados, ou então utilizando a versão com promises desta biblioteca, como abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
async function doSomething1() { const readlinePromises = require("readline/promises"); const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout, terminal: false }) console.clear(); const newCustomer = {}; newCustomer.name = await rl.question("Informe seu nome: "); newCustomer.city = await rl.question("Informe sua cidade: ") console.log("Cliente cadastrado com sucesso!"); customers.push(newCustomer); preMenu(); } |
Repare como importei o readline desta vez a partir de readline/promises, que é onde fica a Promises API da biblioteca. O processo de instanciar um readline é bem parecido, mas é importante a propriedade terminal estar em false para não causar um comportamento incômodo do usuário ver dobrado o que ele digitar.
Depois, o uso de readline com promises evita o callback hell de ter questions dentro dos callbacks de outras questions. Assim, conseguimos manter cada pergunta/resposta em apenas uma linha com o uso de async/await. Bem legal, não?
Com os dados coletados nas perguntas/respostas, podemos montar o objeto que depois é armazenado no array de clientes. Obviamente você poderia armazenar em um arquivo ou banco de dados também, conforme a sua necessidade.
Teste agora e veja como funciona perfeitamente. Com esses conhecimentos fundamentais você está apto a construir todo tipo de console application ou CLI utilizando Node.js.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.