Entendendo o Node.js Event Loop

Continuando os estudos de Node.js me deparei com um elemento chave que não temos como ignorar quando o assunto é essa tecnologia. Estou falando do Event Loop.

Grande parte das características e principalmente das vantagens do Node.js se devem ao funcionamento do seu loop single-thread principal e como ele se relaciona com as demais partes do Node, como a biblioteca C++ libuv.

Assim, a ideia deste artigo é ajudar você a entender como o Event Loop do Node.js funciona, o que deve lhe ajudar a entender como tirar o máximo de proveito dele.

Vamos ver neste artigo:

O problema

Antes de entrar no Event Loop em si, vamos primeiro entender porque o Node.js possui um e qual o problema que ele propõe resolver.

A maioria dos backends por trás dos websites mais famosos não fazem computações complicadas. Nossos programas passam a maior parte do tempo lendo ou escrevendo no disco, ou melhor, esperando a sua vez de ler e escrever, uma vez que é um recurso lento e concorrido. Quando não estamos nesse processo de ir ao disco, estamos enviando ou recebendo bytes da rede, que é outro processo igualmente demorado. Ambos processos podemos resumir como operações de I/O (Input & Output) ou E/S (Entrada & Saída).

Processar dados, ou seja executar algoritmos, é estupidamente mais rápido do que qualquer operação de IO que você possa querer fazer. Mesmo se tivermos um SSD em nossa máquina com velocidades de leitura de 200-730 MB/s fará com que a leitura de 1KB de dados leve 1.4 micro-segundos. Parece rápido? Saiba que nesse tempo uma CPU de 2GHz consegue executar 28.000 instruções.

Isso mesmo. Ler um arquivo de 1KB demora tanto tempo quanto executar 28.000 instruções no processador. É muito lento.

Quando falamos de IO de rede é ainda pior. Faça um teste, abra o CMD e execute um ping no site do google.com, um dos mais rápidos do planeta:

A latência média nesse teste é de 44 milisegundos. Ou seja, enviar um ping para o Google demora o mesmo tempo que uma CPU necessita para executar 88 milhões de operações.

Ou seja, quando estamos fazendo uma chamada a um recurso na Internet, poderíamos estar fazendo cerca de 88 milhões de coisas diferentes na CPU.

É muita diferença!

A solução

A maioria dos sistemas operacionais lhe fornece mecanismos de programação assíncrona, o que permite que você mande executar tarefas concorrentes que não ficam esperando uma pela outra, desde que uma não precise do resultado da outra, é claro.

Esse tipo de comportamento pode ser alcançado de diversas maneiras. Atualmente a forma mais comum de fazer isso é através do uso de threads o que geralmente torna nosso código muito mais complexo. Por exemplo, ler um arquivo em Java é uma operação bloqueante, ou seja, seu programa não pode fazer mais exceto esperar a comunicação com a rede ou disco terminar. O que você pode fazer é iniciar uma thread diferente para fazer essa leitura e mandar ela avisar sua thread principal quando a leitura terminar.

Novas formas e programação assíncrona tem surgido com o uso de interfaces async como em Java e C#, mas isso ainda está evoluindo. Por ora isso é entediante, complicado, mas funciona. Mas e o Node? A característica de single-thread dele obviamente deveria representar um problema uma vez que ele só consegue executar uma tarefa de um usuário por vez, certo? Quase.

O Node usa um princípio semelhante ao da função setTimeout(func, x) do Javascript, onde a função passada como primeiro parâmetro é delegada para outra thread executar após x milisegundos, liberando a thread principal para continuar seu fluxo de execução. Mesmo que você defina x como 0, o que pode parecer algo inútil, isso é extremamente útil pois força a função a ser realizada em outra thread imediatamente.

Vamos falar melhor dessa solução na sequência.

Node.js Event Loop

Sempre que você chama uma função síncrona (i.e. “normal”) ela vai para uma “call stack” ou pilha de chamadas de funções com o seu endereço em memória, parâmetros e variáveis locais. Se a partir dessa função você chamar outra, esta nova função é empilhada em cima da anterior (não literalmente, mas a ideia é essa). Quando essa nova função termina, ela é removida da call stack e voltamos o fluxo da função anterior. Caso a nova função tenha retornado um valor, o mesmo é adicionado à função anterior na call stack.

Mas o que acontece quando chamamos algo como setTimeout, http.get, process.nextTick, ou fs.readFile? Estes não são recursos nativos do V8, mas estão disponíveis no Chrome WebApi e na C++ API no caso do Node.js.

Vamos dar uma olhada em uma aplicação Node.js comum – um servidor escutando em localhost:3000. Após receber a requisição, o servidor vai chamar wttr.in/ para obter informações do tempo e imprimir algumas mensagens no console e depois retorna a resposta HTTP.

