Como criar um servidor com Node.js sem o apoio de frameworks

Como criar um servidor com Node.js sem o apoio de frameworks
Gian Felipe Theodorowicz
Gian Felipe Theodorowicz

Compartilhe

Para criar um servidor com Node.js, o que normalmente pensamos em fazer é, em primeiro lugar, instalar o Express. Mas espere, será que realmente precisamos do Express para fazer um servidor HTTP em Node.js?

Esse é o tópico do artigo de hoje: por incrível que pareça o Node.js não precisa de um framework para construir um servidor. Ele consegue fazer isso usando módulos próprios para criar um servidor HTTP.

E agora você deve estar se perguntando: mas se há frameworks para criação e otimização de servidores, por que vamos utilizar os módulos do Node.js?

Algo essencial para toda pessoa desenvolvedora é compreender as bases que moldam a tecnologia. Dessa forma, entender como os módulos e o próprio ecossistema Node.js funcionam vai te ajudar a otimizar a utilização de qualquer framework e não se restringir a apenas uma ferramenta.

Muito bacana, não é? Vamos lá?

O que é um servidor?

Foto de servidores em funcionamento.

Antes de criarmos um servidor, precisamos lembrar que um servidor web é um computador especializado que armazena e fornece informações ou arquivos para outros dispositivos, como computadores, tablets, smartphones, entre outros, tudo através da internet.

Quando você digita um endereço de um site em seu navegador, ele envia uma solicitação para o servidor web correspondente. O servidor então recebe essa solicitação, localiza os arquivos necessários e envia-os para o seu navegador, que então exibe o site em seu dispositivo.

Essas requisições e respostas são mediadas através do protocolo HTTP, que é um conjunto de regras para as comunicações entre computadores de clientes e servidores. Além disso, os servidores web também podem executar outras funções, como processar formulários enviados pelos usuários, gerar conteúdo dinâmico e armazenar informações de usuários, como perfis e preferências.

Até aqui tudo bem… Mas já que um servidor é um computador que disponibiliza arquivos para o cliente, como consigo “servir” esses dados via código? Vamos entender a seguir!

Imersão dev Back-end: mergulhe em programação hoje, com a Alura e o Google Gemini. Domine o desenvolvimento back-end e crie o seu primeiro projeto com Node.js na prática. O evento é 100% gratuito e com certificado de participação. O período de inscrição vai de 18 de novembro de 2024 a 22 de novembro de 2024. Inscreva-se já!

Como criar um servidor?

Primeiro, vamos criar um servidor com o framework Express. Começamos por instanciar o Express usando:

import express from 'express' 
const app = express() 

Ou então podemos usar o Fastify e fazendo:

import fastify from 'fastify' 
const app = fastify() 

Eu digo que não podemos nos limitar a apenas usar frameworks, pois eles têm muitas funcionalidades. Dependendo do caso, utilizá-los é como usar um lança-chamas para acender uma vela, ou seja, não precisamos de uma ferramenta tão potente e cheia de recursos para um trabalho que não exige tantos.

Vamos pensar no seguinte: em uma tradução livre, framework significaria “estrutura”, o que significa que elas são construídas, ou escritas, a partir de uma base.

Mas afinal, qual seria essa base?

Express e Fastify são frameworks que usam JavaScript e bibliotecas nativas do Node.js, fornecendo uma abstração em funcionalidades do JavaScript, tornando mais fácil e rápido o processo de desenvolvimento de uma API.

No entanto, é importante lembrar que tais frameworks não são a única maneira de se criar uma API e que é possível fazer o mesmo diretamente utilizando as bibliotecas nativas:

  1. Instale o Node.js no seu computador.
  2. Com o editor de sua preferência, crie um arquivo chamado server.js.
  3. Crie um projeto Node.js com o comando npm init -y.
  4. No package.json do seu projeto, adicione o campo chave e valor ”type”: “module” para utilizar o ESModules.
  5. No arquivo server.js, chame o módulo node:http para extrair a função nativa CreateServer com o código a seguir:
import { createServer } from 'node:http' 
  1. Instancie a função createServer() dentro de uma constante. É comum a instância de um servidor ser chamada de app, referindo-se à aplicação:
const app = createServer() 

Mas o que esse código significa?

O ecossistema Node.js trabalha com módulos, e em seu ambiente há o módulo http ou o node:http com uma função muito interessante chamada createServer. Essa função fala para o Node.js: “Crie um servidor!”.

Dessa forma, na teoria, já temos um servidor recebendo http. Porém ainda precisamos lidar com as requisições e respostas, e vamos fazer isso com o código a seguir:

import { createServer } from 'node:http' 

async function HttpHandler(request, response){} 

const app = createServer(HttpHandler) 

No código, a função createServer recebe por padrão uma propriedade 'requestHandler', que lida com as requisições. Então, criamos uma função e passamos o HttpHandler como parâmetro.

Agora ficou com uma cara melhor, certo? Temos um servidor HTTP recebendo requisições! Ok, estamos capturando essas requisições… Mas e se eu te disser que, na realidade, ele não está funcionando ainda?

