Pular para o conteúdo

Node.js: O Guia Completo para Iniciantes

Se você está começando no mundo da programação, provavelmente já se deparou com o nome Node.js em algum lugar. Seja num vídeo do YouTube, numa vaga de emprego ou numa conversa com algum dev mais experiente. E talvez você tenha ficado com aquela dúvida clássica: “mas o que exatamente é isso?”

Calma, porque você está no lugar certo.

Node.js é uma das tecnologias mais faladas e mais usadas no desenvolvimento web hoje em dia, e entender o que ele faz pode abrir muitas portas na sua carreira como programador. Mas antes de sair instalando tudo e rodando código, vale a pena entender o contexto todo, porque Node.js surgiu de uma necessidade real, de resolver um problema que os desenvolvedores enfrentavam no dia a dia.

Pensa assim: durante muitos anos, o JavaScript era uma linguagem que só funcionava dentro do navegador. Você escrevia um código JS e ele rodava ali, no Chrome, no Firefox, no seu navegador favorito. Era ótimo pra deixar sites interativos, mas tinha uma limitação enorme: você não conseguia usar JavaScript fora do navegador, no servidor, por exemplo.

Foi aí que em 2009 um desenvolvedor chamado Ryan Dahl resolveu mudar esse jogo. Ele criou o Node.js, uma plataforma que permite rodar JavaScript fora do navegador, diretamente no servidor. Isso foi uma revolução. De repente, um desenvolvedor que já sabia JavaScript podia usar a mesma linguagem tanto no front-end quanto no back-end, sem precisar aprender uma linguagem completamente nova pra lidar com o servidor.

E esse é só o começo da história. Ao longo desse artigo aqui no CulturaDev, a gente vai mergulhar fundo no mundo do Node.js, entender como ele funciona, ver exemplos práticos e descobrir por que ele ainda é uma escolha tão relevante hoje em dia.

O que é Node.js exatamente?

Vamos começar do zero mesmo, sem enrolação.

Node.js não é uma linguagem de programação. Essa é a primeira coisa que precisa ficar clara, porque muita gente confunde. JavaScript é a linguagem. Node.js é o ambiente que permite você rodar esse JavaScript fora do navegador, no seu computador, no servidor, onde você quiser.

Uma analogia que ajuda bastante a entender isso: pensa no JavaScript como a gasolina e no Node.js como o motor do carro. A gasolina sozinha não faz nada, ela precisa do motor pra gerar movimento. Da mesma forma, o JavaScript precisa de um ambiente pra ser executado, e o Node.js é esse ambiente quando você está fora do navegador.

Tecnicamente falando, o Node.js é construído em cima do motor V8, que é o mesmo motor que o Google Chrome usa pra interpretar e executar JavaScript. O que Ryan Dahl fez em 2009 foi pegar esse motor, que era restrito ao navegador, e adaptá-lo pra rodar em outros ambientes, especialmente no servidor.

Mas o que significa rodar JavaScript no servidor?

Quando você acessa um site, tem duas partes acontecendo. Tem o front-end, que é tudo que você vê na tela, os botões, as cores, os textos, e tem o back-end, que é a parte que fica no servidor, responsável por buscar dados no banco de dados, processar informações e devolver respostas pro seu navegador.

Antes do Node.js, se um desenvolvedor quisesse trabalhar no back-end, precisava aprender uma linguagem diferente, como PHP, Python, Ruby ou Java. Com Node.js, esse mesmo desenvolvedor pode usar JavaScript tanto no front-end quanto no back-end. Isso simplifica muito o processo de aprendizado e o fluxo de trabalho de um projeto.

Node.js também não é um framework

Outra confusão comum é misturar Node.js com frameworks como Express.js ou Next.js. O Node.js em si é a base, a fundação. Os frameworks são ferramentas construídas em cima do Node.js pra facilitar tarefas específicas. É como a diferença entre a fundação de uma casa e os móveis que você coloca dentro dela.

Olha um exemplo básico pra deixar isso mais concreto. Com Node.js, você consegue criar um servidor web com poucas linhas de código:

const http = require('http');

const servidor = http.createServer((requisicao, resposta) => {
  resposta.write('Olá, mundo!');
  resposta.end();
});

servidor.listen(3000, () => {
  console.log('Servidor rodando na porta 3000');
});

Esse código cria um servidor simples que responde com “Olá, mundo!” pra qualquer pessoa que acessar. Se você nunca programou antes, não precisa entender cada linha agora, mas perceba como é simples criar algo funcional com Node.js.

Em resumo, Node.js é uma plataforma de execução de JavaScript no lado do servidor, leve, eficiente e que abriu portas pra uma forma completamente nova de desenvolver aplicações web. E é exatamente essa eficiência que vamos explorar no próximo tópico, quando a gente entender como ele funciona por baixo dos panos.

Como o Node.js funciona por baixo dos panos

Agora que você já sabe o que é Node.js, chegou a hora de entender como ele funciona de verdade. E pra isso, a gente precisa falar sobre dois conceitos que são o coração do Node.js: o modelo non-blocking I/O e o event loop.

Pode parecer assustador no começo, mas eu prometo que vai fazer sentido com as analogias certas.

O problema dos servidores tradicionais

Imagina que você está num restaurante e cada garçom só consegue atender uma mesa por vez. Enquanto ele está esperando a cozinha preparar o prato de uma mesa, ele fica parado ali, sem fazer nada, sem atender ninguém. Pra atender mais mesas ao mesmo tempo, o restaurante precisa contratar mais garçons.

Esse é exatamente o modelo que servidores tradicionais usavam, e em alguns casos ainda usam. Cada requisição que chega ao servidor cria uma nova thread, que é basicamente um processo separado. Se chegarem mil requisições ao mesmo tempo, o servidor precisa criar mil threads. Isso consome muita memória e processamento, e em algum momento o servidor simplesmente não aguenta mais.

Linguagens como PHP e Java trabalharam muito nesse modelo, que a gente chama de blocking, ou seja, bloqueante. Uma operação precisa terminar completamente antes que a próxima comece.

A solução do Node.js: non-blocking I/O

