Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

O que é Node.js e para que serve no back-end

Node.js é o JavaScript rodando fora do navegador: o que ele faz, onde é usado e o primeiro script executado no terminal, com a saída real.

Rodolfo Mori11 min de leitura

Node.js é um programa que executa JavaScript fora do navegador, direto no seu computador ou num servidor. Com ele, a mesma linguagem que troca a cor de um botão passa a ler arquivos, falar com banco de dados e responder requisições HTTP — que é o trabalho do back-end.

Todos os exemplos desta lição são de uma livraria de bairro, a Página Sete: catálogo, estoque e o primeiro servidor que devolve os livros em JSON. Toda saída colada aqui veio de uma execução real no Node 24.16.0, num MacBook com macOS 27.

A mesma língua, outro lugar de trabalho

Uma pessoa pode falar português numa loja ou numa oficina; o idioma não muda, mas as ferramentas ao alcance dela mudam completamente. JavaScript no navegador tem window, DOM e eventos de tela. No Node, a mesma sintaxe encontra arquivos, processos, rede e portas do sistema operacional.

É isso que significa chamar Node de runtime JavaScript fora do navegador. Ele executa a linguagem com o V8 e oferece APIs próprias ao redor dela. Ao rodar o primeiro arquivo, faça uma checagem simples: console.log funciona nos dois ambientes, mas document só existe em um. A diferença da saída localiza com precisão o que pertence à linguagem e o que pertence ao ambiente.

O JavaScript nasceu dentro do navegador, e por muito tempo só existiu lá. O navegador dá à linguagem um conjunto de brinquedos: a página (document), a janela (window), a caixinha de aviso (alert), o armazenamento local (localStorage). Nada disso é JavaScript — é o navegador emprestando poderes.

A linguagem em si é neutra. Variável, função, array, objeto, promise: isso existe em qualquer lugar onde haja um motor de JavaScript. Se você já entendeu o que é JavaScript, já sabe boa parte do Node. O que muda não é a linguagem — é a caixa de ferramentas em volta dela.

o que você quer fazer no navegador no Node
avisar alguma coisa alert() console.log()
guardar um dado localStorage um arquivo, ou um banco
buscar dado de outro sistema fetch() fetch() também
ler um arquivo do disco não pode node:fs
escutar uma porta de rede não pode node:http
saber quem chamou o programa não existe process.argv

As duas últimas linhas são o motivo de o Node existir. Um servidor precisa escutar uma porta e abrir arquivos; o navegador é proibido de fazer as duas coisas, de propósito, porque ele executa código de sites desconhecidos.

Rodando o primeiro arquivo no terminal

Sem HTML, sem <script>, sem abrir o navegador. Crie um arquivo catalogo.js numa pasta vazia:

js
const livros = [
  { titulo: 'Torto Arado', autor: 'Itamar Vieira Junior', preco: 54.9, estoque: 3 },
  { titulo: 'O Avesso da Pele', autor: 'Jeferson Tenório', preco: 49.9, estoque: 0 },
  { titulo: 'A Vida Invisível', autor: 'Martha Batalha', preco: 44.9, estoque: 7 },
];

const emFalta = livros.filter((livro) => livro.estoque === 0);

console.log(`Livraria Página Sete — ${livros.length} títulos no catálogo`);
console.log(`Sem estoque: ${emFalta.map((livro) => livro.titulo).join(', ')}`);

E rode passando o caminho do arquivo para o comando node:

bash
node catalogo.js
Livraria Página Sete — 3 títulos no catálogo Sem estoque: O Avesso da Pele

Foi isso. O node é um executável que recebe um arquivo, lê de cima para baixo, executa e encerra. Repare que console.log não abriu console nenhum: ele escreveu no seu terminal, que é a saída padrão do processo.

As três peças: V8, libuv e os módulos node:

O Node não é um bloco só. Ele é uma montagem de bibliotecas, e o próprio Node sabe dizer quais versões carregou. Peça a ele:

bash
node -p "process.versions"
{ node: '24.16.0', acorn: '8.16.0', ada: '3.4.4', amaro: '1.1.9', ares: '1.34.6', brotli: '1.2.0', cldr: '48.0', icu: '78.3', llhttp: '9.4.1', merve: '1.2.2', modules: '137', napi: '10', nbytes: '0.1.4', ncrypto: '0.0.1', nghttp2: '1.68.0', nghttp3: '', ngtcp2: '', openssl: '3.5.6', simdjson: '4.6.1', simdutf: '6.4.0', sqlite: '3.53.0', tz: '2026b', undici: '7.25.0', unicode: '17.0', uv: '1.52.1', uvwasi: '0.0.23', v8: '13.6.233.17-node.49', zlib: '1.3.1-e00f703', zstd: '1.5.7' }