O que será impresso quando uma requisição é enviada para localhost:3000?

Se você já mexeu um pouco com Node antes, não ficará surpreso com o resultado, pois mesmo que console.log(‘Obtendo a previsão do tempo, aguarde.’) tenha sido chamado depois de console.log(‘Previsão confirmada!’) no código, o resultado da requisição será como abaixo:

O que aconteceu? Mesmo o V8 sendo single-thread, a API C++ do Node não é. Isso significa que toda vez que o Node for solicitado para fazer uma operação bloqueante, Node irá chamar a libuv que executará concorrentemente com o Javascript em background. Uma vez que esta thread concorrente terminar ou jogar um erro, o callback fornecido será chamado com os parâmetros necessários.

A libuv é um biblioteca C++ open-source usada pelo Node em conjunto com o V8 para gerenciar o pool de threads que executa as operações concorrentes ao Event Loop single-thread do Node. Ela cuida da criação e destruição de threads, semáforos e outras “magias” que são necessárias para que as tarefas assíncronas funcionem corretamente. Essa biblioteca foi originalmente escrita para o Node, mas atualmente outros projetos a usam também.

Task/Event/Message Queue

Javascript é uma linguagem single-thread orientada a eventos. Isto significa que você pode anexar gatilhos ou listeners aos eventos e quando o respectivo evento acontece, o listener executa o callback que foi fornecido.

Toda vez que você chama setTimeout, http.get ou fs.readFile, Node.js envia estas operações para a libuv executá-las em uma thread separada do pool, permitindo que o V8 continue executando o código na thread principal. Quando a tarefa termina e a libuv avisa o Node disso, o Node dispara o callback da referida operação.

No entanto, considerando que só temos uma thread principal e uma call stack principal, onde que os callbacks ficam guardados para serem executados? Na Event/Task/Message Queue, ou o nome que você preferir. O nome ‘event loop’ se dá à esse ciclo de eventos que acontece infinitamente enquanto há callbacks e eventos a serem processados na aplicação.

Em nosso exemplo anterior, de previsão do tempo, nosso event loop ficou assim:

  1. Express registrou um handler para o evento ‘request’ que será chamado quando uma requisição chegar em ‘/’
  2. ele começar a escutar na porta 3000
  3. a stack está vazia, esperando pelo evento ‘request’ disparar
  4. quando a requisição chega, o evento dispara e o Express chama o handler configurado: sendWeatherOfRandomCity
  5. sendWeatherOfRandomCity é empilhado na call stack
  6. getWeatherOfRandomCity é chamado dentro da função anterior e é também empilhado na call stack
  7. Math.floor e Math.random são chamadas, empilhadas e logo desempilhadas, retornando uma cidade à variável city
  8. superagent.get é chamado com o parâmetro ‘wttr.in/${city}’ e definimos o handler/callback para o evento de término da requisição.
  9. a requisição HTTP para http://wttr.in/${city} é enviada para uma thread em background e a execução continua
  10. ‘Obtendo a previsão do tempo, aguarde.’é impresso no console e getWeatherOfRandomCity retorna
  11. sayHi é chamada, ‘Hi’ é impresso no console
  12. sendWeatherOfRandomCity retorna, é retirado da call stack, deixando-a vazia
  13. ficamos esperando pela chamada de http://wttr.in/${city} nos responder
  14. uma vez que a resposta chegue, o evento de ‘end’ é disparado
  15. o handler anônimo que passamos para .end() é chamado, é colocado na call stack com todos as variáveis locais, o que significa que ele pode ver e modificar os valores de express, superagent, app, CITIES, request, response, city e todas funções que definimos
  16. response.send() é chamado com um status code de 200 ou 500, mas isso também é executado em uma thread do pool para a stream de respostas não fique bloqueada e o handler anônimo é retirado da pilha.

E é assim que tudo funciona!

Vale salientar que por padrão o pool de threads da libuv inicia com 4 threads concorrentes e que isso pode ser configurado conforme a sua necessidade.

Microtasks e Macrotasks

Além disso, como se não fosse o bastante, nós temos duas task queues, não apenas uma. Uma task queue para microtasks e outra para macrotasks.

Exemplos de microtasks

  • process.nextTick
  • promises
  • Object.observe

Exemplos de macrotasks

  • setTimeout
  • setInterval
  • setImmediate
  • I/O

Para mostrar isso na prática, vamos dar uma olhada no seguinte código:

a saída no console é:

De acordo com a especificação da WHATWG, uma macrotask deve ser processada da macrotask queue em um ciclo do event loop. Depois que essa macrotask terminar, todas as microtasks existentes são processadas dentro do mesmo ciclo. Se durante este processamento de microtasks novas microtasks surgirem, elas são processadas também, até a microtask queue ficar vazia.

Este diagrama do site Rising Stack ilustra bem o event loop completo:

Em nosso caso:

Ciclo 1:

  1. setInterval é agendado como (macro)task
  2. setTimeout 1 é agendado como task
  3. em Promise.resolve 1 ambos thens são agendados como microtasks
  4. a call stack está vazia e as microtasks executam

Task queue: setInterval, setTimeout 1

Ciclo 2:

  1. a microtask queue está vazia, logo o handler setInterval pode executar;
  2. outro setInterval é agendado como task, logo atrás de setTimeout 1

Task queue: setTimeout 1, setInterval

Ciclo 3:

  1. a microtask queue está vazia, logo o handler setTimeout 1 pode executar;
  2. promise 3promise 4 são agendadas como microtasks;
  3. handlers de promise 3promise 4 são executados
  4. setTimeout 2 é agendado como task

Task queue: setInterval, setTimeout 2

Ciclo 4:

  1. a microtask queue está vazia; logo o handler de setInteval pode executar;
  2. outro setInterval é agendado como task, logo atrás de setTimeout

Task queue: setTimeout 2, setInterval

Ciclo 5:

  1. o handler setTimeout 2 executa;
  2. promise 5promise 6 são agendadas como microtasks;
  3. os handlers de promise 5 e promise 6 executam encerrando o programa.

Nota: isso funciona perfeitamente bem no Google Chrome, mas por questões que fogem da minha compreensão, não é regra em todos ambientes de execução. Existem modificações que podemos fazer no código para que o comportamento seja o mesmo em todos ambientes, mas deixam o código terrivelmente feio (i.e. callback hell).

Conclusões

Como você pôde ver, se quisermos ter total controle de nossas aplicações Node.js devemos prestar atenção nestes detalhes de como as tarefas são executadas dentro do event loop, principalmente para não bloquearmos sua execução sem querer.

O conceito do event loop pode ser um tanto complicado no início mas uma vez que você entender sue funcionamento na prática você não conseguirá mais imaginar sua vida sem ele. Obviamente o uso inicial intenso de callbacks é muito chato de gerenciar mas já é possível usar Promises em nossos códigos Javascript que permitem deixar as tarefas assíncronas mais inteligíveis e em breve devemos ter acesso ao recurso async-await com o ES7.

Uma última dica é que você também pode enviar seus processamentos longos (mas que não são operações de IO), que normalmente seriam executados na thread principal para as threads em background usando bibliotecas como async.js.

Recomendo agora colocar a mão na massa com esse tutorial de Node.js com MongoDB que preparei pra você!

Como usar NodeJS + MySQL

Hoje vou ensinar como que você pode usar Node.js com MySQL. Apesar de MongoDB e demais bases não relacionais serem a escolha mais comum com Node, muitos programadores, geralmente de background PHP, conhecem e usam MySQL e não querer abrir mão de seu conhecimento a respeito.

Além disso, como pessoal da Rising Stack afirma, com bons índices e planejamento adequado, o MySQL pode ser excelente para tarefas onde ACID seja uma prioridade, algo geralmente relegado a segundo plano em bancos não-relacionais. Sendo assim, acredito que este post ajudará quem estiver migrando de plataforma, mas não quer migrar de banco.

Existem diversas maneiras de usar MySQL com Node. Uma vez que é um banco de dados muito popular, logo, é possível que você encontre muita informação diferente na Internet à respeito. Entenda que te mostrarei UMA das INÚMERAS opções de como conectar no MySQL com Node.js.

Veremos neste artigo (pule seções à vontade):

  1. Criando o banco de dados
  2. Criando e populando a tabela
  3. Criando a API
  4. Criando a listagem de clientes
  5. Criando a pesquisa de um cliente
  6. Excluindo um cliente
  7. Adicionando um cliente
  8. Atualizando um cliente
  9. Bônus 1: ORM
  10. Bônus 2: Boas práticas

Então vamos lá!

Parte 1: Criando o banco de dados

Se você já possui um banco de dados MySQL, ok, use ele e pule para a próxima parte.

Se você não possui um banco MySQL, você tem duas opções:

  1. baixar e instalar o MySQL (gratuito) na sua máquina;
  2. contratar um banco MySQL na nuvem (gratuito, vou te ensinar abaixo);

A primeira opção é um pouco mais trabalhosa, porém te dá mais liberdade. Baixe, instale e crie um novo banco de dados para uso nesse tutorial. Durante a instalação, você irá precisar definir uma senha para o usuário ‘root’, não esqueça dela.