O Node.js resolveu esse problema de uma forma elegante. Voltando ao restaurante, imagina agora um garçom diferente. Ele anota o pedido da mesa um, leva pra cozinha, e enquanto a cozinha prepara, ele já vai anotar o pedido da mesa dois, da mesa três, da mesa quatro. Quando a cozinha avisa que o prato da mesa um ficou pronto, ele vai lá e entrega. Esse garçom consegue atender muito mais mesas sem precisar ficar parado esperando.

Esse é o modelo non-blocking do Node.js. Ele não fica esperando uma operação terminar pra começar a próxima. Enquanto espera uma resposta do banco de dados, por exemplo, ele já vai processando outras requisições. Isso torna o Node.js extremamente eficiente pra aplicações que precisam lidar com muitas conexões simultâneas.

Olha como isso aparece no código na prática. Compara esses dois exemplos:

Esse seria um comportamento blocking, simulado em JavaScript:

const resultado = buscarDadosNoBancoDeDados(); // espera terminar
console.log(resultado); // só executa depois
console.log('Isso só aparece depois que os dados chegarem');

E esse é o comportamento non-blocking, do jeito que o Node.js trabalha:

buscarDadosNoBancoDeDados((resultado) => {
  console.log(resultado); // executa quando os dados chegarem
});
console.log('Isso aparece imediatamente, sem esperar os dados');

No segundo exemplo, o Node.js não trava o programa esperando os dados chegarem. Ele continua executando o resto do código e, quando os dados estiverem prontos, executa a função de callback com o resultado. Esse conceito de callback é muito importante no mundo do Node.js e você vai encontrá-lo bastante ao longo dos seus estudos.

O Event Loop

Agora que você entendeu o non-blocking, fica mais fácil entender o event loop, que é o mecanismo que torna tudo isso possível.

O event loop é basicamente um ciclo infinito que o Node.js fica rodando, verificando se tem alguma tarefa pra executar. Ele funciona numa fila de eventos. Quando uma operação assíncrona termina, como uma consulta ao banco de dados ou uma leitura de arquivo, ela joga um evento nessa fila. O event loop pega esse evento e executa a função associada a ele.

Pensa assim: o event loop é como um coordenador de eventos numa festa. Ele fica de olho em tudo que está acontecendo, e quando algo termina ou precisa de atenção, ele age. Mas ele nunca fica parado esperando uma coisa específica acontecer, ele continua circulando e gerenciando tudo ao mesmo tempo.

Visualmente, o fluxo funciona mais ou menos assim:

Requisição chega
      |
      v
Node.js registra a operação e continua
      |
      v
Operação termina em segundo plano
      |
      v
Event Loop detecta e executa o callback
      |
      v
Resposta é enviada

Por que isso importa na prática?

Esse modelo faz com que o Node.js seja uma escolha excelente pra certos tipos de aplicação. Ele brilha especialmente em aplicações em tempo real, como chats, sistemas de notificação, jogos online e APIs que precisam lidar com muitas requisições simultâneas.

Por outro lado, ele não é a melhor escolha pra tarefas que exigem muito processamento da CPU, como cálculos matemáticos pesados ou processamento de imagens, porque o event loop é single-threaded, ou seja, roda numa única thread. Se você travar essa thread com um cálculo demorado, todo o resto para.

Mas pra maioria das aplicações web do dia a dia, o Node.js é mais do que suficiente e incrivelmente eficiente. E agora que você entende como ele funciona por dentro, fica muito mais fácil tomar decisões inteligentes sobre quando e como usá-lo nos seus projetos.

Por que aprender Node.js hoje em dia?

Essa é uma pergunta que muita gente faz antes de investir tempo aprendendo uma tecnologia nova. E faz todo sentido perguntar isso, afinal, seu tempo é valioso e você quer ter certeza de que está estudando algo que realmente vai fazer diferença na sua carreira.

A resposta curta é: sim, vale muito a pena. Mas vamos além da resposta curta.

O mercado de trabalho pede Node.js

Se você abrir qualquer site de vagas de emprego agora e pesquisar por desenvolvedor back-end ou desenvolvedor full-stack, vai perceber que Node.js aparece em uma quantidade absurda de vagas. Empresas de todos os tamanhos, desde startups pequenas até gigantes da tecnologia, usam Node.js nos seus projetos.

E não são empresas qualquer. Netflix, LinkedIn, NASA, Uber, PayPal e até o próprio Twitter já usaram ou ainda usam Node.js em partes dos seus sistemas. O PayPal, por exemplo, migrou parte da sua aplicação pra Node.js e relatou que o tempo de resposta das páginas caiu pela metade depois da migração. Isso diz muito sobre o poder dessa tecnologia.

Pra quem está começando agora, isso significa que aprender Node.js aumenta consideravelmente as suas chances de conseguir uma vaga, seja como estagiário, júnior ou até freelancer.

Uma linguagem, dois mundos

Um dos maiores atrativos do Node.js pra quem está aprendendo programação é a possibilidade de usar JavaScript tanto no front-end quanto no back-end. Isso é um baita diferencial.

Pensa na jornada de um desenvolvedor que quer trabalhar com web. Sem Node.js, ele precisaria aprender JavaScript pra front-end e depois partir pra uma linguagem completamente diferente pra back-end, seja Python, PHP, Java ou outra. São duas curvas de aprendizado separadas, dois ecossistemas diferentes, duas formas de pensar o código.

Com Node.js, você aprende JavaScript uma vez e consegue construir aplicações completas, do front ao back, usando a mesma linguagem. Isso acelera muito o aprendizado e permite que iniciantes construam projetos completos mais rápido do que fariam de outra forma.

O ecossistema NPM

Outro ponto que faz o Node.js se destacar é o NPM, que é o gerenciador de pacotes que vem junto com ele. O NPM tem mais de dois milhões de pacotes disponíveis, que são basicamente pedaços de código prontos que você pode usar nos seus projetos.

Precisa enviar e-mails pela sua aplicação? Tem pacote pra isso. Quer autenticar usuários com Google ou Facebook? Tem pacote pra isso também. Precisa conectar com um banco de dados? Tem vários pacotes pra isso. Esse ecossistema gigante significa que você não precisa reinventar a roda toda vez que começa um projeto novo, e isso aumenta muito a sua produtividade.

Node.js é leve e rápido

