Como criar um aplicativo da web Node.js usando nenhum pacote externo

Sem frameworks, sem NPM, sem Package.json, sem frescuras

Abhinav Pandey 27 dez

Neste post, mergulharemos profundamente nos fundamentos do Node.js criando um aplicativo da Web Node.js sem nenhum pacote externo. Nós cobriremos os principais conceitos como streams, eventos, exceções, HTTP etc.

Um olhar dentro dos fundamentos do Node.js

Atualmente, sempre que dissermos que vamos implementar um serviço no Node.js, na maioria das vezes, usaremos o Express ou outras bibliotecas de terceiros para implementar nossa funcionalidade. E eu não vou dizer que há algum mal em fazer isso. Essas bibliotecas fornecem abstração necessária sobre conceitos redundantes que nos tornam eficientes.

Mas com maior abstração, a lógica de baixo nível do seu programa está escondida de você. Como resultado, não podemos desenvolver uma imagem clara de como nossa lógica de negócios interage com o Node.js.

Mas como Ryan Dahl, o criador do Node.js disse:

Você nunca pode entender tudo. Mas você deve se esforçar para entender o sistema.

Vamos nos esforçar para formar esta imagem clara em sua totalidade.

Então, vamos construir um aplicativo HTTP Node.js sem Framework, NPM e Package.json.

Nós vamos construir um aplicativo que irá:

  1. Importar módulos necessários
  2. Criar uma instância do servidor
  3. Anexar listeners ao evento de request do objeto do servidor
  4. Analisar corpo e cabeçalhos da solicitação
  5. Envio de resposta para o cliente.
  6. Manipule eventos de erro em fluxos de solicitação e resposta.

Aqui está a pegadinha 😉

Vamos fazer tudo do zero com apenas

  1. um terminal e
  2. um editor.

Sim!! Nós usaremos o framework de ninguém mais, as bibliotecas de mais ninguém apenas o JavaScript bruto e o tempo de execução do Node.js.

Vamos começar!

Antes de criar um servidor HTTP, vamos esclarecer o conceito necessário de um módulo HTTP no Node.js.

O que é HTTP?

http no Node.js é um módulo embutido que permite a comunicação cliente-servidor via protocolo HTTP. Este módulo fornece uma interface para criar um cliente HTTP ou um servidor HTTP que pode se comunicar com outros servidores ou clientes HTTP.

E para tornar este espaço de comunicação eficiente, um módulo http fornece streaming de dados usando interface de fluxo. E como o fluxo transmite dados em partes, isso significa que o Node.js nunca armazena toda a solicitação ou resposta de uma só vez na memória. Nós vamos chegar aos fluxos em breve.

Portanto, para nosso aplicativo, usaremos essa interface http para criar um servidor HTTP que escutará uma porta específica e fornecerá dados ao usuário.

Importando o módulo HTTP

Para usar o http servidor ou cliente que você deve exigir http módulo.

 var http = require (“http”); 

Agora vamos ver como a linha acima realmente funciona:

Para carregar uma instância de um determinado módulo em nosso tempo de execução, o Node.js nos fornece uma variável require que é globalmente acessível. Usamos essa variável require definida globalmente e informamos ao Node para carregar o módulo http (passando 'http' como o único parâmetro para a chamada de função require ).

Há uma lista de outros objetos Node.js disponíveis globalmente que você pode verificar no nó REPL (pressionando <tab> duas vezes).

Node.js e variáveis JavaScript definidas globalmente

Mas os dois mais importantes para nosso uso são:

  1. O módulo requer
  2. O módulo (explicação detalhada no próximo artigo)

(Não precisamos require('require') ou require ('module') como eles são globais).

Como require trabalho?

