Dia 1 — Node.js: rodar JavaScript fora do navegador

Informatica · Conteudo · publicado em 30/09/2026
Dia 1 de 16

Node.js: rodar JavaScript fora do navegador

Aula 1

O que e Node.js e por que ele existe

O que é Node.js e por que ele existe

Node.js não é outra linguagem. É o mesmo JavaScript do 1º trimestre — o motor

V8, o mesmo Promise, o mesmo async/await — rodando dentro de um processo

do sistema operacional, em vez de dentro de uma aba do navegador. O arquivo .js

não muda de sintaxe. Muda o que existe em volta dele: onde o navegador oferece

document e window, o Node oferece process e os módulos de sistema.

OndeO que a linguagem faz sozinhaO que ela precisa de fora
Navegadoraltera a árvore da páginaa página precisa existir
Nodeescreve no terminal e na redeo processo precisa subir

Essa é a virada do trimestre: no navegador a linguagem sozinha produz efeito

visível; aqui nada acontece sem subir um processo, sem rota e sem banco.

O termo que aparece em toda documentação é backend: o código que roda no

servidor, respondendo requisições em vez de alterar a tela de quem está diante

dela. O JavaScript no servidor é a mesma linguagem — mesmo motor, mesma

sintaxe, mesmas Promise e async/await. O que muda não é o que se escreve,

é o que existe em volta do arquivo.

Rodar um programa de Node.js é entregar um arquivo .js ao runtime pelo prompt

do sistema, o mesmo onde se roda git e ls. A linha de comando é o nome

desse prompt: o lugar de onde o processo é chamado e onde a saída aparece.

node -v mostra a versão instalada e node app.js entrega o arquivo. Não

existe duplo clique nem aba: o ambiente de execução aqui é o processo do

sistema operacional, e por isso todo exemplo deste material termina em

console.log — o efeito deixou de ser a página mudar e passou a ser uma linha

no terminal ou uma resposta de HTTP.

Node.js nasceu na Heroku, em 2009, e a manutenção do projeto foi assumida pela

Node.js Foundation, hoje a OpenJS Foundation, que publica as versões.

O que o runtime entrega além da linguagem

O Node empacota o motor V8 do Chrome e liga nele o que o navegador não expõe:

sistema de arquivos, sockets, processo, sinais, criptografia. Tudo isso chega

por require, e todo módulo nativo tem o prefixo node:.

const http = require('node:http');      // servidor e cliente HTTP
const fs = require('node:fs');          // arquivos
const path = require('node:path');      // caminhos do sistema
const os = require('node:os');          // information do sistema

O prefixo node: é opcional no pacote de módulos nativo, mas é o que a

documentação usa e o que separa require('node:fs') de um pacote chamado fs

que alguém publicou no registro.

process.version e process.versions

process.version é a versão do Node; process.versions é um objeto com uma

chave por componente empacotado no runtime, incluindo v8. É o lugar de olhar

quando a dúvida é "o que este processo é, de verdade" — o mesmo espírito de

capturar a versão do banco em vez de escrevê-la à mão.

console.log(process.version);        // v24.21.0
console.log(process.versions.v8);    // versão do motor
console.log(Object.keys(process.versions));

Ordem de execução: microtask antes de macrotask

O console.log é síncrono e sai na hora. setTimeout(fn, 0) vira uma

macrotask. Promise.resolve().then(fn) vira uma microtask — e a microtask

sempre sai antes de qualquer macrotask, mesmo pedida depois do timer.

O exemplo imprime 1, 2, 3, 4 nessa ordem, e o motivo é esse: o Node

entrega o controle, vai buscar o próximo evento e volta. É o mesmo event loop

que faz um servidor HTTP atender várias requisições sem travar: o processo

nunca "para" esperando I/O, ele volta para a fila.

Exemplo

'use strict';

// Exemplo da aula 1 do dia 1: o runtime lido de dentro do proprio processo.
//
// A tese da aula e que o arquivo que roda em Node e o mesmo JavaScript do
// navegador. O que muda e quem esta em volta: em vez das globais de pagina
// web, existe `process`, que e o proprio processo do sistema operacional.

console.log('node:', process.version);
console.log('executavel:', process.execPath);
console.log('plataforma:', process.platform, '| arquitetura:', process.arch);

// O V8 e o motor de JavaScript. O Node nao reescreve a linguagem: ele empacota
// o mesmo motor do Chrome e liga nele o que o navegador nao tem — arquivo,
// socket, processo. Por isso `Promise`, `async` e `Map` funcionam igual nos dois.
console.log('motor V8:', process.versions.v8);
console.log('componentes empacotados:', Object.keys(process.versions).join(', '));

