src/
  config/
    - configuration files
  controllers/
    - routes with provider functions as callback functions
  providers/
    - business logic for controller routes
  services/
    - common business logic used in the provider functions
  models/
    - database models
  routes.js
    - load all routes
  db.js
    - load all models
  app.js
    - load all of the above
test/
  unit/
    - unit tests
  integration/
    - integration tests
server.js
  - load the app.js file and listen on a port
(cluster.js)
  - load the app.js file and create a cluster that listens on a port
test.js
  - main test file that will run all test cases under the test/ directory

Com essa configuração, você pode limitar o tamanho do arquivo para cerca de 100 linhas, tornando as revisões de código e a solução de problemas muito menos um pesadelo. Você já teve que revisar uma solicitação de recebimento em que cada arquivo tem mais de 500 linhas de código? Adivinha o quê, não é divertido.

Gostaria de chamar uma coisa de separação de preocupações. Você não deseja criar clusterfucks de lógica em um único arquivo. Separe as preocupações em seus arquivos dedicados. Dessa forma, você pode limitar a alternância de contexto que ocorre ao ler um único arquivo. Também é muito útil ao mesclar com o mestre, porque é muito menos propenso a causar conflitos de mesclagem.

Para impor regras como essa em sua equipe, você também pode configurar um linter para informar quando você exceder um limite definido de linhas em um arquivo, bem como se uma única linha tiver mais de 100 caracteres. Uma das minhas configurações favoritas, a propósito.

Como melhorar o desempenho e a confiabilidade do Express.js

O Express.js possui algumas práticas recomendadas conhecidas que você deve aderir. Abaixo estão alguns que eu acho que são os mais importantes.

Definir NODE_ENV = produção

Aqui está uma dica rápida para melhorar o desempenho. Você acreditaria que somente a configuração da variável de ambiente NODE_ENV como produção tornará seu aplicativo Express.js três vezes mais rápido!

No terminal você pode configurá-lo com:

export NODE_ENV=production

Ou, ao executar o arquivo server.js, você pode adicionar assim:

NODE_ENV=production node server.js

Ativar compactação Gzip

Seguindo em frente, outra configuração importante é ativar a compactação Gzip. Primeiro, instale o pacote npm de compactação:

npm i compression

Em seguida, adicione este trecho abaixo ao seu código:

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())

Se você estiver usando um proxy reverso com o Nginx, poderá ativá-lo nesse nível. Isso é coberto no Habilitando a compactação Gzip com o Nginx seção um pouco mais abaixo.

Sempre use funções assíncronas

A última coisa que você deseja fazer é bloquear o encadeamento de execução. Nunca use funções síncronas! Sério, não. Quero dizer.

O que você deve fazer é usar Promessas ou Async / Await. Se por acaso você tiver acesso apenas às funções de sincronização, poderá envolvê-las facilmente em uma função assíncrona que a executará fora do encadeamento principal.

(async () => {
  const foo = () => {
    ...some sync code
    return val
  }

  async const asyncWrapper = (syncFun) => {
    const val = syncFun()
    return val
  }

  // the value will be returned outside of the main thread of execution
  const val = await asyncWrapper(foo)
})()

Se você realmente não puder evitar o uso de uma função síncrona, poderá executá-las em um thread separado. Para evitar bloquear o encadeamento principal e atrapalhar sua CPU, você pode criar processos filho ou garfos para lidar com tarefas intensivas da CPU.

Um exemplo seria o fato de você ter um servidor da web que lida com solicitações recebidas. Para evitar o bloqueio desse thread, você pode gerar um processo filho para lidar com uma tarefa intensiva da CPU. Muito legal. Eu expliquei isso com mais detalhes aqui.

Certifique-se de fazer o log corretamente

Para unificar logs no aplicativo Express.js, em vez de usar console.log (), você deve usar um agente de log para estruturar e coletar logs em um local central.