A segunda opção é bem simples também, mais vai exigir que você tenha conexão com a Internet enquanto estiver programando pois seu banco vai estar na nuvem. Na Umbler, empresa em que trabalho como evangelista tecnológico, você pode criar bancos MySQL gratuitos de até 1GB, para testes e estudos. Para isso, basta você criar um site que custa R$6/mês, mas não se preocupe com o valor, você ganha créditos de graça para usar livremente conforme vai cumprindo algumas etapas dentro do painel e mais tarde, se não quiser mais mantê-lo, basta excluir ele (ou parar o site para reduzir os custos). Crie um site com um domínio qualquer (mesmo que não tenha registrado) e depois vá na parte de banco de dados para criar um do tipo MySQL e escolha a opção Grátis (1GB) e habilite o acesso externo, como na imagem abaixo. Durante a criação do seu banco, você vai definir o nome dele, o usuário e a senha, anote tudo, bem como o endereço do servidor que a Umbler vai te fornecer.

MySQL na Umbler
MySQL na Umbler

Parte 2: Criando e populando a tabela

Agora que você já tem o banco pronto, vamos criar uma tabela nele e colocar alguns dados de exemplo. Não pule esta etapa pois vamos fazer tudo isso usando Node.js!

Crie uma pasta para guardar os arquivos do seu projeto Node.js, você pode fazer isso pelo console se quiser, usaremos ele algumas vezes nesse tutorial. No exemplo abaixo, criei a pasta e depois entrei dentro dela.

Agora execute no console o comando “npm init” que o próprio NPM (gerenciador de pacotes do Node) vai te guiar para a construção do arquivo packages.json, que é o arquivo de configuração do projeto. Se ficar em dúvida ou com preguiça, segue o meu package.json abaixo:

Com o arquivo de configurações criado, vá no console novamente, na pasta do projeto e digite o seguinte comando para instalar a extensão mysql, que permite usar Node com MySQL:

A flag “-S”diz que é pra salvar essa dependência no arquivo packages.json. Se você abrir o arquivo vai ver que tem coisa nova por lá.

Agora, crie um arquivo create-table.js dentro dessa pasta, que será o arquivo que vai criar e popular nossa tabela que usaremos neste exercícios. Também usaremos ele para entender o básico de comandos SQL, conexão com o banco, etc. Vamos começar nosso create-table.js definindo uma constante para a String de conexão com o banco e uma constante para o objeto que vai carregar a extensão mysql (e que mais tarde usaremos para conectar, executar SQL, etc). O código abaixo é auto explicativo:

Agora, usaremos esse objeto connection para fazer uma conexão e, em caso de sucesso, imprimir uma mensagem de sucesso. Caso contrário, se der erro, uma mensagem de falha:

Se esse código lhe parece muito bizarro, calma, é fácil de entender. O objeto connection permite que façamos coisas no banco de dados, uma delas é a conexão (connect). No entanto, o Node.js trabalha de maneira assíncrona, o que quer dizer que ele não espera pela conexão ser estabelecida. Quando ele terminar de estabelecer a conexão, ele vai executar a função de callback passada por parâmetro, contendo ou não um objeto de erro.

Para executar esse arquivo, abra o console (se estiver usando VS Code, apenas aperta F5 com este arquivo aberto no editor) e na pasta do projeto digite:

Agora que sabemos como conectar no MySQL através de Node.js, é hora de executarmos o comando que vai criar a tabela e popular ela, ao invés de simplesmente imprimir “conectou”. Sendo assim, vamos criar uma função JS nesse arquivo pra fazer a criação da tabela:

Coloque a chamada desta função no callback após a conexão no banco, passando o objeto conn por parâmetro, como abaixo:

Mande rodar esse arquivo novamente e verá que ele criará a sua tabela com sucesso. Para adicionar algumas linhas de exemplo, vamos criar outra função que vai fazer um bulk insert no MySQL via Node.js (inserção de várias linhas de uma vez):

Essa função deve ser chamada dentro do callback da query que criou a tabela, logo abaixo de onde diz “console.log(‘criou a tabela!’);”. Se não quiser fazer isso dessa maneira, você pode fazer pela sua ferramenta de gerenciamento do MySQL (como o MySQL Workbench) ou pelo Visual Studio, se estiver usando ele para programar Node.js.

Parte 3: Criando a API

Agora que já temos nosso banco de dados MySQL pronto, com dados de exemplo e aprendemos como fazer a conexão nele, vamos criar uma API básica usando Express para conseguir criar um CRUD com Node.js + MySQL no próximo passo. Se já sabe como montar uma API básica com Node + Express, pule esta parte.

Vamos começar adicionando a dependência do Express (framework web) e do Body-Parser (parser para os POSTs futuros) no projeto via linha de comando na pasta do mesmo:

Na sequência, vamos criar um arquivo index.js na pasta do projeto onde vamos criar o nosso servidor da API para tratar as requisições que chegarão em breve. Vamos começar bem simples, apenas definindo as constantes locais que serão usadas mais pra frente:

Agora, logo abaixo, vamos configurar nossa aplicação (app) Express para usar o body parser que carregamos da biblioteca body-parser, permitindo que recebamos mais tarde POSTs nos formatos URLEncoded e JSON:

Na sequência, vamos criar um roteador e dentro dele definir uma regra inicial que apenas exibe uma mensagem de sucesso quando o usuário requisitar um GET na raiz da API (/) para ver se está funcionando.

Note que na última linha eu digo que requisições que chegarem na raiz devem ser mandadas para o router. Por fim, adicionamos as linhas abaixo no final do arquivo que dão o start no servidor da API:

Teste sua API executando via console o seu index.js com o comando ‘node index.js’. Você deve ver a mensagem de ‘API funcionando!’ no console, e se acessar no navegador localhost:3000 deve ver o JSON default que deixamos na rota raiz!

API Funcionando
API Funcionando

Parte 4: Criando a listagem de clientes

Agora que temos uma API funcionando, vamos adicionar uma rota /clientes que listará todos os clientes do banco de dados. Para fazer isso, primeiro vamos criar uma função que executará consultas SQL no banco usando uma conexão que será criada a cada uso (existem técnicas mais avançadas para maior performance, mas por ora, isso resolve satisfatoriamente), como abaixo:

Esta função pode ficar no final do seu arquivo index.js e nós a usaremos para fazer todas as operações de banco de dados da nossa API, começando com consultar todos os clientes. Para isso, começaremos criando a rota /clientes logo abaixo da rota / (raiz):

Agora, ao executarmos novamente nosso projeto e ao acessarmos a URL localhost:3000/clientes, veremos todos os clientes cadastrados no banco de dados (no passo 2, lembra?):

Todos clientes
Todos clientes

E com isso finalizamos a listagem de todos clientes na nossa API!

Parte 5: Criando a pesquisa de um cliente

Agora, se o usuário quiser ver apenas um cliente, ele deverá passar o ID do mesmo na URL, logo após o /clientes. Para fazer isso, vamos modificar nossa rota criada no passo anterior, /clientes, para aceitar um parâmetro opcional ID. Além disso, dentro do processamento da rota, se vier o ID, devemos fazer uma consulta diferente da anterior, como mostra o código abaixo, com os ajustes na mesma rota do passo anterior:

O parseInt que coloquei é apenas uma proteção contra SQL Injection uma vez que neste caso o ID deve ser um inteiro válido. Não é a melhor forma de resolver isso, mas vai nos atender por enquanto sem ter de entrar em conceitos mais avançados. Manda rodar e teste no navegador, verá que está funcionando perfeitamente!

Apenas um
Apenas um

E com isso terminamos a pesquisa por cliente.

Parte 6: Excluindo um cliente

Para excluir um cliente vamos fazer um processo parecido com o de pesquisar um cliente, no entanto, mudaremos o verbo HTTP de GET para DELETE, como manda o protocolo. Adicione a nova rota logo após as demais:

Note que desta vez o parâmetro id na URL não é opcional (não usei ? após :id). E dentro do processamento da requisição delete do router eu mando um SQL de DELETE passando o ID numérico.

Para testar essa rota você tem duas alternativas, ou usa o POSTMAN para forjar um DELETE, como abaixo:

DELETE com POSTMAN
DELETE com POSTMAN

Ou fazer via console usando cURL (se tiver ele instalado na sua máquina):

Em ambos os casos você deve obter uma resposta 200 OK (caso não tenha dado erro) e se mandar listar todos clientes novamente, verá que o número 1 sumiu.

Parte 7: Adicionando um cliente

Agora vamos adicionar um novo cliente com um POST na rota /clientes. Adicione esta nova rota logo abaixo das anteriores.

Nela, eu pego as variáveis que devem vir junto ao POST, faço algumas validações de tamanho e tipo de dado e depois junto elas a um comando de INSERT que vai ser executado no banco de dados.

Para testar esse POST, você usar o POSTMAN, como mostrado anteriormente:

POST no Postman
POST no Postman

Se quiser fazer via cURL:

Também podemos permitir outras maneiras de passar os dados com a requisição POST, como através de JSON ou através da URL, mas isso foge do escopo deste artigo que deve focar mais no CRUD com MySQL + Node.

Se testar agora vai ver que é possível inserir novos registros no banco de dados através de requisições POST.

Parte 8: Atualizando um cliente

E para finalizar o CRUD, vamos ver como podemos atualizar um cliente no banco de dados SQL Server através da nossa API Node.js. Para fazer updates podemos usar os verbos PUT ou PATCH. O protocolo diz que devemos usar PUT se pretendemos passar todos os parâmetros da entidade que está sendo atualizada, mas não vamos alterar jamais o ID, então usaremos PATCH nesta API.

Crie uma rota PATCH em /clientes esperando o ID do cliente a ser alterado.