A gente já falou sobre o model non-blocking e o event loop no tópico anterior, mas vale reforçar aqui: Node.js é genuinamente rápido e eficiente. Ele consegue lidar com milhares de conexões simultâneas usando poucos recursos de servidor, o que também tem impacto no custo de infraestrutura de uma aplicação.

Pra empresas, isso significa menos gasto com servidores. Pra você como desenvolvedor, significa que suas aplicações vão performar bem mesmo quando crescerem.

A comunidade é enorme

Quando você está aprendendo algo novo, ter uma comunidade ativa por trás faz toda a diferença. E a comunidade do Node.js é uma das maiores e mais ativas do mundo do desenvolvimento.

Isso significa que quando você travar num problema, e acredite, vai travar, você vai encontrar respostas no Stack Overflow, tutoriais no YouTube, artigos em blogs como o CulturaDev e pessoas dispostas a ajudar em fóruns e grupos. Você raramente vai se deparar com um problema que ninguém nunca teve antes.

Além disso, o Node.js é um projeto de código aberto, mantido pela OpenJS Foundation, com contribuições de desenvolvedores do mundo todo. Isso garante que a tecnologia continue evoluindo e sendo mantida por muito tempo.

É uma boa escolha pra projetos pessoais também

Nem todo mundo aprende programação pra trabalhar numa empresa. Muita gente quer criar seus próprios produtos, resolver seus próprios problemas ou simplesmente explorar a tecnologia por curiosidade. Pra esse perfil, Node.js também é uma ótima escolha.

Com ele, você consegue criar desde uma API simples pra um projeto pessoal até uma aplicação completa com autenticação, banco de dados e deploy em produção. E como o ecossistema é tão rico, você vai encontrar ferramentas pra praticamente qualquer coisa que quiser construir.

Instalando o Node.js na sua máquina

Chega de teoria por enquanto. Vamos colocar a mão na massa e instalar o Node.js no seu computador. O processo é bem simples e não vai tomar muito do seu tempo, independente do sistema operacional que você usa.

Antes de instalar: LTS ou Current?

Quando você acessar o site oficial do Node.js em nodejs.org, vai se deparar com duas opções de download: LTS e Current.

LTS significa Long Term Support, ou seja, suporte de longo prazo. Essa é a versão mais estável, recomendada pra maioria das pessoas, especialmente pra quem está começando agora. Ela recebe atualizações de segurança por um período longo e é a escolha mais segura pra projetos reais.

Current é a versão mais recente, com as funcionalidades mais novas, mas que pode ter algumas instabilidades. A menos que você tenha um motivo específico pra usar essa versão, fica com a LTS mesmo.

Instalando no Windows

No Windows o processo é bastante direto. Acesse nodejs.org, clique no botão de download da versão LTS e baixe o instalador com extensão .msi. Depois é só seguir o processo de instalação como qualquer outro programa, clicando em next, next, next e finish.

Depois que a instalação terminar, abra o Prompt de Comando ou o PowerShell e digite os seguintes comandos pra confirmar que tudo foi instalado corretamente:

node --version
npm --version

Se aparecer o número da versão nos dois comandos, está tudo certo. Você vai ver algo parecido com isso:

v20.11.0
10.2.4

Os números podem ser diferentes dependendo da versão que você instalou, mas o importante é que apareça algum número sem mensagem de erro.

Instalando no Mac

No Mac você tem duas opções. A primeira é pelo site oficial, igual ao Windows. Acessa nodejs.org, baixa o instalador com extensão .pkg e segue o processo de instalação normalmente.

A segunda opção, e a que muitos desenvolvedores preferem no Mac, é usar o Homebrew, que é um gerenciador de pacotes pra Mac. Se você já tem o Homebrew instalado, basta abrir o terminal e digitar:

brew install node

O Homebrew vai baixar e instalar o Node.js automaticamente. Depois é só verificar a instalação com os mesmos comandos de antes:

node --version
npm --version

Instalando no Linux

No Linux o processo varia um pouco dependendo da distribuição que você usa. Se você usa Ubuntu ou alguma distribuição baseada nele, o caminho mais recomendado é usar o NVM, que a gente vai falar mais daqui a pouco. Mas se quiser uma instalação rápida, você pode usar o apt:

sudo apt update
sudo apt install nodejs npm

Depois confirma a instalação:

node --version
npm --version

NVM: a forma mais inteligente de instalar o Node.js

Se você pretende trabalhar com Node.js de forma mais séria, vale muito a pena conhecer o NVM, que significa Node Version Manager. Ele é um gerenciador de versões do Node.js que permite você ter várias versões instaladas na mesma máquina e alternar entre elas com facilidade.

Isso é muito útil na prática porque projetos diferentes podem exigir versões diferentes do Node.js. Com o NVM, você consegue trocar de versão com um único comando, sem precisar desinstalar e reinstalar nada.

Pra instalar o NVM no Mac ou Linux, abra o terminal e rode:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

Depois de instalar o NVM, feche e abra o terminal novamente e instale o Node.js com:

nvm install --lts
nvm use --lts

No Windows, o NVM funciona de forma diferente. Existe uma versão chamada nvm-windows que você pode encontrar no GitHub em github.com/coreybutler/nvm-windows. O processo de instalação é bem documentado na página do projeto.

Testando sua instalação com o primeiro comando

Agora que o Node.js está instalado, vamos fazer um teste rápido pra ter certeza que tudo está funcionando. Abra o terminal ou prompt de comando e digite:

node

Isso vai abrir o REPL do Node.js, que é um ambiente interativo onde você pode digitar código JavaScript e ver o resultado na hora. Tenta digitar isso:

console.log('Node.js funcionando!')

Se aparecer “Node.js funcionando!” no terminal, parabéns. Seu ambiente está configurado e pronto pra uso. Pra sair do REPL, é só pressionar Ctrl + C duas vezes ou digitar .exit.

Um editor de código pra facilitar sua vida

Antes de partir pro próximo tópico, vale mencionar que você vai precisar de um editor de código pra escrever seus programas em Node.js. O mais recomendado pra quem está começando, e pra quem já é experiente também, é o Visual Studio Code, conhecido como VS Code.