Você pode usar qualquer Ferramenta de gerenciamento de log SaaS como o local central, como Sematext, Logz.io, Datadog e muitos mais. Pense nisso como um depósito em que você mantém os logs para poder pesquisá-los e filtrá-los mais tarde, mas também ser alertado sobre logs e exceções de erros.

Eu faço parte da equipe de integrações aqui no Sematext, construção agentes de código aberto para Node.js. Eu montei isso pequeno agente Express.js de código aberto para coletar logs. Ele também pode coletar métricas, mas um pouco mais adiante. O agente é baseado em Winston e Morgan. Ele rastreia o tráfego de solicitação da API com um middleware. Isso fornecerá registros e dados por rota imediatamente, o que é crucial para acompanhar o desempenho.

Nota: As funções de middleware do Express.js. são funções que têm acesso ao objeto de solicitação (req), o objeto de resposta (res) e a próxima função de middleware no ciclo de solicitação e resposta do aplicativo. A próxima função de middleware é geralmente indicada por uma variável chamada next. – de Usando middleware, expressjs.com

Veja como adicionar o criador de logs e o middleware:

const { stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')

// At the top of your routes add the stHttpLoggerMiddleware to send API logs to Sematext
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)

// Use the stLogger to send all types of logs directly to Sematext
app.get('/api', (req, res, next) => {
 stLogger.info('An info log.')
 stLogger.debug('A debug log.')
 stLogger.warn('A warning log.')
 stLogger.error('An error log.')


 res.status(200).send('Hello World.')
})

Antes de exigir esse agente, você precisa configurar Tokens de texto semântico como variáveis ​​de ambiente. Na seção dotenv abaixo, você lerá mais sobre a configuração de variáveis ​​de ambiente.

Aqui está uma prévia rápida do que você pode obter.

Manipular Erros e Exceções Corretamente

Ao usar Async / Await em seu código, é uma prática recomendada confiar nas instruções try-catch para lidar com erros e exceções, além de usar o criador de logs Express unificado para enviar o log de erros para um local central, para que você possa usá-lo para solucionar problemas do problema com um rastreamento de pilha.

async function foo() {
  try {
    const baz = await bar()
    return baz
  } catch (err) {
    stLogger.error('Function 'bar' threw an exception.', err);
  }
}

Também é uma prática recomendada configurar um middleware de erro geral na parte inferior do arquivo routes.js.

function errorHandler(err, req, res, next) {
  stLogger.error('Catch-All error handler.', err)
  res.status(err.status || 500).send(err.message)
}

router.use(errorHandler)
module.exports = router

Isso capturará qualquer erro gerado nos seus controladores. Outro último passo que você pode fazer é adicionar ouvintes ao próprio processo.

process.on('uncaughtException', (err) => {
  stLogger.error('Uncaught exception', err)
  throw err
})

process.on('unhandledRejection', (err) => {
  stLogger.error('unhandled rejection', err)
})

Com esses pequenos trechos, você abordará todas as precauções necessárias para lidar com erros do Express e coleta de logs. Agora você tem uma base sólida onde não precisa se preocupar em perder o controle de erros e logs. Daqui você pode configurar alertas na interface do usuário de Logs de Sematext e pegue notificado através do Slack ou E-mail, configurado por padrão. Não permita que seus clientes digam que seu aplicativo está quebrado, saiba antes disso.

Cuidado com vazamentos de memória

Você não pode detectar erros antes que eles aconteçam. Alguns problemas não têm causas raiz em exceções que interrompem seu aplicativo. Eles são silenciosos e, como vazamentos de memória, surgem em você quando você menos espera. Expliquei como evitar vazamentos de memória em um dos meus tutoriais anteriores. Tudo se resume a impedir qualquer possibilidade de vazamento de memória.