// Estas quatro sao globais: existem sem `require`, exatamente como no navegador.
console.log('sem require:', typeof process, typeof Buffer, typeof global, typeof setTimeout);

// A ordem das filas. `console.log` e sincrono e sai na hora; `setTimeout` vira
// macrotask; `Promise.resolve().then` vira microtask. A microtask sai ANTES de
// qualquer macrotask, mesmo tendo sido pedida depois do timer.
console.log('');
console.log('--- ordem de execucao ---');
console.log('1 - sincrono, sai na hora');
setTimeout(() => {
  console.log('4 - macrotask (setTimeout), resolvido por ultimo');
  console.log('agora o event loop esvaziou e o processo pode terminar');
}, 0);
Promise.resolve().then(() => console.log('3 - microtask (then), antes de qualquer macrotask'));
console.log('2 - sincrono, ainda antes da microtask');

// A ordem e 1, 2, 3, 4. E o mesmo event loop que faz um servidor HTTP atender
// varias requisicoes: o Node nunca "para" esperando I/O, ele entrega o controle
// e volta para buscar o proximo evento. Por isso o script acima ja terminou de
// ler quando estas ultimas duas linhas aparecem.

Saída real

node: v24.21.0
executavel: /root/.hermes/node/bin/node
plataforma: linux | arquitetura: x64
motor V8: 13.6.233.17-node.53
componentes empacotados: node, acorn, ada, amaro, ares, brotli, cldr, icu, llhttp, merve, modules, napi, nbytes, ncrypto, nghttp2, nghttp3, ngtcp2, openssl, simdjson, simdutf, sqlite, tz, undici, unicode, uv, uvwasi, v8, zlib, zstd
sem require: object function object function

--- ordem de execucao ---
1 - sincrono, sai na hora
2 - sincrono, ainda antes da microtask
3 - microtask (then), antes de qualquer macrotask
4 - macrotask (setTimeout), resolvido por ultimo
agora o event loop esvaziou e o processo pode terminar
Aula 2

O primeiro programa no terminal

O primeiro programa no terminal

Um programa de terminal responde a três perguntas: com qual nome ele foi

chamado, o que recebeu de argumento e como termina. As três têm resposta em

process, e as três mudam de comportamento entre o navegador e o Node.

node app.js 42 --semente

node arquivo.js e o node REPL

node arquivo.js é o comando inteiro: o runtime, o caminho do arquivo e o que

vier depois são argumentos, todos eles texto. É o mesmo arquivo que roda no

servidor de produção, sem mudança nenhuma.

node REPL é o mesmo runtime sem arquivo nenhum. Ele abre um prompt com >

esperando uma expressão, imprime o resultado e repete a espera; > 2 + 2

devolve 4 e > .exit fecha a sessão. REPL é a sigla de read-eval-print loop

— ler, avaliar, imprimir, repetir — e é o jeito mais rápido de conferir uma

hipótese sobre a linguagem sem abrir editor nenhum.

process.argv, process.cwd e __dirname

process.argv é uma lista: argv[0] é o executável do Node, argv[1] é o

caminho do arquivo e o resto são os argumentos de quem chamou — sempre texto.

process.cwd() é o diretório de trabalho, que não é necessariamente o

diretório do arquivo. __dirname é o diretório do próprio arquivo, e só existe

em CommonJS.

VariávelO que é
process.argv[executavel, arquivo, ...argumentos]
process.execPathcaminho do binário do Node em execução
process.cwd()diretório de trabalho do processo
__dirnamediretório do arquivo (CommonJS)
__filenamecaminho completo do arquivo (CommonJS)

A distinção entre process.argv[1] e __filename é o tipo: argv[1] vem da

linha de comando e pode nem ser um caminho resolvido, enquanto __filename é

sempre absoluto. Para montar caminho de arquivo, o que se usa é __dirname.

Código de saída: process.exitCode e process.exit

O shell lê o resultado do processo em $?, e esse número é o exit code do

programa. 0 é sucesso, qualquer outra coisa é falha — é o que um script de

deploy, um CI e um && no terminal usam para decidir se seguem.

O console.log no Node escreve a linha de saída do programa, e o exit code é o

outro canal: não aparece em lugar nenhum do texto impresso.

process.exitCode = 1 marca a saída sem matar o processo na hora — o Node

termina sozinho quando a fila esvazia, e o que ainda estava pendente termina.

process.exit(1) mata na hora e descarta o que não foi escrito ainda; ele é a

exceção, não a regra.

Os dois fluxos que o shell enxerga são stdout e stderr.