Ele é gratuito, leve, tem suporte excelente pra JavaScript e Node.js e conta com uma biblioteca enorme de extensões que facilitam muito o desenvolvimento. Você pode baixá-lo em code.visualstudio.com.

Com o Node.js instalado e o VS Code configurado, você já tem tudo que precisa pra começar a programar. E é exatamente isso que a gente vai fazer no próximo tópico, onde você vai escrever seu primeiro código com Node.js de verdade.

Seu primeiro código com Node.js

Esse é o momento que muita gente espera quando começa a aprender uma tecnologia nova. Vamos escrever código de verdade, rodar ele na sua máquina e ver as coisas funcionando. E eu prometo que vai ser mais simples do que você imagina.

O clássico Hello World

Todo mundo começa pelo Hello World, e não seria diferente aqui. Vamos criar o primeiro arquivo JavaScript e rodá-lo com Node.js.

Abre o VS Code, cria uma pasta nova no seu computador com o nome que quiser, por exemplo projeto-node, e dentro dela cria um arquivo chamado index.js. Agora digita o seguinte código dentro desse arquivo:

console.log('Hello World!');

Salva o arquivo e abre o terminal. Navega até a pasta que você criou usando o comando cd, que significa change directory:

cd projeto-node

Agora roda o arquivo com Node.js:

node index.js

Se aparecer Hello World! no terminal, funcionou. Você acabou de rodar seu primeiro programa com Node.js. Simples assim.

Mas claro que a gente não vai parar por aí. Vamos evoluindo aos poucos.

Trabalhando com variáveis e funções

Vamos deixar o código um pouco mais interessante. Apaga o que estava no index.js e escreve isso:

function saudacao(nome) {
  return 'Olá, ' + nome + '! Bem-vindo ao mundo do Node.js.';
}

const meuNome = 'João';
console.log(saudacao(meuNome));

Roda novamente com node index.js e você vai ver:

Olá, João! Bem-vindo ao mundo do Node.js.

Aqui a gente criou uma função chamada saudacao que recebe um nome como parâmetro e retorna uma mensagem personalizada. Depois criamos uma variável com um nome e passamos ela pra função. Nada muito complexo, mas já mostra como o JavaScript funciona no ambiente do Node.js da mesma forma que funcionaria no navegador.

Lendo arquivos com o módulo fs

Agora vamos fazer algo que você não conseguiria fazer com JavaScript puro no navegador: ler um arquivo do seu computador. Isso é possível graças ao módulo fs, que significa file system, ou seja, sistema de arquivos.

Dentro da mesma pasta projeto-node, cria um arquivo chamado mensagem.txt e escreve qualquer coisa dentro dele, por exemplo:

Esse é o meu primeiro arquivo lido com Node.js!

Agora no seu index.js, escreve o seguinte código:

const fs = require('fs');

fs.readFile('mensagem.txt', 'utf8', (erro, conteudo) => {
  if (erro) {
    console.log('Aconteceu um erro ao ler o arquivo:', erro);
    return;
  }
  console.log('Conteúdo do arquivo:');
  console.log(conteudo);
});

console.log('Essa linha aparece antes do conteúdo do arquivo!');

Roda com node index.js e presta atenção no resultado:

Essa linha aparece antes do conteúdo do arquivo!
Conteúdo do arquivo:
Esse é o meu primeiro arquivo lido com Node.js!

Percebeu algo interessante? A última linha do código apareceu primeiro no terminal. Isso é o comportamento non-blocking que a gente explicou no tópico anterior funcionando na prática. O Node.js não esperou o arquivo ser lido pra continuar executando o código. Ele registrou a operação de leitura, continuou rodando o resto e quando a leitura terminou, executou o callback com o conteúdo do arquivo.

Esse é um dos conceitos mais importantes do Node.js e ver ele funcionando na prática ajuda muito a fixar o entendimento.

Criando um servidor web simples

Agora vamos dar um passo maior e criar um servidor web do zero. Isso vai mostrar o verdadeiro poder do Node.js pra desenvolvimento web.

Cria um arquivo novo chamado servidor.js na mesma pasta e escreve o seguinte:

const http = require('http');

const servidor = http.createServer((requisicao, resposta) => {
  resposta.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  resposta.write('Olá! Esse é meu primeiro servidor com Node.js.');
  resposta.end();
});

servidor.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});

Roda esse arquivo com:

node servidor.js

Vai aparecer no terminal:

Servidor rodando em http://localhost:3000

Agora abre o navegador e acessa http://localhost:3000. Você vai ver a mensagem “Olá! Esse é meu primeiro servidor com Node.js.” aparecer na tela do navegador.

Pausa um segundo e pensa no que acabou de acontecer. Você criou um servidor web funcional com menos de dez linhas de código. Esse servidor está rodando na sua máquina, escutando requisições na porta 3000 e respondendo com uma mensagem. Isso é impressionante pra quem está começando agora.

Pra parar o servidor, volta no terminal e pressiona Ctrl + C.

Deixando o servidor um pouco mais esperto

Vamos evoluir um pouco esse servidor. Que tal ele responder coisas diferentes dependendo da página que o usuário acessa? Atualiza o arquivo servidor.js com esse código:

const http = require('http');

const servidor = http.createServer((requisicao, resposta) => {
  resposta.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });

  if (requisicao.url === '/') {
    resposta.write('Bem-vindo à página inicial!');
  } else if (requisicao.url === '/sobre') {
    resposta.write('Essa é a página sobre.');
  } else if (requisicao.url === '/contato') {
    resposta.write('Essa é a página de contato.');
  } else {
    resposta.write('Página não encontrada.');
  }

  resposta.end();
});

servidor.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});

Roda novamente com node servidor.js e testa acessando diferentes endereços no navegador:

http://localhost:3000 vai mostrar a página inicial. http://localhost:3000/sobre vai mostrar a página sobre. http://localhost:3000/contato vai mostrar a página de contato. http://localhost:3000/qualquercoisa vai mostrar página não encontrada.

Você acabou de criar um servidor com roteamento básico. Claro que na vida real você usaria um framework como o Express.js pra fazer isso de forma mais organizada e eficiente, mas entender como funciona por baixo dos panos é fundamental.

Entendendo o require