Essa lista é o Node desmontado em cima da mesa, e vale ler com calma:

  • v8 é o motor que compila e executa o seu JavaScript. É o mesmo motor do Google Chrome. Quando alguém diz que “Node é rápido”, está falando do V8.
  • uv é a libuv, biblioteca em C que conversa com o sistema operacional — disco, rede, DNS — e que implementa o event loop.
  • undici é o cliente HTTP que faz o fetch funcionar no servidor; openssl cuida do HTTPS; zlib e brotli comprimem resposta; icu é quem sabe formatar R$ 1.234,56 em português; sqlite é um banco de dados que já vem dentro do Node desde a versão 22.

O -p é de print: ele avalia a expressão e imprime o resultado. Existe também o -e, de execute, que roda o código e não imprime nada por conta própria.

catalogo.js Node.js V8 executa o JavaScript libuv event loop e I/O módulos node: fs, http, path, crypto sistema operacional: disco, rede, processos

Os módulos internos vêm com o prefixo node:. Um relatório do estoque da livraria, lendo um estoque.json que está na mesma pasta:

js
const fs = require('node:fs/promises');
const path = require('node:path');

async function main() {
  const arquivo = path.join(__dirname, 'estoque.json');
  const livros = JSON.parse(await fs.readFile(arquivo, 'utf8'));
  const parado = livros.reduce((soma, l) => soma + l.preco * l.estoque, 0);

  console.log('Arquivo lido:', arquivo);
  console.log('Títulos:', livros.length);
  console.log('Parado em estoque: R$', parado.toFixed(2));
}

main();
Arquivo lido: /private/tmp/pagina-sete/estoque.json Títulos: 3 Parado em estoque: R$ 479.00

require é a forma antiga de importar, do sistema CommonJS, e é o que o Node usa quando não existe um package.json dizendo o contrário. A forma moderna é import, e a diferença entre as duas tem uma lição só para ela mais para a frente.

O REPL: testar uma ideia sem criar arquivo

Digite node sozinho, sem nome de arquivo, e você cai num console interativo — o REPL (read, eval, print, loop). É onde eu confiro uma dúvida de dois segundos sem poluir o projeto com um teste.js:

bash
node
Welcome to Node.js v24.16.0. Type ".help" for more information. > const preco = 54.9 undefined > preco * 3 164.7 > new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(preco * 3) 'R$ 164,70' > .exit

Duas coisas para reparar. A declaração const preco = 54.9 devolveu undefined, porque declarar não é um valor — o REPL imprime o resultado da expressão, e uma declaração não tem resultado. E o Intl formatou em real brasileiro sem instalar nada: é o icu da lista de versões trabalhando.

O que o Node não tem: window, document e localStorage

Essa é a parte que confunde quem vem do front-end. Vamos perguntar ao próprio Node quais globais existem:

js
const globais = ['window', 'document', 'alert', 'localStorage', 'process'];

for (const nome of globais) {
  console.log(nome.padEnd(13), typeof globalThis[nome]);
}
window undefined document undefined alert undefined localStorage undefined process object

Quatro undefined e um object. Agora a mesma lista, feita ao ambiente de navegador. Não dá para colar aqui o console do Chrome, então usei o jsdom — uma implementação do DOM escrita em JavaScript, a mesma que testes de front-end usam para simular uma página:

js
import { JSDOM } from 'jsdom';

const { window } = new JSDOM('<!doctype html><p>Livraria Página Sete</p>', {
  url: 'https://livraria.exemplo/catalogo',
});
const globais = ['window', 'document', 'alert', 'localStorage', 'process'];

for (const nome of globais) {
  console.log(nome.padEnd(13), typeof window[nome]);
}
window object document object alert function localStorage object process undefined

As duas tabelas são a imagem espelhada uma da outra. Onde o navegador tem página, o Node tem processo. O process é o objeto que descreve o programa em execução: os argumentos da linha de comando, as variáveis de ambiente, o código de saída. O DOM, do outro lado, simplesmente não existe no servidor — não há página para manipular.

fetch é a exceção simpática: existe nos dois desde o Node 18, com a mesma assinatura. O jsdom não implementa fetch, mas o navegador de verdade sim.

O erro que aparece na primeira semana

Quem chega do front-end tenta rodar no Node um código que só faz sentido na página. É sempre o mesmo arquivo: uma vitrine que monta <li> no DOM.