No tempo de execução, quando o Node.js invoca uma chamada require (require ('./ path / to / fileName'), ele procura um arquivo com um nome igual ao fornecido no único parâmetro para a chamada de função require.

E quando o nome do arquivo coincide, o Node.js verifica 3 tipos de extensões:

  1. .js – Node.js procura por “fileName.js” no caminho especificado para carregar como script js.
  2. .json – Se o Node.js encontrar o arquivo “filename.json” no caminho especificado, ele carregará um arquivo com o nome correspondente ao valor da chave 'main' no arquivo JSON.
  3. .node – Node.js carrega addons binários com o nome fileName.node no caminho especificado.

A extensão é carregada na ordem de .js> .json> .node.

Criar uma instância do servidor

Agora que incluímos o módulo http , precisamos criar um objeto de servidor da Web HTTP. Isso pode ser feito usando o método createServer no módulo http .

Para o método createServer , passamos uma função de retorno de chamada que é chamada toda vez que uma solicitação é recebida no servidor.

Esse método createServer retorna um objeto do servidor que armazenamos no app variável. Este objeto de servidor é um emissor de evento.

Ok, espere, o que é um event emitter ?

Vamos dar uma olhada nos objetos de event e emitter nomeados.

Muitas das principais APIs do Node.js são construídas em torno de uma arquitetura orientada a eventos. Determinados tipos de objetos (denominados “emissores”) podem fazer com que alguma função (“ouvintes”) seja chamada, emitindo quaisquer eventos “nomeados”.

Vamos ver um exemplo para pegar o jeito dele.

Saída: Called namedEvent in myEventObject's attached listner

Explicação

No exemplo acima, vimos que o namedEvent tem um ouvinte (uma função) anexado a ele. Por anexo, queremos dizer que o ouvinte é chamado depois de ouvir o evento nomeado. Assim, o ouvinte imprime a saída na tela do console quando o objeto emissores emite namedEvent .

Além de anexar os ouvintes, o objeto eventEmitter fornece muitas outras propriedades e funções, como

  • você pode obter a contagem do número total de listeners anexados a um evento nomeado, ou
  • Você também pode remover um Listener anexado aos eventos.

Você pode consultar os documentos oficiais do Node.js para obter informações mais detalhadas sobre eventos no Node.js.

Voltando ao nosso exemplo …

Nosso objeto de servidor da Web também é como todos os outros objetos de emissor que implementam interfaces de emissor de evento. Ele também emite diferentes tipos de eventos nomeados.

Alguns deles são os seguintes:

  1. connect – gerado para todo o pedido de 'conexão' pelo cliente HTTP.
  2. connection – Emitido quando um novo fluxo TCP é estabelecido. Fornece acesso ao soquete estabelecido.
  3. request – Emitido para cada pedido do cliente (Nós ouviríamos aqui).
  4. upgrade – emitido toda vez que um cliente solicita uma atualização do protocolo (pode ser versão HTTP).

Você pode obter a lista completa dos eventos emitidos pelo nosso servidor web a partir dos documentos oficiais do Node.js.

Ouvindo o evento de solicitação

Agora, como nosso servidor precisa ouvir a solicitação recebida, ouviremos o evento de request do nosso servidor HTTP.

Amostra de código:

Na terceira linha, uma função de ouvinte é anexada para ouvir todos os eventos de request em nosso objeto de servidor.

O evento request fornece a função listener com 2 parâmetros, que são:

  1. request – uma instância do objeto http.incomingMessage e
  2. resposta – uma instância do objeto http.ServerResponse.

Estes request e response objetos têm propriedades e métodos que eles herdam a http.incomingMessage e http.ServerResponse aulas, respectivamente.

Analisar corpo e cabeçalhos da solicitação

Agora que temos acesso ao objeto request e response

As primeiras coisas que você pode querer saber sobre as solicitações recebidas são o URL, o método e os cabeçalhos . O Node.js torna muito fácil anexá-los como propriedades ao objeto de request (passado como o primeiro parâmetro para o ouvinte do evento request ).

Você pode desestruturar o objeto de solicitação para obtê-los assim:

const {headers, url, method } = request;

headers passados em request estão presentes como um objeto independente dentro do objeto request (secret: eles estão todos em letras minúsculas).

Depois de analisar o método http , no caso de uma solicitação PUT ou POST, estamos interessados em examinar os data enviados no corpo da solicitação.

Mas, para retirar os dados do corpo da solicitação, precisamos conhecer alguns pontos-chave sobre o objeto de solicitação.

Request Object – um fluxo legível

O objeto de request que é passado para o manipulador também implementa a interface de fluxo legível. Isso significa que nosso objeto de request é um fluxo que pode ser ouvido ou canalizado em outro lugar para capturar os dados que estão fluindo para ele. Também coletaremos os dados do fluxo de request ouvindo os data e eventos end do fluxo.

Diferentes tipos de dados podem ser passados para o nosso servidor, mas para simplificar estaremos passando apenas a string no corpo.

Para usar esses dados, precisamos analisá- los, portanto, usaremos os data e o evento end do fluxo legível, que é implementado pelo nosso objeto de request conforme mencionado anteriormente.

Em cada evento de data , o fluxo legível passa dados como um bloco de buffer. Nós estaremos anexando todos os pedaços em uma matriz vazia. E no evento end , vamos concatenar e restringir o array para obter o corpo cumulativo.

Então aqui está o código até agora:

Enviando a resposta para o cliente.

Após coletar dados da solicitação HTTP, precisamos fornecer uma resposta apropriada ao cliente. Mas como o objeto request implementa apenas um fluxo legível, precisamos de um fluxo gravável onde possamos escrever nossa resposta.

Objeto de resposta – um fluxo gravável

Para fazer isso, o Node.js nos fornece um segundo parâmetro que é o objeto de response para o ouvinte de evento de request .

Usando o objeto de response , podemos definir o código de status HTTP, definir cabeçalhos e gravar conteúdo no fluxo de gravação do objeto de resposta.

Embora se você não definir o código de resposta explicitamente, o próprio Node.js o definirá como 200. Mas conforme a complexidade aumenta, você desejará definir o statusCode desejado da resposta HTTP.

Configuração de cabeçalhos implícita

Você pode definir, obter e remover cabeçalhos para a resposta usando a setHeader(name, value) , getHeader(name) e removeHeader(name) .

Amostra de código:

Ao usar o método setHeader() acima para definir cabeçalhos, dependemos do Node.js para definir implicitamente os cabeçalhos de resposta antes de enviar o corpo da resposta.

Para definir cabeçalhos e código de status explicitamente, temos um método response.writeHead() .

Amostra de código:

Ao definir explicitamente os cabeçalhos, devemos ter em mente que os cabeçalhos vêm antes do corpo na resposta HTTP . Ou seja, devemos preferir usar o método writeHead() antes de escrever qualquer coisa no corpo da resposta.

Agora vamos ver como podemos gravar dados em uma resposta.

Como o objeto de resposta é um objeto de fluxo gravável, precisamos apenas usar métodos de fluxo de gravação para gravar fragmentos de dados no objeto de resposta HTTP.

Amostra de código:

Depois de gravar no fluxo de resposta, precisamos fechar o fluxo para que o Node.js fique sabendo que é hora de enviar a resposta de volta ao cliente.

.end() método .end() nos permite fechar a conexão HTTP que foi configurada no momento em que a requisição chegou ao nosso servidor. O método end() também aceita uma última string a ser escrita antes de fechar a conexão.

Se não usarmos o método end, o Node.js gravará dados no fluxo de gravação e aguardará…

… Até que o tempo limite padrão no objeto do servidor expire. Ou seja, para qualquer solicitação, o Node.js só aguarda um tempo fixo (que é especificado no objeto do servidor) antes de fechar a conexão . E uma vez que a conexão é fechada (usando manualmente end() ou o tempo limite expira), o Node libera todos os recursos alocados imediatamente .

Você pode definir ou alterar o tempo limite usando server.setTimeout([msecs][, callback]) .

Para desativar o tempo limite, você pode definir o valor de tempo limite como 0. Mas como o tempo limite é atribuído no momento de formar uma nova conexão, o tempo limite só será atualizado para as próximas novas conexões .

Agora que escrevemos nossa resposta, nosso servidor deve funcionar bem.

Mas, o que acontecerá quando nosso servidor encontrar uma exceção?

Precisamos ouvir os eventos de error dos fluxos de request e response . Um evento de error é gerado toda vez que ocorre uma exceção. Você pode tentar evitá-lo, mas eles vêm e temos que pegá-los e manuseá-los adequadamente.

Mas como?

Nós os manipularemos , anexando manipuladores de error eventos de error dos fluxos de request e response .

Explicação

Aqui estamos pegando todos os eventos de error dos fluxos de request e response e apenas os registrando no console. Você também pode usar util invés do console no ambiente de produção (embora na produção seja aconselhável inspecionar os erros apropriadamente).

Agora vamos dar uma olhada no exemplo de código que temos até agora.

Ok, então nosso servidor é capaz das seguintes coisas neste momento:

  1. Importar módulos necessários
  2. criar uma instância do servidor
  3. Anexar listeners ao evento de request do objeto do servidor
  4. Analisar corpo e cabeçalhos da solicitação
  5. Escreva a resposta para o fluxo de resposta
  6. Manipule eventos de erro em fluxos de solicitação e resposta.

Até agora, tornamos nosso objeto de servidor capaz de levar em consideração novas conexões, mas não informamos onde procurar novas conexões. Ou seja, esse objeto de servidor também precisa estar vinculado a uma porta específica para que nosso servidor possa ter acesso a todas as solicitações recebidas nessa porta.

Para fazer isso, usaremos o método .listen do nosso objeto de servidor HTTP, .listen(PORT , CB).

@params PORT é o número da porta onde queremos que nosso servidor ouça.

@params A chamada de retorno é chamada assim que o servidor começar a escutar.

Amostra de código:

Até agora, nosso servidor está pronto para receber solicitações.

Deixe-nos executar nosso aplicativo Node.js:

 node app.js 

E acerte nosso servidor com a seguinte curva em um terminal:

 curl -d “Hello World” -H “Tipo de conteúdo: texto” -X POST http: // localhost: 8008 

WooHoo !! Parabéns, você criou um aplicativo Node.js sem nenhum pacote externo.

É maravilhosamente aplaudível que você tenha permanecido assim por muito tempo.

Se você estiver disposto a aprender mais sobre o núcleo do Node.js como este, me avise estourando as contagens de palmas para 50.

Nos próximos artigos, continuaremos desenvolvendo esse aplicativo básico e adicionando outros recursos críticos de roteamento, middleware, tratamento de erros, etc. Receba notificações seguindo-me aqui no Medium.

Eu tentei fazer este artigo o mais completo possível. Se tiver alguma ideia que possa melhorar, por favor mencione nos seus valiosos comentários.

Você pode me conectar via Gmail ou tweet-me aqui .

Muito obrigado pelo seu amor! Perdoe meus erros, você tem sido um público maravilhoso.

Parabéns!