No código acima, pegamos o ID que veio na URL e as demais informações que vieram no corpo da requisição, fazendo as mesmas validações que já havia feito antes (podemos melhorar a segurança aqui). Depois monto o UPDATE com as variáveis locais e mando para nossa função de executar SQL.

Para testar uma requisição PATCH, você pode usar o POSTMAN:

PATCH no POSTMAN
PATCH no POSTMAN

Ou o cURL:

O resultado é o mesmo: o cliente cujo ID=4 vai ter o seu nome alterado para ‘fernando’. Note que se ele não existir, ocasionará um erro que será apresentado no corpo da resposta.

E com isso finalizamos o CRUD da nossa API Node.js que usa MySQL como persistência de dados.

Bônus 1: ORM

Se você não curte muito a ideia de ficar usando SQL no meio dos seus códigos JS, experimente usar alguma biblioteca ORM (Object-Relational Mapping) como o Sequelize. Nunca usei, mas muita gente recomenda por possuir suporte a MySQL, SQL Server, PostgreSQL e SQLite.

Bônus 2: Boas práticas

Não quis deixar o tutorial muito extenso e por isso deixei de passar algumas boas práticas. Como foi levantada esta questão no grupo do Facebook de Node.js pelo usuário Ícaro Tavares, achei válido trazer as sugestões para cá. Se você está lendo esse bônus é porque se interessa em saber mais do que o básico e isso é ótimo, então lá vai.

SQL Injection

O código que forneci nos exemplos concatenando strings para formar uma query SQL deixa uma brecha potencialmente nociva no código da sua API chamada SQL Injection. Para evitar isso, você pode usar ‘?’ no lugar das variáveis que devem ser substituídas e passar um array de variáveis para substituição como segundo parâmetro da função query.

Como assim?

Para um dado comando SQL como:

note que coloquei o ‘?’ no lugar do valor do id, e poderia fazer isso para quantos filtros minha consulta tiver. Agora, quando chamar a função query do módulo mysql, farei da seguinte forma, passando um array de variáveis/valores para preencher esses ‘?’, na mesma ordem que foram descritos na consulta:

Dessa forma, meu código não fica mais suscetível à SQL Injection!

Conexões auto-gerenciadas

No código que forneci, nós temos total controle sobre a abertura e fechamento de conexões. Como boa prática, você pode deixar para a própria função query se encarregar de abrir e fechar essas conexões, se livrando desse “microgerenciamento”.

Em meus testes pessoais notei que isso pode ser melhor ou pior que o exemplo que mostrei no tutorial, considerando peculiaridades da sua infraestrutura de MySQL. Na dúvida, o meu código não tem a melhor performance, mas a garantia de funcionamento mesmo em hospedagens compartilhadas, onde não temos tanto controle das configurações do MySQL.

SELECT *

A menos que você sempre vá usar todas as informações da sua tabela, jamais use SELECT *, mas sim a versão extensão da consulta SQL em que você passa todas as colunas da tabela que deseja retornar. Pense em um SELECT como sendo um download do seu banco de dados, quanto menos colunas você incluir no SELECT, mais rápido será esse download pois menos bytes serão retornados.

Até a próxima!

As 8 dúvidas técnicas mais comuns sobre NodeJS

Plataforma nova, dúvidas novas. Como muita gente (inclusive eu) têm começado agora a desenvolver aplicações em Node.js, por diversos motivos que já elenquei em outros posts, é comum que surjam dúvidas uma vez que é uma plataforma bem diferente do que as tradicionais C# e Java que muito de nós programam.

A ideia desse post é elencar as 8 principais dúvidas técnicas sobre Node.js. Caso esteja ainda mais cru do que eu na plataforma, recomendo ler primeiramente estes posts:

As dúvidas que serão respondidas neste artigo são:

  1. Qual a diferença entre til e circunflexo no packages.json?
  2. Como eu depuro programas Node.js?
  3. Qual é o propósito do module.exports e como eu uso ele?
  4. Como faço uso de todos meus processadores?
  5. Como ver completamente os objetos Node.js no console?
  6. Como executar Node.js como um serviço?
  7. Como enviar um email via Node.js?
  8. Como eu posso compartilhar código entre o servidor e o cliente?

Vamos lá!

#1 – Qual a diferença entre til e circunflexo no packages.json?

Então você abre o packages.json e rapidamente entende que as dependencies são os pacotes que sua aplicação usa, mas onde deveriam estar listadas as versões dos pacotes tem um monte de símbolos que não lhe dizem muita coisa…Resumidamente funciona assim:

  • O til garante que o pacote seja sempre carregado  respeitando o número do meio da versão. Ex: ˜1.2.3 pega o pacote mais recente da versão 1.2.x, mas não vai atualizar para 1.3. Geralmente garante que correções de bugs sejam atualizados no seu pacote.
  • O circunflexo garante que o pacote seja sempre carregado respeitando o primeiro número da versão. Ex: ˆ1.2.3 pega o pacote mais recente da versão 1.x, mas não vai atualizar para 2.0. Garante que bugs e novas funcionalidades do seu pacote sejam atualizados, mas não novas versões “major” dele.

