Não é raro termos que debugar nossa aplicação à procura de problemas, muitas vezes por aqueles que ocorrem insidiosamente. Como em outras plataformas, Node.js possui um debugger padrão que pode estar integrado a editores e IDE’s do mercado. Contudo, podemos acessá-lo diretamente pelo Chrome DevTools utilizando-o como ferramenta de depuração agnóstica de editor ou IDE. Para que possamos ver essa integração em ação, primeiro veremos o uso do debugger através do terminal. Você pode pular esta parte se assim preferir e ir direto para a depuração do programa no Chrome DevTools.

Introdução rápida à depuração em Node.js através do terminal

Todo o procedimento aqui pode ser feito em sua máquina, contanto que esteja utilizando Node 8.0 ou superior

Vamos criar o script index.js que utiliza recursos do Node.js versão 8.0 para criar um arquivo e exbir seu tamanho em seguida através do terminal:

// index.js 
const { promisify } = require('util');
const writeFile = promisify(require('fs').writeFile);
const stat = promisify(require('fs').stat);

(async function() {
  try {
    const filePath = 'arquivo.txt';
    await writeFile(filePath, 'conteúdo arquivo');
    console.log('arquivo criado com sucesso!');
    const fileStat = await stat(filePath);
    console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
  } catch(err) {
    console.log(err);
  }
})();

Para debugarmos nosso arquivo, basta chamá-lo através do interpretador do Node com auxílio do parâmetro inspect:

node inspect index

Por padrão, o debugger adicionará um breakpoint na primeira linha de execução do programa, aguardando nossa intervenção:

MacBook-Pro-de-Flavio:Desktop flavioalmeida$ node inspect index.js

< Debugger listening on ws://127.0.0.1:9229/9de73f77-46ec-4f7d-9d98-967d4f7544b5
< For help see https://nodejs.org/en/docs/inspector
< Debugger attached.

