É muito comum, quando nos deparamos com algum bug em Node.js, começarmos a marcar vários trechos de código com `console.log`.
No entanto, conforme a coisa vai ficando séria, se torna cada vez mais difícil extrair informações úteis e precisas sobre o que (e onde) está acontecendo, além de surgirem saídas confusas dos logs.
Se preparando
Nesse artigo, iremos usar o VS Code como front-end para o debugger do Node.js. Para simular um caso próximo da realidade, vou usar como base uma aplicação feita usando `Node.js 9.3.0` e `Express 4`. A aplicação está disponível neste repositório.
Conhecendo a aplicação
Nossa aplicação é uma API CRUD para cadastro de usuários. As rotas são as seguintes:
Verbo HTTP | Rota | O que faz |
GET | /api/user | Lista os usuários cadastrados |
GET | /api/users/:user_id | Lista usuário de ID :user_id |
POST | /api/users | Cria novo usuário |
>Para criar um novo usuário, deve-se enviar um JSON com os seguintes campos: `username`, `real_name` e `country`. > > Exemplo: > ``` > { > "username": "hails", > "real_name": "Allan Jorge", > "country": "BR" > } > ```
Rodando e testando a aplicação
Após clonar o repositório do projeto, instale as dependências usando seu package manager favorito:
- yarn ```sh $ yarn ``` - npm ```sh $ npm install ```
Com as dependências instaladas, vamos rodar a aplicação:
```sh $ node index.js ``` ``` Server ready and listening on port 3000 ```
Com a aplicação rodando, podemos testá-la! Gosto de usar o HTTPie para fazer requests HTTP direto do meu terminal, mas você pode usar a sua ferramenta favorita. Vamos começar dando um `GET` na rota `/api/users` e ver o que ela retorna:
- Request ```sh $ http get :3000/api/users ``` - Response ```json [] ```
Legal! A API retornou um array vazio (`[]`), pois não temos nenhum usuário cadastrado. O próximo passo é cadastrar o primeiro usuário:
- Request ```sh $ echo '{"username": "hails", "real_name": "Allan Jorge", "country": "BR"}' | http post :3000/api/users ``` - Response ```json { "user_id": 1 } ```
Agora temos um usuário cadastrado. Que tal consultarmos pelo `user_id` dele?
- Request ```sh $ http get :3000/api/users/0 ``` - Response ```json { "user_id": 0 } ```
Essa resposta está estranha… Algo de errado não está certo!
Parece que temos um bug!
Tanto ao completar o Request quanto na Response, o que queremos de retorno é o objeto completo do usuário cadastrado, por exemplo:
```json { "user_id": 0, "username": "hails", "real_name": "Allan Jorge", "country": "BR" } ```
Mas só recebemos o `user_id` como retorno. Investigando a source, chegamos a conclusão que o problema deve estar no método `create`do arquivo `controllers/user.js`. O bug pode estar em qualquer lugar, dado que não estamos familiarizados com a codebase e a aplicação carece de testes automatizados.
Nossos instintos podem estar gritando para colocarmos alguns `console.log`s no código, como após a definição da variável `user`, ou depois da `userResponse`. Vamos com calma: temos ferramentas melhores para nos ajudar nesse caso!
Usando o Inspector do Node.js
Nota: essa feature ainda está em estado experimental
Desde a versão 6.3, o Node.js vem com suporte nativo ao Inspector Protocol, possibilitando o uso de breakpoints, introspecção de código e outras ferramentas de debbuging apenas com o uso de uma flag. Você pode ler mais sobre o Inspector nas docs oficiais do Node.js.
Iniciando o inspector agent do Node.js
Não se esqueça de terminar o processo da aplicação que usamos anteriormente.
Para começarmos a usar o Inspector, precisamos iniciar nossa aplicação usando a flag `–inspect` junto ao executável do Node. Ao invés de fazer como antes:
```sh $ node index.js ```
Vamos aplicar a flag:
```sh $ node --inspect index.js ```
Devemos ver uma mensagem sobre o `debugger` estar rodando, além do nosso servidor:
``` Debugger listening on ws://127.0.0.1:9229/a7d5b063-09c4-40e9-baac-318efee0ad9f For help see https://nodejs.org/en/docs/inspector Server ready and listening on port 3000 ```
A partir de agora, podemos usar qualquer front-end que suporte o Inspector Protocol para começarmos a debugar!
Debugando com o VS Code
Com o VS Code aberto, escolha a opção `Debug` no menu esquerdo ou usar o atalho `CTRL + Shift + D`:
Como VS Code, temos duas opções principais para debugarmos uma aplicação Node.js:
- Dar `attach` diretamente ao `debugger` (chamarei de `Node Attach`)
- Rodar a aplicação em modo debug direto pelo VS Code (chamarei de `Launch Program`)`
Node Attach
Nesse modo, o VS Code se comunica diretamente com o `debugger`, sendo possível até debugar uma aplicação que esteja em um servidor remoto! Vamos criar a configuração do `Node Attach`. No canto inferior esquerdo, ao lado de `DEBUG`, selecione a opção `Add Configuration…`
O arquivo de configuração é um `JSON` chamado `launch.json`, que pode ser configurado por projeto e conter diversos modos diferentes para o mesmo projeto. No nosso caso, vamos usar as configurações abaixo:
```json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "attach", "name": "Node Attach", "port": 9229 }, ] } ```
Para começar a debugar, basta ir no mesmo menu onde escolhemos a opção `Add Configuration…` e escolher agora a opção `Node Attach`. O código da aplicação estará disponível no canto inferior esquerdo, no menu `LOADED SCRIPTS`, com o nome do projeto — no caso `debugging-nodejs`:
Launch Program
Neste modo deixamos que o própio VS Code inicialize a aplicação, algo mais prático pra quando estamos desenvolvendo localmente usando o próprio VS Code.
> Como o próprio VS Code irá rodar a aplicação, não se esqueça de terminar qualquer instância do Node que ainda esteja rodando!
Usando praticamente os mesmos passos do `Node Attach`, vamos adicionar mais uma configuração, resultando no seguinte `launch.json`:
```json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}/index.js" }, { "type": "node", "request": "attach", "name": "Node Attach", "port": 9229 }, ] } ```
Repare que agora temos uma configuração com `name` de `Launch Program`, que vai executar o arquivo Javascript que estiver definido em `program`. No caso, queremos que ele execute o arquivo `index.js` que está na raiz do nosso projeto (`${workspaceFolder}`). Para começar a debugar, basta ir no mesmo menu onde escolhemos a opção `Add Configuration…` e escolher agora a opção `Launch Program`:
O código da aplicação estará disponível normalmente.
Gotta Debug ‘Em All!
Agora, vamos começar a debugar a aplicação para entender onde está o problema. Com a investigação anterior, percebemos que o problema está em algum lugar do método `create` do arquivo `controllers/user.js`. O próximo passo é colocar um `breakpoint` em alguma linha dentro do método, para que a execução seja pausada ali e possamos fazer uma introspecção no código.
Com o arquivo que você quer debugar aberto, basta clicar com o botão esquerdo do mouse bem do lado do número da linha que deseja para a execução. No nosso caso, optei por parar a execução na linha 21, pois assim conseguimos ver o estado de todas as variáveis que pertencem ao método.
Olhando mais de perto, repare no círculo vermelho ao lado da linha 21:
Toda vez que o arquivo é executado, a execução pausa nessa linha. Já com a aplicação rodando em modo `debug`, vamos fazer novamente um request para criar um usuário:
```sh $ echo '{"username": "hails", "real_name": "Allan Jorge", "country": "BR"}' | http post :3000/api/users ```
Perceba que não recebemos uma resposta, isso porque a aplicação está “congelada” na linha 21, esperando que tomemos alguma ação! Temos que descobrir onde está o problema e, para isso, iremos no menu `VARIABLES`, no canto esquerdo. Aqui conseguimos ver todas as variáveis que estão dentro dos nossos contextos (`Local`, `Closure` e `Global`).
Veja como está o objeto `user`, dentro de `Local`:
HOLY JESUS! Todas as chaves estão com os valores `undefined`, a não ser o `user_id`. Como pegamos esses valores do `req.params`, podemos inspecioná-lo a partir do objeto `req`:
O objeto `req.params` está vazio, por isso, quando tentamos pegar os valores dele para o objeto `user`, retorna `undefined`. Para os mais experientes com o Express, está na cara o erro: o `body` de um request `POST` no Express não fica em `req.params`, e sim em `req.body`, pois usei um `middleware` chamado `body-parser`. Podemos validar isso inspecionando o objeto `body`, como fizemos com o `params`:
Aha! Achamos onde estão as informações que precisamos! Agora é só alterar o código, referenciando `req.body` ao invés de `req.params` e rodar a aplicação.
- Request ```sh $ echo '{"username": "hails", "real_name": "Allan Jorge", "country": "BR"}' | http post :3000/api/users ``` - Response ```sh { "country": "BR", "real_name": "Allan Jorge", "user_id": 0, "username": "hails" } ```
Funcionou! Em poucos passos conseguimos achar onde estava o problema, sem acabar em um mar de `console.log` e sem ter que alterar uma linha da aplicação em si.
Considerações finais
Usar o Inspector do Node.js ajuda a debuggar comportamentos estranhos, entender conceitos como Closures ou simplesmente entender mais os internals da V8, acompanhando o passo-a-passo de cada ação. Além do VS Code, você pode usar o Chrome DevTools ou outros front-ends, basta consultar a documentação oficial do Inspector e ver o qual melhor atende às suas necessidades.