A imagem abaixo ajuda a entender o template de versões dos pacotes do NPM, que aliás usa um padrão bem comum da indústria de software:

Outros símbolos incluem:

  • >, >=, <, <=1.0: a versão deve ser superior, superior ou igual, inferior, inferior ou igual à 1.0, respectivamente.
  • 1.2.x: equivalente a ˜1.2.0
  • *: qualquer versão do pacote
  • latest: a versão mais recente do pacote

Agora se você não tiver símbolo algum, aí o pacote deve ser sempre carregado usando a versão especificada.

Uma dica bem valiosa aqui (quem não gosta de um bônus?) para quando se quer atualizar todos pacotes é colocar * na versão de todos e rodar o comando “npm update –save” sobre a pasta do projeto.

#2 – Como eu depuro programas Node.jS?

Assim como o JS tradicional, em Node podemos usar qualquer editor de texto para programar nossas aplicações. No entanto, opções simplificadas demais como Notepad e editores de linha de comando (como nano), embora práticas de usar não são muito úteis quando precisamos depurar aplicações bugadas. Sendo assim, vou dar duas sugestões de como depurar programas Node.js.

Opção 1: troque seu editor de texto. O melhor jeito que eu faço atualmente é usando o Visual Studio Code, que vem com suporte nativo a Node.js. É gratuito e roda em Windows, Mac e Linux. Nele você pode colocar breakpoints em arquivos .js, inspecionar variáveis, testar expressões no console, integração com Git, StandardJS e muito mais. E é uma ferramenta gratuita que roda em Windows e tem uma versão beta pra Mac.

Outra alternativa, mais pesada é o  Visual Studio Community (2015 com plugin para Node ou 2017 com suporte nativo). Eu usava antes, mas não não uso mais.

Opção 2: use um depurador externo ao seu editor. Agora se você não quer abrir mão de usar editores de texto nativos do seu SO ou os mais clássicos que você já está acostumado, tem algum tempo que você pode depurar seus programas Node.js diretamente no Google Chrome também, usando o F12. Você consegue mais informações neste post do Medium.

E por fim, uma outra alternativa para não largar seus editores de texto favoritos é usando o Node Inspector, um projeto open-source disponível no GitHub.

#3 – Qual é o propósito do module.exports e como eu uso ele?

Nos tutoriais simples que eu tenho aqui no blog de como programar usando Node.js com MongoDB eu sempre recomendo criar um arquivo JS db.js com a lógica de conexão do banco de dados. Ao final do arquivo, sempre vemos um modulo.exports que recebe um objeto JSON. Mais tarde, damos um require nesse db.js pra poder usar a conexão internamente criada nele. Mas final, o que o module.exports faz?

Resumidamente, module.exports define qual objeto deste arquivo JS será exportado (ou exposto) quando uma chamada require for feita à ele. Ele é um análogo ao return do arquivo JS como um todo.

Um atalho do Node permite que você use apenas a palavra exports ao invés de module.exports.

Um exemplo de uso bem comum é para criar uma biblioteca de funções dentro de um arquivo JS que precisa ser chamada por outros arquivos JS. Considere o seguinte arquivo mymodule.js:

Aqui expusemos as funções myFunc1 e myFunc2. Se quisermos usar essas funções em outro arquivo, basta usarmos o require, como abaixo:

#4 – Como faço uso de todos meus processadores?

O Node.js trabalha com uma única thread dentro de um único processo na sua máquina. Dessa forma, é natural que ele utilize apenas um processador, mesmo que você esteja rodando sua aplicação em um webserver com 16 núcleos ou mais. Sendo assim, uma dúvida bem comum é: como escalar um projeto Node para que use todo o poder do seu servidor?

Basicamente você tem duas opções:

Opção 1: usar uma arquitetura de micro-serviços. Cada módulo da sua aplicação deve ser uma sub-aplicação autônoma, que responde a requisições e realiza apenas as tarefas que são sua responsabilidade. Sendo assim, teríamos diversas aplicações pequenas escritas em Node.js, cada uma usando um core da sua máquina e recebendo (e processando) as requisições que lhe cabem. Uma aplicação principal recebe a requisição original do usuário e delega as tarefas para as demais sub-aplicações.

Opção 2: usar um webproxy na frente do Node. Você pode colocar um Apache, Nginx ou IIS à frente da sua aplicação e deixar com ele essa tarefa de controlar a carga de requisições, balanceando entre diferentes nós idênticos da sua aplicação, cada um em um processador. Você pode fazer isso com Node.js também, mas geralmente Apache e cia. já possuem muito mais maturidade pra isso.