A função createServer() precisa que ser acompanhada de um `.listen(port) para poder funcionar e ouvir requisições HTTP vindas de uma porta específica da sua máquina. Confira:

import { createServer } from 'node:http' 

async function HttpHandler(request, response){} 

const app = createServer(HttpHandler) 
.listen(3333) 
.on('listening', () => console.log('http server ready')) 

Repare que adicionamos mais um item além do .listen(). Para entendermos o que é exatamente esse .on() precisamos entender primeiro o que são eventos em Node.js, como são gerados e o que eles retornam.

Em resumo, eventos são notificações geradas por uma função que podem ser capturadas por quem está chamando-as. Por sua vez, podem ou não possuir dados e funcionam de forma diferente de retornos, pois não encerram a função.

Agora o que fazemos é capturar o evento cujo método seja listening, que é para informar que está “escutando”. Em seguida, passamos uma ação que ocorrerá após a chegada desta notificação para nossa função, que, nesse caso, é um console.log('http server ready').

Em resumo, a linha .on('listening', () => console.log('http server ready')) define um ouvinte para o evento 'listening'. Quando o servidor estiver ouvindo na porta especificada, a função de retorno de chamada será executada e imprimirá a mensagem 'http server ready'.

Bacana, né? Agora temos nosso próprio servidor recebendo e respondendo com o protocolo HTTP!

Adicionando um retorno para API

Você rodou o código aí na sua máquina e descobriu que o código não está respondendo nada? Por que isso acontece? Porque precisamos enviar uma resposta. Então, vamos adicionar um retorno!

import { createServer } from 'node:http' 

async function HttpHandler(request, response){ 
try { 
const data = request 
response.writeHead(200) 
} catch (e) { 
response.writeHead(500) 
} 
} 

const app = createServer(HttpHandler) 
.listen(3333) 
.on('listening', () => console.log('http server ready')) 

Repare que agora estamos interagindo de forma mais completa com a requisição HTTP. Na verdade, estamos até retornando diferentes códigos de status para diferentes situações que podem ocorrer no meu código.

No entanto, temos um problema quando usamos const data = request devido ao request ser resultante de um evento. Dentro do createServer, que é um event emmiter, nem todos os eventos são requisições. Para resolver isso, podemos usar outra solução que também vem direto do próprio Node.js:

import { createServer } from 'node:http' 

import { once } from 'node:events' 

async function HttpHandler(request, response){ 
try {  

const data = await once(request, 'data') 
response.writeHead(200) 
} catch (e) { 
response.writeHead(500) 
} 
} 

const app = createServer(HttpHandler) 
.listen(3333) 
.on('listening', () => console.log('http server ready')) 

Dentro do try nós utilizamos a função once() para aguardar o evento 'data' ser emitido no objeto request e armazenamos na const data. Isso significa que o código vai esperar até que os dados da solicitação estejam disponíveis para serem processados. Dessa forma, agora estamos capturando apenas quando data estiver presente dentro da requisição!

Se houver algo, a response.writeHead(200) vai escrever o cabeçalho da resposta HTTP com o código de status 200 (OK), indicando que a solicitação foi processada com sucesso.

Por outro lado, se a requisição entrar no bloco de catch (e) significa que ocorreu algum erro dentro do bloco try. Então, o código vai para o bloco catch e a variável e vai conter a informação sobre o erro. Em seguida, a response.writeHead(500) vai escrever o cabeçalho da resposta HTTP com o código de status 500 (Internal Server Error). Isso indica que ocorreu um erro durante o processamento da solicitação.

E então temos nosso servidor com módulos nativos do ecossistema Node.js! Conseguimos entender mais sobre a ferramenta, pois agora temos os fundamentos para utilizarmos os frameworks da melhor forma possível!

Conclusão

Viu como é interessante fazer um servidor HTTP no Node.js sem a utilização de quaisquer frameworks?

Percebi que existem devs que não conhecem o módulo HTTP do Node.js ou que até mesmo nem sabiam que era possível usar outra solução para construir um servidor HTTP sem o framework Express. Então, decidi escrever este artigo para mostrar que é possível criar API sem usar quaisquer frameworks dentro do Node.js.

Cabe lembrar que não existe uma única fórmula para todas as situações. No contexto de desenvolvimento, precisamos entender quais problemas desejamos solucionar para buscarmos as melhores soluções.

Esse caso da criação de um servidor sem frameworks mais robustos pode ser aplicado tanto para estudo quanto para rotinas internas ou servidores simples, por exemplo, um servidor de arquivos ou um servidor web estático. Porém, gostaria de ressaltar que eu particularmente não construo minhas APIs sem utilizar frameworks HTTP, justamente pela comodidade que eles nos oferecem, pois já vem com diversas funcionalidades para tratar requisições, construir APIs e até mesmo padrões de projeto.

Sendo assim, estudo e prática são fundamentais para analisar cada cenário e também para escolher a melhor ferramenta que será utilizada.

Portanto, continue a aprofundar seus conhecimentos e bons estudos!

Referências

Veja outros artigos sobre Programação