Break on start in index.js:1
> 1 (function (exports, require, module, __filename, __dirname) { const { promisify } = require('util');
  2 const writeFile = promisify(require('fs').writeFile);
  3 const stat = promisify(require('fs').stat);
debug> 

Para que o programa prossiga até o próximo breakpoint, usamos a instrução c (ou cont):

debug> c
< arquivo criado com sucesso!
< Tamanho do arquivo 17 bytes
< Waiting for the debugger to disconnect...
debug> 

Como não havia nenhum outro breakpoint no programa, ele foi executado até o fim. Vamos parar o debugger utilizando Ctrl + C / Cmd + C duas vezes.

Adicionaremos desta vez um breakpoint em nosso programa:

// index.js 
const { promisify } = require('util');
const writeFile = promisify(require('fs').writeFile);
const stat = promisify(require('fs').stat);

(async function() {
  try {
    const filePath = 'arquivo.txt';
    await writeFile(filePath, 'conteúdo arquivo');
    console.log('arquivo criado com sucesso!');
    const fileStat = await stat(filePath);

    // breakpoint aqui!
    debugger;

    console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
  } catch(err) {
    console.log(err);
  }
})();

E mais uma vez vamos rodá-lo com o comando:

node inspect index

Teremos o mesmo comportamento que antes, pois o breakpoint default na primeira linha do script será disparado:

MacBook-Pro-de-Flavio:Desktop flavioalmeida$ node inspect index.js
< Debugger listening on ws://127.0.0.1:9229/9de73f77-46ec-4f7d-9d98-967d4f7544b5
< For help see https://nodejs.org/en/docs/inspector
< Debugger attached.

Break on start in index.js:1
> 1 (function (exports, require, module, __filename, __dirname) { const { promisify } = require('util');
  2 const writeFile = promisify(require('fs').writeFile);
  3 const stat = promisify(require('fs').stat);
debug> 

Agora, ao executarmos c o fluxo do nosso programa continuará até o proximo breakpoint:

debug> c
< arquivo criado com sucesso!
break in index.js:11
  9     await writeFile(filePath, 'conteúdo arquivo');
 10     console.log('arquivo criado com sucesso!');
>11     debugger; // breakpoint aqui!
 12     const fileStat = await stat(filePath);
 13 
debug> 

Queremos saber o valor de fileStat, mas para isso precisamos fazer com que a instrução seja processada. Podemos dar pequenos passos no debugger através do comando n (ou next):

debug> n
break in index.js:12
 10     console.log('arquivo criado com sucesso!');
 11     debugger; // breakpoint aqui!
>12     const fileStat = await stat(filePath);
 13 
 14     console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
debug> 

Porém, há um detalhe. Nosso debugger parou para avaliar a instrução stat(filePath). Ao executarmos mais uma vez a instrução n, a instrução await será processada. Por fim, na terceira vez que entrarmos com n, teremos o resultado na variável fileStart, pois já estaremos posicionados para executar a instrução console.log():

debug> n
break in index.js:12
 10     console.log('arquivo criado com sucesso!');
 11     debugger; // breakpoint aqui!
>12     const fileStat = await stat(filePath); // executando stat
 13 
 14     console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
debug> n
break in index.js:12
 10     console.log('arquivo criado com sucesso!');
 11     debugger; // breakpoint aqui!
>12     const fileStat = await stat(filePath); // processando await
 13 
 14     console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
debug> n
break in index.js:14
 12     const fileStat = await stat(filePath);
 13 
>14     console.log(`Tamanho do arquivo ${fileStat.size} bytes`);
 15   } catch(err) {
 16     console.log(err);

Agora, precisamos abrir o repl do Node que ganhará como contexto o ponto no qual nosso código esta parado:

debug> repl
Press Ctrl + C to leave debug repl
>

Por fim, vamos verificar o valor de fileStat:

debug> repl
Press Ctrl + C to leave debug repl
> fileStat
{ dev: 16777220,
  mode: 33188,
  nlink: 1,
  uid: 501,
  gid: 20,
  ... }
> 

Através de Ctrl + C / Cmd + C, saimos do repl e voltamos para o debugger. Ao utilizarmos o comando c mais uma vez, a aplicação chegará até o fim.

Há outros comando como o (out) e p (pause) que permitem pular uma instrução e pausar a depuração.

Excelente, agora que já temos um overview de como é feita a depuração de uma aplicação Node.js pelo terminal, vamos realizar os mesmos passos através do Chrome DevTools. Mas antes de cairmos dentro do assunto, remova o breakpoint debugger; de index.js. Vamos adicioná-lo de outra maneira.

Preparando a depuração para o Chrome DevTools

Para debugarmos nossa aplicação através do Chrome, também usaremos o parâmetro inspect, porém utilizaremos node --inspect index em vez de node inspect index apenas.

MBP-de-Flavio:Desktop flavioalmeida$ node --inspect index
Debugger listening on ws://127.0.0.1:9229/128e9932-2323-4309-b00c-0e9909584334
For help see https://nodejs.org/en/docs/inspector

arquivo criado com sucesso!
Tamanho do arquivo 17 bytes
MBP-de-Flavio:Desktop flavioalmeida$ 

Ops! Temos um problema! Não há um breakpoint padrão na aplicação que a pause temporariamente para termos chance de depurá-la. Inclusive, nem adiantará adicionarmos debugger; no início do arquivo que ele não será respeitado.

Para solucionarmos o problema anterior, basta mudarmos ligeiramente a instrução utilizada para node --inspect-brk index:

MBP-de-Flavio:Desktop flavioalmeida$ node --inspect-brk index
Debugger listening on ws://127.0.0.1:9229/0bc2d5f3-fde1-4455-bdbe-9dfa3552a82e
For help see https://nodejs.org/en/docs/inspector

O comando anterior realizou um breakpoint na primeira linha do arquivo. Como o debugger ainda continua rodando, podemos acessá-lo através do Chrome DevTool.

Debugando sua app Node com Chrome DevTools

Para debugarmos nossa aplicação através do Chrome, precisamos acessar o endereço chrome://inspect. Será exibida uma tela com uma série de informações.

A informação que nos interessa por agora é a Target. Ela possui um link que ao ser clicado nos direcionará para o depurador do Chrome DevTools.

Nele, visualizaremos nosso código envolvido por uma closure do depurador do Node.js. Vamos clicar na linha 12, exatamente no número da linha para adicionarmos um breakpoint:

Adicionando um breakpoint

Agora, na barra lateral superior direita, na barra de botões Barra de botões, através do botão play executamos o comando c (continue) fazendo com que nosso código execute até encontrar o próximo breakpoint que definimos. Neste ponto de parada, podemos escrutinar o valor da variável fileStat passando o mouse por cima. Também podemos marcar a variável e com o botão direito executar uma série de operações:

escrutinando o valor da variável

Um ambiente mais amistoso do que aquele oferecido pela depuração através do terminal.

Conclusão

Podemos debugar uma aplicação Node.js através do terminal ou através do Chrome DevTools. Este último fornece um ambiente de depuração visual independente do editor de texto ou IDE escolhida.