Você deve ter percebido que nos exemplos anteriores usamos bastante o require. Ele é a forma que o Node.js usa pra importar módulos, que são basicamente arquivos ou pacotes com funcionalidades específicas.

No caso do http e do fs, são módulos nativos do Node.js, ou seja, já vêm instalados junto com ele e você não precisa instalar nada extra. Mas você também pode criar seus próprios módulos e importá-los nos seus arquivos.

Cria um arquivo chamado calculadora.js com esse conteúdo:

function somar(a, b) {
  return a + b;
}

function subtrair(a, b) {
  return a - b;
}

module.exports = { somar, subtrair };

E agora no index.js, importa e usa esse módulo:

const calculadora = require('./calculadora');

console.log(calculadora.somar(10, 5));
console.log(calculadora.subtrair(10, 5));

Roda com node index.js e vai ver:

15
5

Isso mostra como você pode organizar seu código em módulos separados e reutilizá-los em diferentes partes do projeto. Essa é uma prática muito comum e importante no desenvolvimento com Node.js.

NPM: o gerenciador de pacotes do Node.js

Se o Node.js já é poderoso por si só, o NPM é o que transforma ele numa ferramenta verdadeiramente completa. E entender como o NPM funciona é tão importante quanto entender o próprio Node.js.

O que é o NPM?

NPM significa Node Package Manager, ou gerenciador de pacotes do Node. Ele é instalado automaticamente junto com o Node.js, então se você seguiu o tópico anterior e instalou o Node.js corretamente, o NPM já está na sua máquina.

Mas o que exatamente é um gerenciador de pacotes? Pensa assim: pacotes são pedaços de código que outras pessoas escreveram e disponibilizaram pra comunidade usar gratuitamente. Em vez de você escrever do zero uma funcionalidade complexa, você simplesmente instala um pacote que já faz isso pra você.

É parecido com o conceito de aplicativos no seu celular. Você não precisa programar um aplicativo de mapas do zero pra saber como chegar num lugar. Você simplesmente baixa o Google Maps. O NPM é essa loja de aplicativos, mas pra desenvolvedores Node.js.

O repositório do NPM tem mais de dois milhões de pacotes disponíveis, e esse número cresce todo dia. É praticamente impossível você precisar de alguma funcionalidade e não encontrar um pacote que já resolva isso.

Iniciando um projeto com NPM

Antes de instalar qualquer pacote, a gente precisa inicializar o projeto. Cria uma pasta nova chamada meu-projeto-npm, abre ela no terminal e digita:

npm init

O NPM vai te fazer uma série de perguntas sobre o projeto, como nome, versão, descrição e autor. Você pode responder cada uma ou simplesmente apertar Enter pra aceitar os valores padrão. Se quiser pular todas as perguntas de uma vez e aceitar tudo automaticamente, usa:

npm init -y

Depois disso, vai aparecer um arquivo chamado package.json na sua pasta. Abre ele e vai ver algo parecido com isso:

{
  "name": "meu-projeto-npm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Esse arquivo é o coração do seu projeto. Ele guarda informações sobre o projeto e, principalmente, registra todos os pacotes que você instalar. Pensa nele como a identidade do seu projeto.

Instalando seu primeiro pacote

Vamos instalar um pacote pra ver como funciona na prática. A gente vai usar o chalk, que é um pacote que permite colorir textos no terminal. É simples, visual e ótimo pra entender o processo de instalação.

No terminal, dentro da pasta do projeto, digita:

npm install chalk

O NPM vai baixar o pacote e você vai notar duas coisas novas na sua pasta. A primeira é uma pasta chamada node_modules, que é onde todos os pacotes instalados ficam guardados. A segunda é um arquivo chamado package-lock.json, que registra as versões exatas de cada pacote instalado.

Se você abrir o package.json agora, vai ver que ele foi atualizado automaticamente com o chalk nas dependências:

{
  "dependencies": {
    "chalk": "^5.3.0"
  }
}

Agora cria um arquivo index.js e usa o chalk no código:

import chalk from 'chalk';

console.log(chalk.green('Esse texto está verde!'));
console.log(chalk.blue('Esse texto está azul!'));
console.log(chalk.red('Esse texto está vermelho!'));
console.log(chalk.yellow.bold('Esse texto está amarelo e em negrito!'));

Para usar o chalk na versão mais recente, você precisa adicionar uma linha no seu package.json. Abre ele e adiciona:

{
  "type": "module"
}

Agora roda com node index.js e vai ver os textos coloridos aparecendo no terminal. É um exemplo simples, mas mostra perfeitamente como instalar e usar um pacote externo no seu projeto.

Dependências de desenvolvimento

Existe um detalhe importante sobre instalação de pacotes que vale mencionar. Alguns pacotes você só precisa durante o desenvolvimento, não quando a aplicação está rodando em produção. Pra instalar um pacote como dependência de desenvolvimento, você usa a flag –save-dev:

npm install nodemon --save-dev

O nodemon é um exemplo clássico de dependência de desenvolvimento. Ele monitora os arquivos do seu projeto e reinicia o servidor automaticamente toda vez que você salva uma alteração, o que economiza muito tempo durante o desenvolvimento porque você não precisa ficar parando e reiniciando o servidor manualmente.

No package.json, essas dependências ficam numa seção separada:

{
  "devDependencies": {
    "nodemon": "^3.0.0"
  }
}

Scripts no package.json

Outra funcionalidade muito útil do NPM são os scripts. Você pode definir comandos personalizados no package.json e rodá-los com npm run. Atualiza a seção scripts do seu package.json assim:

{
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  }
}

Agora em vez de digitar node index.js toda vez, você pode simplesmente rodar:

npm run start

Ou pra rodar com o nodemon durante o desenvolvimento:

npm run dev

Isso pode parecer um detalhe pequeno agora, mas em projetos maiores com comandos mais complexos, os scripts do package.json são um recurso que você vai usar o tempo todo.

O arquivo node_modules e o gitignore

Um ponto importante que muita gente ignora no começo: a pasta node_modules nunca deve ser enviada pro GitHub ou qualquer outro repositório de código. Ela pode ter centenas de megabytes e não faz sentido versionar ela, porque qualquer pessoa que clonar seu projeto pode simplesmente rodar npm install e o NPM vai baixar todos os pacotes automaticamente baseado no package.json.