Um erro no terminal que ninguém tratou sai pelo segundo: stderr, com exit code

1 e o stack trace inteiro. O stdout fica limpo — é por isso que quem loga

servidor separa erro de resultado redirecionando só o segundo fluxo.

Stack trace: o que acontece sem tratamento

Erro não tratado derruba o processo: TypeError sai com código 1 e o stack

inteiro vai para o stderr. Erro tratado vira uma variável e o programa segue.

O exemplo dispara o erro sem tratamento em um processo filho, para mostrar

o código de saída real sem matar o exemplo.

try {
  divide(10, 0);
} catch (erro) {
  console.error(erro.name + ': ' + erro.message);   // stderr: o terminal
  console.log('  erro tratado:', erro.code, '-', erro.message);
}

O par das duas linhas é o padrão do material: o console.error mostra o erro

para quem está olhando o terminal, e o console.log é o que sobrevive na

página, porque a página embute o stdout.

Exemplo

'use strict';

// Exemplo da aula 2 do dia 1: rodar o arquivo, ver os argumentos e o codigo de
// saida.
//
// Um programa de terminal responde a tres perguntas: com qual nome eu fui
// chamado, o que recebi de argumento e como eu termino. As tres tem resposta
// em `process`.

console.log('executavel:', process.execPath);
console.log('diretorio atual:', process.cwd());

// `argv[0]` e o proprio node, `argv[1]` e o caminho do arquivo. O que vem depois
// disso sao os argumentos de quem chamou: `node app.js 42 --semente`.
console.log('process.argv inteiro:', JSON.stringify(process.argv));
const argumentos = process.argv.slice(2);
console.log('argumentos do usuario:', argumentos.length ? argumentos.join(' ') : '(nenhum)');

// O outro lado da linha de comando e a variavel de ambiente: nao aparece no
// `ps` de quem olha de fora, e por isso e onde mora o segredo.
console.log('NODE_ENV:', process.env.NODE_ENV || '(nao definido)');

// --- codigo de saida, com erro tratado ---
function divide(a, b) {
  if (b === 0) {
    const erro = new Error('divisao por zero');
    erro.code = 'ER_DIVISAO';
    throw erro;
  }
  return a / b;
}

console.log('');
console.log('10 / 2 =', divide(10, 2));
console.log('agora 10 / 0, dentro de try:');
try {
  divide(10, 0);
  console.log('  nao chega aqui');
} catch (erro) {
  console.error(erro.name + ': ' + erro.message);      // stderr: so o terminal
  console.log('  erro tratado:', erro.code, '-', erro.message);
  console.log('  primeira linha do stack:', erro.stack.split('\n')[1].trim());
}

// Erro NAO tratado e o outro caso: o processo morre com codigo 1 e imprime o
// stack inteiro no stderr. Para mostrar isso sem matar este exemplo, o erro
// acontece em um processo FILHO e o pai le o que o filho imprimiu.
console.log('');
console.log('processo filho com erro nao tratado:');
const { execFileSync } = require('node:child_process');
try {
  execFileSync(
    process.execPath,
    ['-e', 'const vazio = null; console.log(vazio.naoExiste);'],
    { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
  );
} catch (falha) {
  const linhas = String(falha.stderr).trim().split('\n');
  console.log('  codigo de saida do filho:', falha.status);
  console.log('  primeira linha:', linhas[0]);
  console.log('  origem:', linhas.slice(1).find((l) => l.trim().startsWith('at ')).trim());
  console.log('  no pai, o processo continua vivo');
}

// `process.exitCode` diz como terminar sem matar no meio: o Node sai sozinho
// quando nao ha mais nada na fila, e o shell le este valor no `$?`.
process.exitCode = 0;
console.log('');
console.log('este processo vai sair com exitCode', process.exitCode);

Saída real

executavel: /root/.hermes/node/bin/node
diretorio atual: /root/materiais/node-mysql
process.argv inteiro: ["/root/.hermes/node/bin/node","/root/materiais/node-mysql/codigo/t2/dia01/aula2.js"]
argumentos do usuario: (nenhum)
NODE_ENV: (nao definido)

10 / 2 = 5
agora 10 / 0, dentro de try:
  erro tratado: ER_DIVISAO - divisao por zero
  primeira linha do stack: at divide (/root/materiais/node-mysql/codigo/t2/dia01/aula2.js:26:18)

processo filho com erro nao tratado:
  codigo de saida do filho: 1
  primeira linha: [eval]:1
  origem: at [eval]:1:39
  no pai, o processo continua vivo

este processo vai sair com exitCode 0