Existem outras opções? Sim. No entanto, as duas que recomendo estão acima.

#5 – Como ver completamente os objetos em NodeJS no console?

Certas vezes quando temos objetos complexos em Node.js e queremos ver o que ele está guardando dentro de si usamos o console do Google Chrome ou mesmo do Visual Studio para entender o que se passa com nosso objeto. No entanto, dependendo do quão “profundo” é o nosso objeto (quantos objetos ele possui dentro de si), essa tarefa não é muito fácil.

Aqui vão algumas formas de imprimir no console o seu objeto JSON inteiro, não importando quantos níveis hierárquicos ele tenha:

Opção 1: console.log. Tente usar a função console.log passando o objeto or parâmetro, isso funciona na maioria dos casos.

Opção 2: util.inspect. Use o seguinte código abaixo para usara  função util.inspect e retornar todo o conteúdo de um objeto JSON.

Opção 3: JSON.stringify. Use a função JSON.stringify passando o objeto e o nível de identação que deseja dentro do objeto, como abaixo.

#6 – Como executar Node.js como um serviço?

Para quem escolher rodar suas aplicações Node.js em ambiente Windows, existe uma série de passos necessários para que funcione correta e ininterruptamente, que já abordei nesse post aqui, inclusive no Passo 6 eu ensino como instalar seu app Node como se fosse um Windows Service.

#7 – Como enviar um email via Node.js?

Este é um problema bem comum para quem quer fazer coisas simples como um formulário de contato em uma aplicação web. Algo tão trivial em linguagens como PHP se torna uma dor de cabeça em Node. Claro, não estou falando de envio massivo de emails, que nunca deve ser feito usando SMTPs tradicionais (como o da sua conta de email) e sim com serviços de SMTP Gateway como Sendgrid e Mandrill. Estou falando de envios pontuais.

Existem algumas bibliotecas em Node que prometem resolver esse problema para você. Algumas oferecem suporte a contas Gmail,outras não, mas em geral todas funcionem em menor ou maior grau com os padrões atuais de envio de email. Elas são (em ordem de relevância):

  • Nodemailer: outra opção, mais focada no envio dos emails do que na personalização dos mesmos, muito popular desde 2010, quando hão havia opção alguma na plataforma para envio de emails. Em menos de 2 minutos você tem ele funcionando e enviando emails.
  • Node Email Templates:  O pacote mais completo da Internet para envio de emails via Node.js. Permite construir mensagens agradáveis usando a sua view engine e pré-processadores CSS. É um projeto ativo com atualizações frequentes no Github, mas um pouco complexo para quem está começando.
  • EmailJS: para quem tem problemas com anexos no Nodemailer, esse aqui é o pacote ideal, no mais, Nodemailer é melhor.
  • AlphaMail: uma solução completa “as-a-Service” onde você dispara emails usando a infraestrutura da AlphaMail mesmo. Não sei o quão bom os caras são no envio de emails, mas me pareceu muito simples e particularmente útil para quem não quer se preocupar com SMTP e coisas do gênero.

Para não ficar apenas indicando bibliotecas,a qui vai um código JS de envio de email usando Nodemailer, o mais recomendado dentre os pacotes citados:

#8 – Como eu posso compartilhar código entre o servidor e o cliente?

Muitas pessoas chegam até o Node com a promessa de escrever tanto o client-side quanto o server-side na mesma linguagem. No entanto, para que realmente isso seja vantajoso tem de ser possível o reuso de software em ambos os lados da aplicação, certo?!

Eu vou mostrar aqui uma forma de conseguir reutilizar os seus módulos JS tanto no browser quanto no Node.js.

Primeiramente, em Node quando queremos expor um módulo, usamos um código semelhante a esse:

No entanto, no browser isso dá erro uma vez que exports é undefined neste ambiente. Sendo assim, para contornar este problema, o primeiro passo é verificar a existência ou não do exports, caso contrário, teremos que criar um objeto para o qual possamos exportar as funções:

O problema com essa abordagem, é que no browser as funções que não foram exportadas também ficam disponíveis como funções globais, o que não é algo desejável. Resolvo isso usando closures, como no exemplo abaixo:

O uso do objeto this representa o browser, e o uso de this[‘mymodule’] é o local de exportação no browser. Esse código está pronto para ser usado tanto no browser quanto no server. Considerando que ele está em um arquivo mymodule.js, usamos esse módulo da seguinte maneira em Node:

E no browser usamos assim:

Claro, existem códigos JS escritos em Node que não serão suportados pelo browser, como o comando require por exemplo. Sendo assim, os módulos compartilhados entre os dois terão de ser o mais genéricos possíveis para que haja compatibilidade.

Também tome cuidado com as features mais recentes da linguagem Javascript que muitas vezes são suportadas pela engine V8 que o Node usa mas não pelos demais browsers.

Até a próxima!