Pra evitar que a pasta node_modules seja enviada acidentalmente, cria um arquivo chamado .gitignore na raiz do projeto e adiciona dentro dele:

node_modules

Isso instrui o Git a ignorar essa pasta completamente. É uma prática padrão em todo projeto Node.js e você vai ver esse arquivo em praticamente qualquer projeto que encontrar no GitHub.

O NPM é uma ferramenta que você vai usar todos os dias como desenvolvedor Node.js, e entender como ele funciona desde o começo faz uma diferença enorme na sua produtividade.

Módulos no Node.js: entendendo o sistema de importação

Se você prestou atenção nos exemplos anteriores, já viu o sistema de módulos do Node.js em ação. Aquele require que apareceu várias vezes nos códigos não estava lá por acaso. Ele é parte fundamental de como o Node.js organiza e compartilha código entre arquivos diferentes.

Nesse tópico a gente vai entender de verdade como esse sistema funciona, quais são os tipos de módulos que existem e qual a diferença entre as duas formas de importar código que você vai encontrar por aí.

O que é um módulo?

Um módulo é basicamente um arquivo JavaScript. Cada arquivo que você cria num projeto Node.js é considerado um módulo. E a grande vantagem disso é que você pode separar seu código em partes menores, cada uma responsável por uma coisa específica, e importar essas partes onde precisar.

Pensa num projeto grande como uma casa. Você não constrói a casa toda num único cômodo gigante. Você divide em quartos, sala, cozinha, banheiro, cada espaço com sua função. Os módulos são exatamente isso pra um projeto de software.

Os três tipos de módulos no Node.js

No Node.js existem três categorias de módulos que você vai trabalhar:

A primeira são os módulos nativos, que já vêm instalados com o Node.js e você não precisa instalar nada extra pra usá-los. Já vimos dois deles nos exemplos anteriores, o http e o fs. Existem vários outros como o path, que ajuda a trabalhar com caminhos de arquivos, o os, que dá acesso a informações do sistema operacional, e o crypto, que oferece funcionalidades de criptografia.

A segunda categoria são os módulos externos, que são os pacotes que você instala via NPM, como o chalk que a gente instalou no tópico anterior.

A terceira são os módulos locais, que são os arquivos que você mesmo cria dentro do seu projeto, como o calculadora.js que fizemos anteriormente.

CommonJS: o sistema tradicional com require

O sistema de módulos original do Node.js é chamado de CommonJS, e ele usa o require pra importar e o module.exports pra exportar. Você já viu esse sistema em ação várias vezes nos exemplos anteriores.

Vamos ver como ele funciona com mais detalhes. Cria um arquivo chamado usuario.js:

const nome = 'Maria';
const idade = 25;

function apresentar() {
  return 'Olá, meu nome é ' + nome + ' e tenho ' + idade + ' anos.';
}

function calcularAnoNascimento() {
  return 2024 - idade;
}

module.exports = {
  nome,
  idade,
  apresentar,
  calcularAnoNascimento
};

E agora num arquivo index.js, importa esse módulo:

const usuario = require('./usuario');

console.log(usuario.nome);
console.log(usuario.apresentar());
console.log('Ano de nascimento:', usuario.calcularAnoNascimento());

Roda com node index.js e vai ver:

Maria
Olá, meu nome é Maria e tenho 25 anos.
Ano de nascimento: 1999

Perceba o ./ antes do nome do arquivo. Isso indica que o módulo está na mesma pasta que o arquivo atual. Se estivesse numa pasta diferente, você ajustaria o caminho, como ./pasta/usuario por exemplo. Quando não tem o ./, o Node.js entende que é um módulo nativo ou um pacote instalado via NPM.

Você também pode exportar coisas individualmente, sem precisar agrupar tudo num objeto. Atualiza o usuario.js assim:

module.exports.nome = 'Maria';
module.exports.idade = 25;
module.exports.apresentar = function() {
  return 'Olá, meu nome é Maria!';
};

O resultado é o mesmo, é só uma forma diferente de escrever a exportação.

ES Modules: o sistema moderno com import e export

Se você já estudou JavaScript moderno, provavelmente conhece a sintaxe de import e export. Essa é a forma padrão do JavaScript de lidar com módulos, chamada de ES Modules ou ESM.

O Node.js suporta ES Modules, mas com uma diferença importante: você precisa ou usar a extensão .mjs nos arquivos ou adicionar “type”: “module” no seu package.json, como a gente fez no exemplo do chalk.

Com ES Modules, o mesmo exemplo do usuario.js ficaria assim:

export const nome = 'Maria';
export const idade = 25;

export function apresentar() {
  return 'Olá, meu nome é ' + nome + ' e tenho ' + idade + ' anos.';
}

export function calcularAnoNascimento() {
  return 2024 - idade;
}

E a importação no index.js:

import { nome, idade, apresentar, calcularAnoNascimento } from './usuario.js';

console.log(nome);
console.log(apresentar());
console.log('Ano de nascimento:', calcularAnoNascimento());

Perceba que com ES Modules você precisa incluir a extensão .js no caminho do arquivo, diferente do CommonJS que não precisa.

Você também pode usar o export default pra exportar uma coisa principal do módulo:

export default function apresentar() {
  return 'Olá, meu nome é Maria!';
}

E importar sem as chaves:

import apresentar from './usuario.js';
console.log(apresentar());

CommonJS ou ES Modules: qual usar?

Essa é uma dúvida comum e a resposta é: depende do projeto. Se você está trabalhando num projeto mais antigo ou usando pacotes que ainda não suportam ES Modules, o CommonJS com require é a escolha mais segura. Se você está começando um projeto do zero e quer usar a sintaxe mais moderna, ES Modules é o caminho.

Na prática, você vai encontrar os dois no mercado de trabalho, então vale a pena se familiarizar com ambos. O importante é ser consistente dentro de um mesmo projeto e não misturar os dois sistemas, porque isso pode causar erros difíceis de debugar.

Explorando módulos nativos úteis

Antes de fechar esse tópico, vale dar uma olhada rápida em alguns módulos nativos do Node.js que você vai usar bastante no dia a dia.

O módulo path é muito útil pra trabalhar com caminhos de arquivos de forma segura entre diferentes sistemas operacionais:

const path = require('path');

const caminho = path.join(__dirname, 'pasta', 'arquivo.txt');
console.log(caminho);

console.log(path.extname('arquivo.txt'));
console.log(path.basename('/pasta/subpasta/arquivo.txt'));

O módulo os dá informações sobre o sistema operacional:

const os = require('os');

console.log('Sistema operacional:', os.platform());
console.log('Memória total:', os.totalmem());
console.log('Memória livre:', os.freemem());

Esses módulos nativos são ferramentas que você vai recorrer bastante conforme seus projetos forem crescendo e ficando mais complexos.

Entender o sistema de módulos é fundamental pra organizar bem seus projetos e escrever código limpo e reutilizável.

Boas práticas e erros comuns de quem está começando com Node.js

Chegamos num dos tópicos mais valiosos do artigo. Você já sabe o que é Node.js, como ele funciona, como instalar, como escrever código, como usar o NPM e como conectar com bancos de dados. Agora vamos falar sobre o que separa um código que simplesmente funciona de um código bem escrito.

Essas dicas vêm de erros que praticamente todo desenvolvedor comete no começo, e conhecê-las agora vai te poupar muita dor de cabeça no futuro.

Erro 1: ignorar o tratamento de erros

Esse é provavelmente o erro mais comum entre iniciantes. Escrever código que funciona quando tudo dá certo, mas quebra feio quando algo dá errado.

Olha esse exemplo de código sem tratamento de erros:

const fs = require('fs');

fs.readFile('arquivo.txt', 'utf8', (erro, conteudo) => {
  console.log(conteudo);
});

Se o arquivo não existir, o programa vai mostrar undefined no terminal ou até travar. Agora olha o mesmo código com tratamento de erros adequado:

const fs = require('fs');

fs.readFile('arquivo.txt', 'utf8', (erro, conteudo) => {
  if (erro) {
    console.error('Erro ao ler o arquivo:', erro.message);
    return;
  }
  console.log(conteudo);
});

Simples assim. Sempre verifique se existe um erro antes de usar o resultado de uma operação assíncrona. Isso vale pra leitura de arquivos, consultas ao banco de dados, requisições a APIs externas e qualquer outra operação que pode falhar.

Com async await, o tratamento de erros fica assim:

async function lerArquivo() {
  try {
    const conteudo = await fs.promises.readFile('arquivo.txt', 'utf8');
    console.log(conteudo);
  } catch (erro) {
    console.error('Erro ao ler o arquivo:', erro.message);
  }
}

lerArquivo();

O bloco try catch é seu melhor amigo quando trabalha com código assíncrono. Acostume a usá-lo desde o começo.

Erro 2: usar variáveis globais desnecessariamente

Outro erro muito comum é sair declarando variáveis globais por todo o código. Isso pode causar comportamentos inesperados, especialmente em aplicações maiores onde diferentes partes do código podem acabar modificando a mesma variável sem querer.

Código com variável global desnecessária:

let resultado;

function calcular(a, b) {
  resultado = a + b;
}

calcular(5, 3);
console.log(resultado);

Forma mais correta:

function calcular(a, b) {
  return a + b;
}

const resultado = calcular(5, 3);
console.log(resultado);

A regra geral é manter o escopo das variáveis o mais restrito possível. Declare variáveis dentro das funções que as usam, não fora delas.

Erro 3: não usar variáveis de ambiente

Muita gente no começo coloca informações sensíveis diretamente no código, como senhas de banco de dados, chaves de API e outros dados que não deveriam aparecer no código fonte. Isso é um problema sério de segurança, especialmente se você publicar esse código no GitHub.

Código errado:

const conexao = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'minha-senha-secreta-123',
  database: 'meubanco'
});

A forma correta é usar variáveis de ambiente. Instala o pacote dotenv:

npm install dotenv

Cria um arquivo chamado .env na raiz do projeto:

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=minha-senha-secreta-123
DB_NAME=meubanco

E adiciona o .env no seu .gitignore pra garantir que ele nunca seja enviado pro GitHub:

node_modules
.env

Agora no seu código:

require('dotenv').config();

const conexao = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
});

Dessa forma as informações sensíveis ficam fora do código e você pode compartilhar seu projeto sem expor dados importantes.

Erro 4: callback hell

Quando você começa a trabalhar com muitas operações assíncronas aninhadas usando callbacks, o código pode virar uma bagunça difícil de ler e manter. Isso tem até um nome carinhoso na comunidade: callback hell.

Olha um exemplo de callback hell:

fs.readFile('arquivo1.txt', 'utf8', (erro1, conteudo1) => {
  if (erro1) {
    console.error(erro1);
    return;
  }
  fs.readFile('arquivo2.txt', 'utf8', (erro2, conteudo2) => {
    if (erro2) {
      console.error(erro2);
      return;
    }
    fs.readFile('arquivo3.txt', 'utf8', (erro3, conteudo3) => {
      if (erro3) {
        console.error(erro3);
        return;
      }
      console.log(conteudo1, conteudo2, conteudo3);
    });
  });
});

Esse código funciona, mas é horrível de ler e manter. Com async await o mesmo código fica assim:

async function lerArquivos() {
  try {
    const conteudo1 = await fs.promises.readFile('arquivo1.txt', 'utf8');
    const conteudo2 = await fs.promises.readFile('arquivo2.txt', 'utf8');
    const conteudo3 = await fs.promises.readFile('arquivo3.txt', 'utf8');
    console.log(conteudo1, conteudo2, conteudo3);
  } catch (erro) {
    console.error('Erro ao ler arquivos:', erro.message);
  }
}

lerArquivos();

Muito mais limpo e fácil de entender. Sempre que possível, prefira async await em vez de callbacks aninhados.

Boa prática: organizar o projeto em pastas

Conforme seu projeto cresce, jogar todos os arquivos na pasta raiz vira um caos rapidamente. Uma estrutura de pastas bem organizada faz toda a diferença na manutenção do projeto.

Uma estrutura comum em projetos Node.js é essa:

meu-projeto/
src/
controllers/
models/
routes/
middlewares/
services/
.env
.gitignore
package.json
index.js