js
const livros = require('./estoque.json');

const lista = document.querySelector('#vitrine');

for (const livro of livros) {
  const item = document.createElement('li');
  item.textContent = `${livro.titulo} — R$ ${livro.preco.toFixed(2)}`;
  lista.appendChild(item);
}
/private/tmp/pagina-sete/vitrine.js:3 const lista = document.querySelector('#vitrine'); ^

ReferenceError: document is not defined at Object.<anonymous> (/private/tmp/pagina-sete/vitrine.js:3:15) at Module._compile (node:internal/modules/cjs/loader:1854:14) at Object..js (node:internal/modules/cjs/loader:1985:10) at Module.load (node:internal/modules/cjs/loader:1577:32) at Module._load (node:internal/modules/cjs/loader:1379:12) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)

Node.js v24.16.0

Repare que a linha 1 funcionou: o require do JSON passou sem reclamar. O problema não é o arquivo inteiro estar errado, é uma peça de navegador no lugar errado. ReferenceError: document is not defined quer dizer, ao pé da letra, “esse nome não existe neste ambiente”.

A correção nunca é instalar um pacote que finge ser navegador. É separar os papéis: o Node calcula e entrega os dados, o navegador desenha. Daqui a algumas lições, a livraria vai ter o back-end mandando JSON e o front-end montando a lista — cada um no seu ambiente.

Uma thread só não quer dizer devagar

Você vai ouvir que o Node é single-threaded. É verdade para o seu JavaScript: existe uma thread só executando o seu código. E, ainda assim, três consultas lentas terminam juntas.

js
const inicio = performance.now();

function consultar(fornecedor, ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`${fornecedor} respondeu em ${(performance.now() - inicio).toFixed(0)}ms`);
      resolve();
    }, ms);
  });
}

async function main() {
  await Promise.all([
    consultar('Distribuidora A', 300),
    consultar('Distribuidora B', 300),
    consultar('Distribuidora C', 300),
  ]);
  console.log(`total: ${(performance.now() - inicio).toFixed(0)}ms`);
}

main();
Distribuidora A respondeu em 302ms Distribuidora B respondeu em 304ms Distribuidora C respondeu em 304ms total: 304ms

Três esperas de 300ms somaram 304ms, não 900ms. Em cinco execuções seguidas o total ficou entre 301ms e 304ms. A espera não gasta thread: quem espera é o sistema operacional, via libuv, e o seu JavaScript fica livre nesse meio-tempo.

O reverso também é fácil de provar. Se o trabalho for cálculo puro, ele segura a thread e todo o resto fica na fila:

js
const inicio = performance.now();

setTimeout(() => {
  console.log(`atendimento chegou em ${(performance.now() - inicio).toFixed(0)}ms`);
}, 0);

let paginas = 0;
for (let i = 0; i < 3_000_000_000; i++) paginas += 1;

console.log(`contagem terminou em ${(performance.now() - inicio).toFixed(0)}ms`);
contagem terminou em 1524ms atendimento chegou em 1527ms

O setTimeout foi agendado para 0ms e só rodou depois de 1,5 segundo. Em cinco execuções o laço levou de 1274ms a 1589ms, e o timer sempre chegou 2ms depois dele. Num servidor de verdade, esse laço é o cliente que fica sem resposta.

Resumindo a regra: espera é de graça, cálculo é caro. O mecanismo que organiza isso é o event loop do Node.

Do script ao servidor: quinze linhas que respondem HTTP

Até aqui o Node rodou e encerrou. Um servidor faz o contrário: ele fica de pé, esperando. O módulo node:http já traz tudo, sem instalar nada.

js
const http = require('node:http');
const fs = require('node:fs/promises');
const path = require('node:path');

const servidor = http.createServer(async (req, res) => {
  if (req.url === '/livros') {
    const bruto = await fs.readFile(path.join(__dirname, 'estoque.json'), 'utf8');
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(bruto);
    return;
  }

  res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('Página Sete: rota não encontrada');
});

servidor.listen(3737, () => console.log('Catálogo no ar em http://localhost:3737/livros'));

Com o servidor rodando num terminal, bati nele com curl -i, que mostra os cabeçalhos junto com o corpo:

bash
curl -i http://localhost:3737/livros
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: Sat, 22 Aug 2026 19:30:27 GMT Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunked

[ { “titulo”: “Torto Arado”, “preco”: 54.9, “estoque”: 3 }, { “titulo”: “O Avesso da Pele”, “preco”: 49.9, “estoque”: 0 }, { “titulo”: “A Vida Invisível”, “preco”: 44.9, “estoque”: 7 } ]