Perceber vazamentos de memória é mais fácil do que você imagina. Se a memória do processo continuar crescendo de forma constante, embora não seja periodicamente reduzida pela coleta de lixo, você provavelmente terá um vazamento de memória. Idealmente, você deseja se concentrar em evitar vazamentos de memória, em vez de solucionar problemas e depurá-los. Se você encontrar um vazamento de memória em seu aplicativo, é terrivelmente difícil rastrear a causa raiz.

É por isso que você precisa analisar métricas sobre processos e memória de pilha.

Adicionando um coletor de métricas ao seu aplicativo Express.js, que reunirá e armazenará todas as principais métricas em um local central, onde você poderá posteriormente fatiar e dividir os dados para chegar à causa raiz de quando ocorreu um vazamento de memória e, mais importante, por que aconteceu.

Importando um agente de monitoramento do Sematext Agent Express No módulo mencionado acima, você pode ativar o coletor de métricas para armazenar e visualizar todos os dados na interface do usuário de monitoramento de Sematext.

Aqui está o kicker, é apenas uma linha de código. Adicione este snippet ao seu arquivo app.js.

const { stMonitor, stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')
stMonitor.start() // run the .start method on the stMonitor

// At the top of your routes add the stHttpLoggerMiddleware to send API logs to Sematext
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
...

Com isso, você terá acesso a vários painéis, fornecendo informações importantes sobre tudo o que está acontecendo com seu aplicativo Express.js. Você pode filtrar e agrupar os dados para visualizar processos, memória, uso da CPU e solicitações e respostas HTTP. Porém, o que você deve fazer imediatamente é configurar alertas para notificá-lo quando a memória do processo começar a crescer constantemente, sem aumentar a taxa de solicitação.

Seguindo as dicas e práticas recomendadas específicas do Express.js, vamos falar um pouco sobre JavaScript e como usar a própria linguagem de uma maneira mais otimizada e sólida.

Como configurar seu ambiente JavaScript

JavaScript não é orientado a objetos nem funcional. Pelo contrário, é um pouco de ambos. Sou bastante inclinado a usar o máximo possível de paradigmas funcionais no meu código. No entanto, um supera todos os outros. Usando funções puras.

Funções puras

Como o nome sugere, funções puras são funções que não modificam o estado externo. Eles pegam parâmetros, fazem algo com eles e retornam um valor.

Cada vez que você os executa, eles se comportam da mesma forma e retornam um valor. Esse conceito de jogar fora as mutações de estado e confiar apenas em funções puras é algo que simplificou minha vida em uma extensão enorme.

Em vez de usar var ou deixar apenas usar const, confie em funções puras para criar novos objetos em vez de alterar objetos existentes. Isso está relacionado ao uso funções de ordem superior em JavaScript, como .map (), .reduce (), .filter () e muito mais.

Como praticar a escrita de código funcional? Jogue fora toda declaração de variável, exceto const. Agora tente escrever um controlador.

Parâmetros do objeto

O JavaScript é uma linguagem de tipo fraco e pode mostrar sua cabeça feia ao lidar com argumentos de função. Uma chamada de função pode receber um, nenhum ou tantos parâmetros quanto você desejar, mesmo que a declaração da função tenha um número fixo de argumentos definidos. O pior é que a ordem dos parâmetros é fixa e não há como impor seus nomes para que você saiba o que está sendo repassado.

É uma loucura absoluta! Tudo isso, enlouquecendo! Por que não há como impor isso? Mas você pode resolvê-lo um pouco usando objetos como parâmetros de função.

const foo = ({ param1, param2, param3 }) => {
 if (!(param1 && param2 && param3)) {
   throw Error('Invalid parameters in function: foo.')
}

 const sum = param1 + param2 + param3
 return sum
}

foo({ param1: 5, param2: 345, param3: 98 })
foo({ param2: 45, param3: 57, param1: 81 }) // <== the same

Todas essas chamadas de função funcionarão de forma idêntica. Você pode aplicar os nomes dos parâmetros e não está vinculado à ordem, facilitando o gerenciamento.

Freaking escrever testes, a sério!

Você sabe qual é a melhor maneira de documentar seu código, acompanhar os recursos e dependências, aumentar a conscientização da comunidade, obter colaboradores, aumentar o desempenho, aumentar a produtividade do desenvolvedor, ter uma vida melhor, atrair investidores, aumentar a produção, ganhar milhões vendendo seu startup!? .... espera que saiu do controle.

Sim, você adivinhou, escrever testes é a resposta.

Vamos voltar à pista. Escreva testes com base nos recursos que você deseja criar. Em seguida, escreva o recurso. Você terá uma imagem clara do que deseja construir. Durante esse processo, você começará automaticamente a pensar em todos os casos extremos que você nunca consideraria.

Confie em mim, o TDD funciona.

Como começar? Use algo simples como Mocha e Chai. Mocha é uma estrutura de teste, enquanto Chai é uma biblioteca de asserções.

Instale os pacotes npm com:

npm i mocha chai

Vamos testar a função foo de cima. No seu arquivo test.js principal, adicione este trecho de código:

const chai = require('chai')
const expect = chai.expect

const foo = require('./src/foo')

describe('foo', function () {
  it('should be a function', function () {
    expect(foo).to.be.a('function')
  })
  it('should take one parameter', function () {
    expect(
      foo.bind(null, { param1: 5, param2: 345, param3: 98 }))
      .to.not.throw(Error)
  })
  it('should throw error if the parameter is missing', function () {
    expect(foo.bind(null, {})).to.throw(Error)
  })
  it('should throw error if the parameter does not have 3 values', function () {
    expect(foo.bind(null, { param1: 4, param2: 1 })).to.throw(Error)
  })
  it('should return the sum of three values', function () {
    expect(foo({ param1: 1, param2: 2, param3: 3 })).to.equal(6)
  })
})

Adicione isso à sua seção de scripts no package.json:

"scripts": {
 "test": "mocha"
}

Agora você pode executar os testes executando um único comando no seu terminal:

npm test

A saída será:

> [email protected] test /path/to/your/expressjs/project
> mocha

foo
  ✓ should be a function
  ✓ should take one parameter
  ✓ should throw error if the parameter is missing
  ✓ should throw error if the parameter does not have 3 values
  ✓ should return the sum of three values

 5 passing (6ms)

Escrever testes dá a você uma sensação de clareza. E parece incrível demais! Já me sinto melhor.

Com isso fora do meu sistema, estou pronto para os tópicos do DevOps. Vamos passar para alguma automação e configuração.

Além das coisas que você pode fazer no código, como você viu acima, algumas coisas precisam ser configuradas no ambiente e na configuração do servidor. Desde o básico, você precisa de uma maneira fácil de gerenciar variáveis ​​de ambiente, além de garantir que o aplicativo Express.js seja reiniciado automaticamente, caso ocorra uma falha.

Você também deseja configurar um proxy reverso e um balanceador de carga para expor seu aplicativo, solicitações de cache e tráfego de balanceamento de carga em vários processos de trabalho. A etapa mais importante para manter o alto desempenho é adicionar um coletor de métricas para que você possa visualizar dados ao longo do tempo e solucionar problemas sempre que eles ocorrerem.

Gerenciando variáveis ​​de ambiente no Node.js com dotenv

Dotenv é um módulo npm que permite carregar variáveis ​​de ambiente facilmente em qualquer aplicativo Node.js. usando um arquivo

Na raiz do seu projeto, crie um arquivo .env. Aqui você adiciona todas as variáveis ​​de ambiente necessárias.

NODE_ENV=production
DEBUG=false
LOGS_TOKEN=xxx-yyy-zzz
MONITORING_TOKEN=xxx-yyy-zzz
INFRA_TOKEN=xxx-yyy-zzz
...

Carregar esse arquivo é super simples. No seu arquivo app.js, exija dotenv na parte superior antes de qualquer outra coisa.

// dotenv at the top
require('dotenv').config()

// require any agents
const { stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')

// require express and instantiate the app
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
...

Dotenv carregará um arquivo chamado .env por padrão. Se você deseja ter vários arquivos dotenv, aqui está como você pode configurá-los.

Verifique se o aplicativo reinicia automaticamente com Systemd ou PM2

JavaScript é uma linguagem de script, obviamente, o nome diz isso. O que isto significa? Quando você inicia o arquivo server.js executando o nó server.js, ele executa o script como um processo. No entanto, se falhar, o processo é encerrado e não há nada dizendo para reiniciar.

Aqui é onde o uso do Systemd ou PM2 entra em jogo. Qualquer um deles funciona bem, mas os mantenedores do Node.js. pedem que usemos o Systemd.

Verifique se o aplicativo reinicia com o Systemd

Em resumo, o Systemd faz parte dos elementos básicos dos sistemas operacionais Linux. Ele executa e gerencia os processos do sistema. O que você deseja é executar o processo do Node.js. como um serviço do sistema para que ele possa se recuperar de falhas.

Aqui está como você faz isso. Na sua VM ou servidor, crie um novo arquivo em /lib/systemd/system/ chamado app.service.

# /lib/systemd/system/fooapp.service
[Unit]
Description=Node.js as a system service.
Documentation=https://example.com
After=network.target
[Service]
Type=simple
User=ubuntu
ExecStart=/usr/bin/node /path/to/your/express/project/server.js
Restart=on-failure
[Install]
WantedBy=multi-user.target

As duas linhas importantes neste arquivo são ExecStart e Restart. o ExecStart diz que o /usr/bin/node binário irá iniciar o seu server.js Arquivo. Certifique-se de adicionar um caminho absoluto ao seu server.js Arquivo. o Restart=on-failure Certifique-se de reiniciar o aplicativo se ele travar. Exatamente o que você está procurando.

Depois de salvar o fooapp.service , recarregue seu daemon e inicie o script.

systemctl daemon-reload
systemctl start fooapp
systemctl enable fooapp
systemctl status fooapp

O comando status mostrará que o aplicativo está sendo executado como um serviço do sistema. O comando enable garante que ele seja iniciado na inicialização. Isso foi mais fácil do que você pensou, estou certo?

Verifique se o aplicativo é reiniciado com o PM2

O PM2 existe há alguns anos. Eles utilizam um script personalizado que gerencia e executa o arquivo server.js. É mais simples de configurar, mas vem com a sobrecarga de ter outro processo Node.js. que atua como um processo Mestre, como um gerente, para seus processos de aplicativo Express.js.

Primeiro você precisa instalar o PM2:

npm i -g pm2

Em seguida, inicie o aplicativo executando este comando no diretório raiz do seu projeto Express.js:

pm2 start server.js -i max

o -i max O flag garantirá o início do aplicativo no modo de cluster, gerando tantos trabalhadores quanto os núcleos da CPU no servidor.

Mencionar o modo de cluster é o procedimento perfeito para a próxima seção sobre balanceamento de carga e proxies reversos e cache.

Ativar balanceamento de carga e proxies reversos

O balanceamento de carga pode ser feito com o módulo de cluster Node.js. ou com o Nginx. Vou mostrar a minha configuração preferida, que também é o que os espreitadores do Node.js acham que é o caminho certo a seguir.

Balanceamento de carga com o módulo de cluster

O módulo de cluster interno no Node.js permite gerar processos de trabalho que atenderão ao seu aplicativo. É baseado no child_process implementação e, felizmente para nós, é muito fácil de configurar se você tiver um aplicativo Express.js básico.

Você realmente só precisa adicionar mais um arquivo. Crie um arquivo chamado cluster.js e cole este trecho de código nele:

const cluster = require('cluster')
const numCPUs = require('os').cpus().length
const app = require('./src/app')
const port = process.env.PORT || 3000

const masterProcess = () => Array.from(Array(numCPUs)).map(cluster.fork)
const childProcess = () => app.listen(port)

if (cluster.isMaster) {
 masterProcess()
} else {
 childProcess()
}

cluster.on('exit', () => cluster.fork())

Vamos detalhar o que está acontecendo aqui. Quando você inicia o cluster.js arquivo com node cluster.js o módulo de cluster detectará que está sendo executado como um processo mestre. Nesse caso, invoca o masterProcess() função. o masterProcess() A função conta quantos núcleos de CPU o servidor possui e chama o cluster.fork() função que muitas vezes. Uma vez o cluster.fork() é chamada, o módulo de cluster detectará que está sendo executado como um processo filho e chamará o childProcess() , que informa ao servidor Express.js para .listen() em uma porta. Todos esses processos estão em execução na mesma porta. É possível devido a algo chamado de conexão IPC. Leia mais sobre isso aqui.

o cluster.on('exit') O ouvinte de eventos reiniciará um processo de trabalho, se falhar.

Com esta configuração, agora você pode editar o ExecStart campo no fooapp.service Arquivo de serviço Systemd para executar o cluster.js arquivo.

Substituir:

ExecStart=/usr/bin/node /path/to/your/express/project/server.js

Com:

ExecStart=/usr/bin/node /path/to/your/express/project/cluster.js

Recarregue o daemon Systemd e reinicie o fooapp.service:

systemctl daemon-reload
systemctl restart fooapp

Aí está. Você adicionou o balanceamento de carga ao seu aplicativo Express.js. Agora ele será escalado em todas as CPUs do seu servidor.

No entanto, isso funcionará apenas para uma configuração de servidor único. Se você quiser ter vários servidores, precisará do Nginx.

Adicionando um proxy reverso com o Nginx

Uma das leis primordiais da execução de aplicativos Node.js. é nunca expô-los na porta 80 ou 443. Você sempre deve usar um proxy reverso para direcionar o tráfego para seu aplicativo. O Nginx é a ferramenta mais comum que você usa com o Node.js para conseguir isso. É um servidor web que pode atuar como proxy reverso e balanceador de carga.

A instalação do Nginx é bastante simples, para o Ubuntu seria assim:

apt update
apt install nginx

Verifique as instruções de instalação do Nginx se você estiver usando outro sistema operacional.

O Nginx deve começar imediatamente, mas verifique se:

systemctl status nginx

[Output]
nginx.service - A high performance web server and a reverse proxy server
  Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
  Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago
    Docs: man:nginx(8)
Main PID: 2369 (nginx)
  Tasks: 2 (limit: 1153)
  CGroup: /system.slice/nginx.service
          ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
          └─2380 nginx: worker process

Se não for iniciado, vá em frente e execute este comando para iniciá-lo.

systemctl start nginx

Depois de executar o Nginx, você precisará editar a configuração para ativar um proxy reverso. Você pode encontrar o arquivo de configuração do Nginx no diretório /etc/nginx/ diretório. O arquivo de configuração principal é chamado nginx.conf, enquanto houver trechos adicionais no etc/nginx/sites-available/ diretório. A configuração padrão do servidor é encontrada aqui e é denominada default.

Para ativar apenas um proxy reverso, abra o default arquivo de configuração e edite-o para que fique assim:

server {
   listen 80;
   location / {
       proxy_pass http://localhost:3000; # change the port if needed
  }
}

Salve o arquivo e reinicie o serviço Nginx.

systemctl restart nginx

Essa configuração encaminhará todo o tráfego que atinge a porta 80 para o aplicativo Express.js.

Balanceamento de carga com Nginx

Se você quiser dar um passo adiante e ativar o balanceamento de carga, veja como fazê-lo.

Agora edite o arquivo principal nginx.conf Arquivo:

http {
   upstream fooapp {
       server localhost:3000;
       server domain2;
       server domain3;
      ...
  }
  ...
}

Adicionando isso upstream A seção criará um grupo de servidores que carregará o saldo do tráfego em todos os servidores que você especificar.

Você também precisa editar o default arquivo de configuração para apontar o proxy reverso para este upstream.

server {
   listen 80;
   location / {
       proxy_pass http://fooapp;
  }
}

Salve os arquivos e reinicie o serviço Nginx novamente.

systemctl restart nginx

Habilitando o cache com o Nginx

O armazenamento em cache é importante para reduzir o tempo de resposta dos pontos de extremidade da API e os recursos que não mudam com muita frequência.

Mais uma vez edite o arquivo nginx.conf e adicione esta linha:

http {
   proxy_cache_path /data/nginx/cache levels=1:2   keys_zone=STATIC:10m
  inactive=24h max_size=1g;
  ...
}

Abra o default arquivo de configuração novamente. Adicione estas linhas de código também:

server {
   listen 80;
   location / {
       proxy_pass             http://fooapp;
       proxy_set_header       Host $host;
       proxy_buffering       on;
       proxy_cache           STATIC;
       proxy_cache_valid      200 1d;
       proxy_cache_use_stale  error timeout invalid_header updating
            http_500 http_502 http_503 http_504;
  }
}

Salve os dois arquivos e reinicie o serviço Nginx novamente.

Habilitando a compactação Gzip com o Nginx

Para melhorar ainda mais o desempenho, vá em frente e ative o Gzip. No bloco do servidor do seu arquivo de configuração do Nginx, adicione estas linhas:

server {
   gzip on;
   gzip_types     text/plain application/xml;
   gzip_proxied    no-cache no-store private expired auth;
   gzip_min_length 1000;
  ...
}

Se você quiser conferir mais opções de configuração sobre a compactação Gzip no Nginx, Veja isso.

Ativando o cache com Redis

Redis em um armazenamento de dados na memória, que geralmente é usado como cache.

A instalação no Ubuntu é bastante simples:

apt update
apt install redis-server

Isso fará o download e instalará o Redis e suas dependências. Há uma alteração importante na configuração a ser feita no arquivo de configuração Redis que foi gerado durante a instalação.

Abra o /etc/redis/redis.conf Arquivo. Você precisa alterar uma linha de:

supervised no

Para:

supervised systemd

Essa é a única alteração que você precisa fazer no arquivo de configuração Redis neste momento. Portanto, salve e feche-o quando terminar. Em seguida, reinicie o serviço Redis para refletir as alterações feitas no arquivo de configuração:

systemctl restart redis
systemctl status redis

[Output]
● redis-server.service - Advanced key-value store
  Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
  Active: active (running) since Wed 2018-06-27 18:48:52 UTC; 12s ago
    Docs: http://redis.io/documentation,
          man:redis-server(1)
Process: 2421 ExecStop=/bin/kill -s TERM $MAINPID (code=exited, status=0/SUCCESS)
Process: 2424 ExecStart=/usr/bin/redis-server /etc/redis/redis.conf (code=exited, status=0/SUCCESS)
Main PID: 2445 (redis-server)
  Tasks: 4 (limit: 4704)
  CGroup: /system.slice/redis-server.service
          └─2445 /usr/bin/redis-server 127.0.0.1:6379

Em seguida, você instala o redis npm module para acessar o Redis a partir do seu aplicativo.

npm i redis

Agora você pode solicitá-lo em seu aplicativo e começar a armazenar em cache as respostas da solicitação. Deixe-me mostrar-lhe um exemplo:

const express = require('express')
const app = express()
const redis = require('redis')

const redisClient = redis.createClient(6379)

async function getSomethingFromDatabase (req, res, next) {
  try {
    const { id } = req.params;
    const data = await database.query()

    // Set data to Redis
    redisClient.setex(id, 3600, JSON.stringify(data))


    res.status(200).send(data)
  } catch (err) {
    console.error(err)
    res.status(500)
  }
}

function cache (req, res, next) {
  const { id } = req.params

  redisClient.get(id, (err, data) => {
    if (err) {
      return res.status(500).send(err)
    }


    // If data exists return the cached value
    if (data != null) {
      return res.status(200).send(data)
    }

   // If data does not exist, proceed to the getSomethingFromDatabase function
   next()
  })
}


app.get('/data/:id', cache, getSomethingFromDatabase)
app.listen(3000, () => console.log(`Server running on Port ${port}`))

Esse trecho de código armazenará em cache a resposta do banco de dados como uma sequência JSON no cache do Redis por 3600 segundos. Você pode mudar isso com base em suas próprias necessidades.

Com isso, você definiu as principais configurações para melhorar o desempenho. Mas você também introduziu outros possíveis pontos de falha. E se o Nginx travar ou o Redis sobrecarregar o espaço em disco? Como você soluciona isso?

Habilitar monitoramento e registro em VM / servidor

Idealmente, você configuraria um agente de infraestrutura em sua VM ou servidor reunir métricas e logs e enviá-los para um local central. Dessa forma, você pode acompanhar todas as métricas de infraestrutura, como CPU, memória, uso de disco, processos, etc.

Dessa forma, você pode ficar de olho em toda a infraestrutura, incluindo CPU, memória e uso de disco, além de todos os processos separados enquanto executa seu aplicativo no modo de cluster.

Mas precisamos saber o que está acontecendo com o Nginx primeiro. Você pode configurar o stub_status para mostrar métricas do Nginx, mas isso realmente não fornece nenhuma percepção acionável. Mas você pode instalar um Integração Nginx e obtenha informações sobre as métricas do Nginx ao lado do seu Integração Express.js na nuvem Sematext.

Por que o monitoramento do Nginx é importante? Nginx é o ponto de entrada para seu aplicativo. Se falhar, todo o aplicativo falhará. Sua instância do Node.js pode estar bem, mas o Nginx para de responder e seu site fica inativo. Você não tem idéia do que está acontecendo, porque o aplicativo Express.js ainda está sendo executado sem problemas.

Você precisa ficar de olho em todos os pontos de falha do seu sistema. É por isso que ter alertas apropriados é tão crucial. Se você quiser saber mais sobre alertas, pode Leia isso.

O mesmo vale para Redis. Para ficar de olho, confira maneiras de monitorar Redis, aqui ou aqui.

Isso envolve as ferramentas e práticas recomendadas do DevOps às quais você deve se ater. Que passeio que foi! Se você quiser se aprofundar no aprendizado sobre DevOps e ferramentas, confira este guia que meu colega de trabalho escreveu.

Empacotando

Levei quase quatro anos para começar a usar ferramentas adequadas e aderir às melhores práticas. No final, só quero ressaltar que a parte mais importante do seu aplicativo é estar disponível e ter bom desempenho. Caso contrário, você não verá nenhum usuário por perto. Se eles não podem usar seu aplicativo, qual é o objetivo?

A idéia por trás deste artigo era cobrir as práticas recomendadas às quais você deve seguir, mas também as práticas ruins a serem evitadas.

Você aprendeu muitas coisas novas neste tutorial do Express.js. Desde a otimização do Express.js, a criação de uma estrutura de projeto intuitiva e a otimização do desempenho até o aprendizado sobre as melhores práticas de JavaScript e o desenvolvimento orientado a testes. Você também aprendeu sobre tratamento, registro e monitoramento de erros.

Depois de tudo isso, você pode dizer com certeza que teve uma introdução à cultura DevOps. O que isso significa? Bem, certifique-se de escrever um software confiável e de alto desempenho com cobertura de teste, mantendo a melhor produtividade possível do desenvolvedor. É assim que nós, como engenheiros, continuamos amando nosso trabalho. Caso contrário, é tudo caos.

Espero que todos tenham gostado de ler isso tanto quanto eu. Se você gostou, sinta-se à vontade para clicar no botão de compartilhamento para que mais pessoas vejam este tutorial. Até a próxima vez, seja curioso e divirta-se.