Cada pasta tem uma responsabilidade clara. Controllers lidam com a lógica das requisições, models representam os dados, routes definem as rotas da aplicação, middlewares são funções intermediárias e services contêm a lógica de negócio.

Você não precisa seguir essa estrutura à risca, mas ter alguma organização desde o começo é sempre melhor do que deixar pra organizar depois quando o projeto já está grande.

Boa prática: usar o nodemon durante o desenvolvimento

Já mencionamos o nodemon no tópico do NPM, mas vale reforçar aqui. Ficar parando e reiniciando o servidor manualmente toda vez que você faz uma alteração no código é uma perda de tempo enorme.

Com o nodemon instalado como dependência de desenvolvimento, você configura um script no package.json:

{
  "scripts": {
    "dev": "nodemon index.js"
  }
}

E roda com:

npm run dev

A partir daí, toda vez que você salvar um arquivo, o nodemon reinicia o servidor automaticamente. Simples e muito produtivo.

Boa prática: sempre versionar seu código

Isso não é específico do Node.js, mas é algo que todo desenvolvedor precisa fazer desde o primeiro dia: usar Git pra versionar seu código. Git é um sistema de controle de versão que permite você acompanhar todas as mudanças feitas no projeto ao longo do tempo.

Se você ainda não conhece Git, vale a pena dedicar um tempo aprendendo o básico. Com ele você consegue voltar atrás se algo der errado, colaborar com outros desenvolvedores e publicar seus projetos no GitHub pra mostrar pro mundo o que você está construindo.

Boa prática: ler a documentação oficial

Por último, e talvez mais importante: acostume a consultar a documentação oficial do Node.js em nodejs.org/docs. A documentação é completa, bem organizada e vai te dar respostas muito mais precisas do que qualquer outro lugar quando você tiver dúvidas sobre módulos nativos e funcionalidades específicas.

No começo pode parecer intimidador ler documentação técnica, mas com o tempo você vai se acostumar e vai perceber que é a fonte mais confiável de informação que existe.

Com essas boas práticas no seu radar desde o início, você já está alguns passos à frente da maioria dos iniciantes.

Conclusão

Ufa, chegamos ao final! Foi uma jornada e tanto, mas espero que você esteja saindo daqui com uma visão muito mais clara sobre o que é Node.js e o que ele pode fazer por você como desenvolvedor.

Vamos recapitular rapidamente tudo que a gente viu ao longo desse artigo.

Começamos entendendo o que é Node.js de verdade, deixando claro que ele não é uma linguagem nem um framework, mas sim um ambiente de execução que permite rodar JavaScript fora do navegador. Depois mergulhamos no funcionamento interno dele, entendendo conceitos fundamentais como o modelo non-blocking e o event loop, que são o coração da eficiência do Node.js.

Falamos sobre por que vale a pena aprender Node.js hoje em dia, olhando pro mercado de trabalho, pras empresas que usam a tecnologia e pras vantagens de usar JavaScript tanto no front-end quanto no back-end. Depois partimos pra prática, instalando o Node.js na máquina, escrevendo os primeiros códigos, criando servidores web e lendo arquivos do sistema.

Exploramos o NPM e seu ecossistema gigante de pacotes, entendemos como o sistema de módulos funciona com CommonJS e ES Modules, vimos como conectar o Node.js a bancos de dados relacionais e não relacionais, e fechamos com boas práticas e erros comuns que todo iniciante precisa conhecer.

São muitos conceitos, e é completamente normal não absorver tudo de uma vez. Programação é uma habilidade que se desenvolve com prática constante e paciência. O mais importante agora é não parar aqui.

Quais são os próximos passos?

Se você quer continuar evoluindo com Node.js, aqui estão algumas sugestões concretas de por onde seguir.

O primeiro passo natural é aprender Express.js, que é o framework mais popular do ecossistema Node.js. Ele simplifica muito a criação de servidores e APIs, e é usado em praticamente todo projeto Node.js que você vai encontrar no mercado. Com o que você aprendeu aqui, vai ser muito mais fácil entender o que o Express faz por baixo dos panos.

Depois do Express, vale a pena se aprofundar em bancos de dados, escolhendo um e estudando ele com mais profundidade. Se optar pelo caminho relacional, o PostgreSQL é uma excelente escolha pra projetos sérios. Se preferir o NoSQL, o MongoDB com Mongoose vai te dar uma base sólida.

Outra coisa que vai agregar muito é aprender sobre autenticação e segurança em APIs, como JWT, que significa JSON Web Token, e como proteger suas rotas e dados dos usuários. Isso é algo que toda aplicação real precisa e que o mercado valoriza muito.

E claro, continue acompanhando o CulturaDev. A gente tem muito mais conteúdo sobre Node.js, JavaScript, desenvolvimento web e tecnologia em geral pra compartilhar com você. Nosso objetivo é sempre trazer conteúdo acessível e prático pra quem está começando e pra quem já tem experiência e quer se aprofundar ainda mais.

Uma última coisa

Aprender programação pode ser frustrante às vezes. Você vai travar em problemas que parecem impossíveis de resolver, vai ver mensagens de erro que não fazem sentido nenhum e vai ter dias em que vai questionar se está no caminho certo.

Isso é normal. Todo desenvolvedor passa por isso, independente do nível de experiência. O segredo é continuar, buscar ajuda quando precisar e celebrar cada pequena vitória no caminho. Aquele Hello World que você rodou mais cedo? Foi uma vitória. Aquele servidor que respondeu no navegador? Foi uma vitória também.

O caminho é longo, mas cada linha de código que você escreve te deixa um pouco melhor do que você era antes.

E agora eu quero ouvir de você. Esse artigo foi útil pra você? Ficou com alguma dúvida sobre algum dos tópicos que a gente abordou? Já conhecia o Node.js ou foi seu primeiro contato com a tecnologia hoje? Tem algum assunto relacionado que você gostaria que a gente cobrisse aqui no CulturaDev?

Deixa seu comentário aqui embaixo, a gente lê todos e responde sempre que possível. Sua dúvida pode ser exatamente a mesma que outros leitores têm mas não tiveram coragem de perguntar, então não tenha vergonha de comentar. A comunidade do CulturaDev é feita de pessoas que estão aprendendo juntas, assim como você.

Até o próximo artigo!