E numa rota que não existe, o mesmo servidor devolve 404 com texto puro:

bash
curl -i http://localhost:3737/promocao
HTTP/1.1 404 Not Found Content-Type: text/plain; charset=utf-8 Date: Sat, 22 Aug 2026 19:30:27 GMT Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunked

Página Sete: rota não encontrada

Isso já é uma API. Feia, sem organização de rotas, sem validação — mas de pé, e qualquer front-end do mundo consegue consumir. É o mesmo caminho que a lição do servidor HTTP puro percorre com calma.

Onde o Node é usado de verdade

O Node aparece em quatro lugares distintos, e vale saber diferenciá-los porque as vagas usam palavras diferentes para cada um:

  • API de back-end. É o uso mais comum e o que gera vaga com o nome “Node”. Quase sempre com o Express, que é uma camada fina em cima do node:http para organizar rotas e middlewares. iFood, Nubank e boa parte das startups brasileiras têm serviços em Node nessa função.
  • Ferramenta de front-end. Vite, ESLint, Prettier, o próprio npm: tudo isso é programa Node. Quem só faz interface também precisa do Node instalado — ele é o motor por baixo do npm run dev.
  • Script de automação. Renomear mil arquivos, gerar relatório, importar planilha da livraria para o banco. É onde o Node substitui o shell script sem você precisar aprender outra linguagem.
  • Tempo real. Chat, notificação, dashboard que atualiza sozinho. O modelo de uma thread com muita espera é exatamente o perfil de milhares de conexões abertas fazendo pouca conta.

Para quem está começando no Brasil, o item que paga a conta é o primeiro. As vagas de júnior costumam pedir “Node + Express + Postgres ou Mongo”, e é essa combinação que a trilha de Node monta do começo ao fim.

Node, Deno e Bun: onde o Node ainda é a escolha segura

Existem dois concorrentes diretos, criados para consertar decisões antigas do Node. Os dois são bons. Nenhum dos dois é onde você deve começar hoje.

Node Deno Bun
idade 2009 2020 2022
motor V8 V8 JavaScriptCore
TypeScript direto não, precisa compilar sim sim
vagas no Brasil milhares dezenas dezenas
material em português enorme pouco pouco

Minha posição, e ela é uma escolha, não uma verdade: aprenda Node primeiro. Não porque seja tecnicamente superior — em vários pontos não é — mas porque o seu objetivo agora não é ter a ferramenta mais elegante, é conseguir a primeira vaga e ter onde procurar resposta quando travar às onze da noite. Quando o Node já for natural, migrar para Bun leva uma tarde: a linguagem é a mesma e a maior parte da API também.

O que instalar antes da próxima lição

Confira o que já existe na sua máquina:

bash
node --version
npm --version
v24.16.0 11.13.0

Se os dois comandos responderem com um número, você está pronto. Se algum disser “command not found”, ou se a versão do Node for menor que 20, o próximo passo é instalar o Node com o nvm — o gerenciador que deixa cada projeto na sua própria versão, sem quebrar os outros. Instalar pelo site oficial funciona, mas você vai se arrepender no dia em que um projeto antigo pedir Node 18.

Depois disso, a livraria Página Sete ganha package.json, dependências e rotas de verdade. O guia de Node mostra a ordem inteira, desta lição até o deploy da API.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • node
  • back-end
  • javascript
  • v8
  • terminal

Perguntas frequentes

Node.js é uma linguagem de programação?
Não. A linguagem é o JavaScript. O Node é o programa que executa esse JavaScript fora do navegador e entrega, junto, acesso a disco, rede e processos do sistema.
Preciso saber JavaScript antes de aprender Node?
Precisa. Variáveis, funções, array, objeto e promise são pré-requisito. Sem isso, você vai passar a lição inteira debatendo com a linguagem em vez de aprender o que o Node acrescenta.
Node serve só para back-end?
Não. Quase toda ferramenta moderna de front-end roda em cima do Node: Vite, ESLint, Prettier, o próprio npm. Mesmo quem só faz interface depende do Node instalado na máquina.
Qual versão do Node devo instalar?
A LTS mais recente, que é a versão com par no número (24, 22) e suporte estendido. As versões ímpares são de teste e saem de suporte rápido demais para um projeto que você quer manter.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. Node.js — About — nodejs.org
  2. Node.js Docs — process.versions — nodejs.org
  3. libuv — Design overview — docs.libuv.org

Continue por aqui