Como configurar um projeto de back-end de API Express com PostgreSQL
Publicados: 2022-03-10Adotaremos uma abordagem de Desenvolvimento Orientado a Testes (TDD) e a configuração do trabalho de Integração Contínua (CI) para executar automaticamente nossos testes no Travis CI e AppVeyor, completos com qualidade de código e relatórios de cobertura. Aprenderemos sobre controladores, modelos (com PostgreSQL), tratamento de erros e middleware Express assíncrono. Por fim, concluiremos o pipeline de CI/CD configurando a implantação automática no Heroku.
Parece muito, mas este tutorial é destinado a iniciantes que estão prontos para experimentar um projeto de back-end com algum nível de complexidade e que ainda podem estar confusos sobre como todas as peças se encaixam em um projeto real .
É robusto sem sobrecarregar e é dividido em seções que você pode concluir em um período de tempo razoável.
Começando
A primeira etapa é criar um novo diretório para o projeto e iniciar um novo projeto de nó. O nó é necessário para continuar com este tutorial. Se você não o tiver instalado, acesse o site oficial, faça o download e instale-o antes de continuar.
Estarei usando o fio como meu gerenciador de pacotes para este projeto. Há instruções de instalação para seu sistema operacional específico aqui. Sinta-se à vontade para usar o npm se quiser.
Abra seu terminal, crie um novo diretório e inicie um projeto Node.js.
# create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init
Responda às perguntas a seguir para gerar um arquivo package.json . Este arquivo contém informações sobre seu projeto. O exemplo dessas informações inclui quais dependências ele usa, o comando para iniciar o projeto e assim por diante.
Agora você pode abrir a pasta do projeto no editor de sua escolha. Eu uso o código do estúdio visual. É um IDE gratuito com vários plugins para facilitar sua vida e está disponível para todas as principais plataformas. Você pode baixá-lo no site oficial.
Crie os seguintes arquivos na pasta do projeto:
- README.md
- .editorconfig
Aqui está uma descrição do que .editorconfig faz no site EditorConfig. (Você provavelmente não precisa disso se estiver trabalhando sozinho, mas não faz mal, então vou deixar aqui.)
“O EditorConfig ajuda a manter estilos de codificação consistentes para vários desenvolvedores trabalhando no mesmo projeto em vários editores e IDEs.”
Abra .editorconfig
e cole o seguinte código:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
O [*]
significa que queremos aplicar as regras que o acompanham a todos os arquivos do projeto. Queremos um tamanho de recuo de dois espaços e um conjunto de caracteres UTF-8
. Também queremos cortar o espaço em branco à direita e inserir uma linha vazia final em nosso arquivo.
Abra o README.md e inclua o nome do projeto como um elemento de primeiro nível.
# Express API template
Vamos adicionar o controle de versão imediatamente.
# initialize the project folder as a git repository git init
Crie um arquivo .gitignore e insira as seguintes linhas:
node_modules/ yarn-error.log .env .nyc_output coverage build/
Estes são todos os arquivos e pastas que não queremos rastrear. Ainda não os temos em nosso projeto, mas os veremos à medida que prosseguirmos.
Neste ponto, você deve ter a seguinte estrutura de pastas.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
Considero isso um bom ponto para confirmar minhas alterações e enviá-las para o GitHub.
Iniciando um novo projeto expresso
Express é uma estrutura Node.js para construir aplicativos da web. Segundo o site oficial, é um
Framework web rápido, sem opinião e minimalista para Node.js.
Existem outras excelentes estruturas de aplicativos da Web para Node.js, mas o Express é muito popular, com mais de 47 mil estrelas no GitHub no momento da redação deste artigo.
Neste artigo, não teremos muitas discussões sobre todas as partes que compõem o Express. Para essa discussão, recomendo que você confira a série de Jamie. A primeira parte está aqui, e a segunda parte está aqui.
Instale o Express e inicie um novo projeto Express. É possível configurar manualmente um servidor Express do zero, mas para facilitar nossa vida, usaremos o express-generator para configurar o esqueleto do aplicativo.
# install the express generator globally yarn global add express-generator # install express yarn add express # generate the express project in the current folder express -f
O sinalizador -f
força o Express a criar o projeto no diretório atual.
Vamos agora realizar algumas operações de limpeza da casa.
- Exclua o arquivo index/users.js .
- Exclua as pastas
public/
eviews/
. - Renomeie o arquivo bin/www para bin/www.js .
- Desinstale o
jade
com o comandoyarn remove jade
. - Crie uma nova pasta chamada
src/
e mova o seguinte para dentro dela: 1. arquivo app.js 2.bin/
pasta 3.routes/
pasta dentro. - Abra o package.json e atualize o script de
start
para ficar como abaixo.
"start": "node ./src/bin/www"
Neste ponto, a estrutura de pastas do seu projeto se parece com a abaixo. Você pode ver como o VS Code destaca as alterações de arquivo que ocorreram.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
Abra src/app.js e substitua o conteúdo pelo código abaixo.
var logger = require('morgan'); var express = require('express'); var cookieParser = require('cookie-parser'); var indexRouter = require('./routes/index'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); module.exports = app;
Depois de exigir algumas bibliotecas, instruímos o Express a lidar com todas as solicitações que chegam a /v1
com indexRouter
.
Substitua o conteúdo de routes/index.js pelo código abaixo:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { return res.status(200).json({ message: 'Welcome to Express API template' }); }); module.exports = router;
Pegamos o Express, criamos um roteador a partir dele e servimos a rota /
, que retorna um código de status de 200
e uma mensagem JSON.
Inicie o aplicativo com o comando abaixo:
# start the app yarn start
Se você configurou tudo corretamente, deverá ver apenas $ node ./src/bin/www
em seu terminal.
Visite https://localhost:3000/v1
em seu navegador. Você deverá ver a seguinte mensagem:
{ "message": "Welcome to Express API template" }
Este é um bom ponto para comprometer nossas mudanças.
- A ramificação correspondente no meu repositório é 01-install-express.
Convertendo nosso código para ES6
O código gerado pelo express-generator
está em ES5
, mas neste artigo estaremos escrevendo todo o nosso código em sintaxe ES6
. Então, vamos converter nosso código existente para ES6
.
Substitua o conteúdo de routes/index.js pelo código abaixo:
import express from 'express'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: 'Welcome to Express API template' }) ); export default indexRouter;
É o mesmo código que vimos acima, mas com a instrução import e uma função de seta no manipulador /
route.
Substitua o conteúdo de src/app.js pelo código abaixo:
import logger from 'morgan'; import express from 'express'; import cookieParser from 'cookie-parser'; import indexRouter from './routes/index'; const app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); export default app;
Vamos agora dar uma olhada no conteúdo de src/bin/www.js . Vamos construí-lo de forma incremental. Exclua o conteúdo de src/bin/www.js
e cole no bloco de código abaixo.
#!/usr/bin/env node /** * Module dependencies. */ import debug from 'debug'; import http from 'http'; import app from '../app'; /** * Normalize a port into a number, string, or false. */ const normalizePort = val => { const port = parseInt(val, 10); if (Number.isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }; /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ const server = http.createServer(app); // next code block goes here
Este código verifica se uma porta personalizada está especificada nas variáveis de ambiente. Se nenhum for definido, o valor de porta padrão de 3000
será definido na instância do aplicativo, após ser normalizado para uma string ou um número por normalizePort
. O servidor é então criado a partir do módulo http
, com app
como função de retorno de chamada.
A linha do #!/usr/bin/env node
é opcional, pois especificamos o nó quando queremos executar este arquivo. Mas certifique-se de que esteja na linha 1 do arquivo src/bin/www.js ou remova-o completamente.
Vamos dar uma olhada na função de tratamento de erros. Copie e cole este bloco de código após a linha onde o servidor foi criado.
/** * Event listener for HTTP server "error" event. */ const onError = error => { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': alert(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': alert(`${bind} is already in use`); process.exit(1); break; default: throw error; } }; /** * Event listener for HTTP server "listening" event. */ const onListening = () => { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; debug(`Listening on ${bind}`); }; /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening);
A função onError
escuta erros no servidor http e exibe as mensagens de erro apropriadas. A função onListening
simplesmente envia a porta que o servidor está escutando no console. Por fim, o servidor escuta as solicitações recebidas no endereço e na porta especificados.
Neste ponto, todo o nosso código existente está na sintaxe ES6
. Pare seu servidor (use Ctrl + C ) e execute yarn start
. Você receberá um erro SyntaxError: Invalid or unexpected token
. Isso acontece porque o Node (no momento da escrita) não suporta algumas das sintaxes que usamos em nosso código.
Vamos agora corrigir isso na seção a seguir.
Configurando dependências de desenvolvimento: babel
, nodemon
, eslint
e prettier
É hora de configurar a maioria dos scripts que vamos precisar nesta fase do projeto.
Instale as bibliotecas necessárias com os comandos abaixo. Você pode simplesmente copiar tudo e colá-lo no seu terminal. As linhas de comentário serão ignoradas.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
Isso instala todos os scripts babel listados como dependências de desenvolvimento. Verifique seu arquivo package.json e você deverá ver uma seção devDependencies
. Todos os scripts instalados serão listados lá.
Os scripts babel que estamos usando são explicados abaixo:
@babel/cli | Uma instalação necessária para usar o babel . Ele permite o uso do Babel a partir do terminal e está disponível como ./node_modules/.bin/babel . |
@babel/core | Funcionalidade básica do Babel. Esta é uma instalação necessária. |
@babel/node | Isso funciona exatamente como a CLI do Node.js, com o benefício adicional de compilar com predefinições e plugins do babel . Isso é necessário para uso com nodemon . |
@babel/plugin-transform-runtime | Isso ajuda a evitar a duplicação na saída compilada. |
@babel/preset-env | Uma coleção de plugins que são responsáveis por realizar transformações de código. |
@babel/register | Isso compila arquivos em tempo real e é especificado como um requisito durante os testes. |
@babel/runtime | Isso funciona em conjunto com @babel/plugin-transform-runtime . |
Crie um arquivo chamado .babelrc na raiz do seu projeto e adicione o seguinte código:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
Vamos instalar o nodemon
# install nodemon yarn add nodemon --dev
nodemon
é uma biblioteca que monitora o código-fonte do nosso projeto e reinicia automaticamente nosso servidor sempre que observa alguma alteração.
Crie um arquivo chamado nodemon.json na raiz do seu projeto e adicione o código abaixo:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
A tecla watch
informa nodemon
quais arquivos e pastas devem ser observados quanto a alterações. Portanto, sempre que qualquer um desses arquivos for alterado, o nodemon reinicia o servidor. A tecla ignore
informa aos arquivos para não observarem as alterações.
Agora atualize a seção de scripts
do seu arquivo package.json para que fique assim:
# build the content of the src folder "prestart": "babel ./src --out-dir build" # start server from the build folder "start": "node ./build/bin/www" # start server in development mode "startdev": "nodemon --exec babel-node ./src/bin/www"
- Os scripts
prestart
compilam o conteúdo da pastasrc/
e o colocam na pastabuild/
. Quando você emite o comandoyarn start
, esse script é executado antes do scriptstart
. -
start
script agora serve o conteúdo da pastabuild/
em vez da pastasrc/
que estávamos servindo anteriormente. Este é o script que você usará ao servir o arquivo em produção. Na verdade, serviços como o Heroku executam esse script automaticamente quando você implanta. -
yarn startdev
é usado para iniciar o servidor durante o desenvolvimento. A partir de agora, usaremos este script à medida que desenvolvemos o aplicativo. Observe que agora estamos usandobabel-node
para executar o aplicativo em vez donode
normal. O sinalizador--exec
forçababel-node
a servir a pastasrc/
. Para o script destart
, usamosnode
, pois os arquivos na pastabuild/
foram compilados para ES5.
Execute yarn startdev
e visite https://localhost:3000/v1. Seu servidor deve estar funcionando novamente.
A etapa final desta seção é configurar o ESLint
e o prettier
. O ESLint ajuda a impor regras de sintaxe, enquanto o mais bonito ajuda a formatar nosso código adequadamente para facilitar a leitura.
Adicione ambos com o comando abaixo. Você deve executar isso em um terminal separado enquanto observa o terminal em que nosso servidor está sendo executado. Você deve ver o servidor reiniciando. Isso ocorre porque estamos monitorando o arquivo package.json para alterações.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
Agora crie o arquivo .eslintrc.json na root
do projeto e adicione o código abaixo:
{ "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": ["airbnb-base"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { "indent": ["warn", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": 1, "comma-dangle": [0], "arrow-parens": [0], "object-curly-spacing": ["warn", "always"], "array-bracket-spacing": ["warn", "always"], "import/prefer-default-export": [0] } }
Este arquivo define principalmente algumas regras contra as quais o eslint
verificará nosso código. Você pode ver que estamos estendendo as regras de estilo usadas pelo Airbnb.
Na seção "rules"
, definimos se eslint
deve mostrar um aviso ou um erro ao encontrar determinadas violações. Por exemplo, ele mostra uma mensagem de aviso em nosso terminal para qualquer recuo que não use 2 espaços. Um valor de [0]
desativa uma regra, o que significa que não receberemos um aviso ou erro se violarmos essa regra.
Crie um arquivo chamado .prettierrc e adicione o código abaixo:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
Estamos definindo uma largura de tabulação de 2
e impondo o uso de aspas simples em todo o nosso aplicativo. Verifique o guia mais bonito para mais opções de estilo.
Agora adicione os seguintes scripts ao seu package.json :
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
Executar yarn lint
. Você deve ver vários erros e avisos no console.
O comando pretty
embeleza nosso código. O comando postpretty
é executado imediatamente depois. Ele executa o comando lint
com o sinalizador --fix
anexado. Esse sinalizador informa ao ESLint
para corrigir automaticamente problemas comuns de linting. Dessa forma, executo principalmente o comando yarn pretty
sem me preocupar com o comando lint
.
Execute yarn pretty
. Você deve ver que temos apenas dois avisos sobre a presença de alert
no arquivo bin/www.js .
Veja como está nossa estrutura de projeto neste momento.
EXPRESS-API-TEMPLATE ├── build ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── nodemon.json ├── package.json ├── README.md └── yarn.lock
Você pode descobrir que tem um arquivo adicional, yarn-error.log
na raiz do seu projeto. Adicione-o ao arquivo .gitignore
. Confirme suas alterações.
- A ramificação correspondente neste ponto no meu repositório é 02-dev-dependencies.
Configurações e variáveis de ambiente em nosso arquivo .env
Em quase todos os projetos, você precisará de um lugar para armazenar as configurações que serão usadas em todo o seu aplicativo, por exemplo, uma chave secreta da AWS. Armazenamos essas configurações como variáveis de ambiente. Isso os mantém longe de olhares indiscretos e podemos usá-los em nosso aplicativo conforme necessário.
Gosto de ter um arquivo settings.js com o qual leio todas as minhas variáveis de ambiente. Em seguida, posso consultar o arquivo de configurações de qualquer lugar dentro do meu aplicativo. Você tem a liberdade de nomear esse arquivo como quiser, mas há algum tipo de consenso sobre como nomear esses arquivos settings.js ou config.js .
Para nossas variáveis de ambiente, nós as manteremos em um arquivo .env
e as leremos em nosso arquivo de settings
a partir daí.
Crie o arquivo .env na raiz do seu projeto e digite a linha abaixo:
TEST_ENV_VARIABLE="Environment variable is coming across"
Para poder ler variáveis de ambiente em nosso projeto, existe uma boa biblioteca, dotenv
, que lê nosso arquivo .env
e nos dá acesso às variáveis de ambiente definidas dentro dele. Vamos instalá-lo.
# install dotenv yarn add dotenv
Adicione o arquivo .env à lista de arquivos monitorados pelo nodemon
.
Agora, crie o arquivo settings.js dentro da pasta src/
e adicione o código abaixo:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
Importamos o pacote dotenv
e chamamos seu método de configuração. Em seguida, exportamos a testEnvironmentVariable
que definimos em nosso arquivo .env
.
Abra src/routes/index.js e substitua o código pelo abaixo.
import express from 'express'; import { testEnvironmentVariable } from '../settings'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: testEnvironmentVariable })); export default indexRouter;
A única mudança que fizemos aqui é que importamos testEnvironmentVariable
do nosso arquivo de settings
e usamos como mensagem de retorno para uma solicitação da rota /
.
Visite https://localhost:3000/v1 e você deverá ver a mensagem, conforme mostrado abaixo.
{ "message": "Environment variable is coming across." }
E é isso. A partir de agora podemos adicionar quantas variáveis de ambiente quisermos e podemos exportá-las do nosso arquivo settings.js .
Este é um bom ponto para confirmar seu código. Lembre-se de embelezar e criar fiapos em seu código.
- A ramificação correspondente no meu repositório é 03-env-variables.
Escrevendo nosso primeiro teste
É hora de incorporar testes em nosso aplicativo. Uma das coisas que dão confiança ao desenvolvedor em seu código são os testes. Tenho certeza que você já viu inúmeros artigos na web pregando Desenvolvimento Orientado a Testes (TDD). Não pode ser enfatizado o suficiente que seu código precisa de alguma medida de teste. O TDD é muito fácil de seguir quando você está trabalhando com o Express.js.
Em nossos testes, faremos chamadas para nossos endpoints de API e verificaremos se o que é retornado é o que esperamos.
Instale as dependências necessárias:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
Cada uma dessas bibliotecas tem seu próprio papel a desempenhar em nossos testes.
mocha | corredor de teste |
chai | usado para fazer afirmações |
nyc | coletar relatório de cobertura de teste |
sinon-chai | estende as afirmações de chai |
supertest | usado para fazer chamadas HTTP para nossos endpoints de API |
coveralls | para carregar a cobertura de teste para o coveralls.io |
Crie uma nova pasta test/
na raiz do seu projeto. Crie dois arquivos dentro desta pasta:
- teste/setup.js
- teste/index.test.js
O Mocha encontrará a pasta test/
automaticamente.
Abra test/setup.js e cole o código abaixo. Este é apenas um arquivo auxiliar que nos ajuda a organizar todas as importações que precisamos em nossos arquivos de teste.
import supertest from 'supertest'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import app from '../src/app'; chai.use(sinonChai); export const { expect } = chai; export const server = supertest.agent(app); export const BASE_URL = '/v1';
Isso é como um arquivo de configurações, mas para nossos testes. Dessa forma, não precisamos inicializar tudo dentro de cada um de nossos arquivos de teste. Então, importamos os pacotes necessários e exportamos o que inicializamos - que podemos importar nos arquivos que precisam deles.
Abra index.test.js e cole o seguinte código de teste.
import { expect, server, BASE_URL } from './setup'; describe('Index page test', () => { it('gets base url', done => { server .get(`${BASE_URL}/`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.message).to.equal( 'Environment variable is coming across.' ); done(); }); }); });
Aqui fazemos uma solicitação para obter o endpoint base, que é /
e afirmamos que o res.
body
tem uma chave de message
com um valor de Environment variable is coming across.
Se você não estiver familiarizado com o padrão describe
, it
, eu o encorajo a dar uma olhada rápida no documento “Getting Started” do Mocha.
Adicione o comando test à seção de scripts
do package.json .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
Este script executa nosso teste com nyc
e gera três tipos de relatório de cobertura: um relatório HTML, enviado para a pasta coverage/
; um relatório de texto enviado para o terminal e um relatório lcov enviado para a pasta .nyc_output/
.
Agora execute yarn test
. Você deve ver um relatório de texto em seu terminal, assim como o da foto abaixo.

Observe que duas pastas adicionais são geradas:
-
.nyc_output/
-
coverage/
Olhe dentro .gitignore
e você verá que já estamos ignorando ambos. Eu encorajo você a abrir coverage/index.html
em um navegador e visualizar o relatório de teste para cada arquivo.
Este é um bom ponto para confirmar suas alterações.
- A ramificação correspondente no meu repositório é 04-first-test.
Integração Contínua (CD) e emblemas: Travis, Coveralls, Code Climate, AppVeyor
Agora é hora de configurar ferramentas de integração e implantação contínuas (CI/CD). Vamos configurar serviços comuns como travis-ci
, coveralls
, AppVeyor
e codeclimate
e adicionar emblemas ao nosso arquivo README.
Vamos começar.
Travis CI
O Travis CI é uma ferramenta que executa nossos testes automaticamente toda vez que enviamos um commit para o GitHub (e recentemente, Bitbucket) e sempre que criamos um pull request. Isso é útil principalmente ao fazer solicitações de pull, mostrando se o nosso novo código quebrou algum de nossos testes.
- Visite travis-ci.com ou travis-ci.org e crie uma conta se você não tiver uma. Você precisa se inscrever com sua conta do GitHub.
- Passe o mouse sobre a seta suspensa ao lado da foto do seu perfil e clique em
settings
. - Na guia
Repositories
, clique emManage repositories on Github
para ser redirecionado para o Github. - Na página do GitHub, role para baixo até
Repository access
e clique na caixa de seleção ao lado deOnly select repositories
. - Clique no menu suspenso
Select repositories
e localize o repositórioexpress-api-template
. Clique nele para adicioná-lo à lista de repositórios que você deseja adicionar aotravis-ci
. - Clique em
Approve and install
e espere ser redirecionado de volta aotravis-ci
. - Na parte superior da página do repositório, próximo ao nome do repositório, clique no ícone de
build unknown
. No modal de imagem de status, selecione markdown no menu suspenso de formato. - Copie o código resultante e cole-o em seu arquivo README.md .
- Na página do projeto, clique em
More options
>Settings
. Na seçãoEnvironment Variables
, adicione a variável envTEST_ENV_VARIABLE
. Ao inserir seu valor, certifique-se de colocá-lo entre aspas duplas como esta"Environment variable is coming across."
- Crie o arquivo .travis.yml na raiz do seu projeto e cole o código abaixo (Vamos definir o valor de
CC_TEST_REPORTER_ID
na seção Code Climate).
language: node_js env: global: - CC_TEST_REPORTER_ID=get-this-from-code-climate-repo-page matrix: include: - node_js: '12' cache: directories: [node_modules] install: yarn after_success: yarn coverage before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - yarn test after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESUL
Primeiro, dizemos ao Travis para executar nosso teste com o Node.js e, em seguida, definimos a variável de ambiente global CC_TEST_REPORTER_ID
(chegaremos a isso na seção Code Climate). Na seção da matrix
, dizemos ao Travis para executar nossos testes com o Node.js v12. Também queremos armazenar em cache o diretório node_modules/
para que ele não precise ser gerado novamente todas as vezes.
Instalamos nossas dependências usando o comando yarn
que é uma abreviação de yarn install
. Os comandos before_script
e after_script
são usados para carregar resultados de cobertura para codeclimate
. Vamos configurar codeclimate
breve. Depois yarn test
for executado com sucesso, queremos também executar a yarn coverage
que fará o upload do nosso relatório de cobertura para o coveralls.io.
Macacão
O Coveralls carrega dados de cobertura de teste para facilitar a visualização. Podemos visualizar a cobertura de teste em nossa máquina local a partir da pasta de cobertura, mas a Coveralls a disponibiliza fora de nossa máquina local.
- Visite coveralls.io e faça login ou cadastre-se com sua conta do Github.
- Passe o mouse sobre o lado esquerdo da tela para revelar o menu de navegação. Clique em
ADD REPOS
. - Procure o
express-api-template
e ative a cobertura usando o botão de alternância no lado esquerdo. Se você não conseguir encontrá-lo, clique emSYNC REPOS
no canto superior direito e tente novamente. Observe que seu repositório deve ser público, a menos que você tenha uma conta PRO. - Clique em detalhes para ir para a página de detalhes do repositório.
- Crie o arquivo .coveralls.yml na raiz do seu projeto e insira o código abaixo. Para obter o
repo_token
, clique nos detalhes do repo. Você vai encontrá-lo facilmente nessa página. Você pode simplesmente fazer uma pesquisa no navegador porrepo_token
.
repo_token: get-this-from-repo-settings-on-coveralls.io
Este token mapeia seus dados de cobertura para um repositório no Coveralls. Agora, adicione o comando de coverage
à seção de scripts
do seu arquivo package.json :
"coverage": "nyc report --reporter=text-lcov | coveralls"
Este comando carrega o relatório de cobertura na pasta .nyc_output
para coveralls.io. Ligue sua conexão com a Internet e execute:
yarn coverage
Isso deve carregar o relatório de cobertura existente para o macacão. Atualize a página do repositório em macacões para ver o relatório completo.
Na página de detalhes, role para baixo para encontrar a seção BADGE YOUR REPO
. Clique no menu suspenso EMBED
e copie o código de remarcação e cole-o em seu arquivo README .
Clima de código
Code Climate é uma ferramenta que nos ajuda a medir a qualidade do código. Ele nos mostra as métricas de manutenção verificando nosso código em relação a alguns padrões definidos. Ele detecta coisas como repetição desnecessária e loops for profundamente aninhados. Ele também coleta dados de cobertura de teste assim como coveralls.io.
- Visite codeclimate.com e clique em 'Sign up with GitHub'. Entre se você já tem uma conta.
- Uma vez em seu painel, clique em
Add a repository
. - Encontre o
express-api-template
na lista e clique emAdd Repo
. - Aguarde a conclusão da compilação e redirecione para o painel do repositório.
- Em
Codebase Summary
da base de código, clique emTest Coverage
. No menuTest coverage
, copie oTEST REPORTER ID
e cole-o em seu .travis.yml como o valor deCC_TEST_REPORTER_ID
. - Ainda na mesma página, na navegação à esquerda, em
EXTRAS
, clique em Badges. Copie os selos detest coverage
emaintainability
no formato markdown e cole-os no arquivo README.md .
É importante observar que existem duas maneiras de configurar as verificações de manutenibilidade. Existem as configurações padrão que são aplicadas a todos os repositórios, mas, se desejar, você pode fornecer um arquivo .codeclimate.yml na raiz do seu projeto. Usarei as configurações padrão, que você pode encontrar na guia Maintainability
da página de configurações do repositório. Eu encorajo você a dar uma olhada pelo menos. Se você ainda deseja definir suas próprias configurações, este guia fornecerá todas as informações necessárias.

AppVeyor
AppVeyor e Travis CI são ambos executores de testes automatizados. A principal diferença é que o travis-ci executa testes em um ambiente Linux enquanto o AppVeyor executa testes em um ambiente Windows. Esta seção está incluída para mostrar como começar a usar o AppVeyor.
- Visite o AppVeyor e faça login ou cadastre-se.
- Na página seguinte, clique em
NEW PROJECT
. - Na lista de repositórios, localize o
express-api-template
. Passe o mouse sobre ele e clique emADD
. - Clique na guia
Settings
. Clique emEnvironment
na navegação à esquerda. AdicioneTEST_ENV_VARIABLE
e seu valor. Clique em 'Salvar' na parte inferior da página. - Crie o arquivo appveyor.yml na raiz do seu projeto e cole o código abaixo.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
Este código instrui o AppVeyor a executar nossos testes usando o Node.js v12. Em seguida, instalamos nossas dependências de projeto com o comando yarn
. test_script
especifica o comando para executar nosso teste. A última linha diz ao AppVeyor para não criar uma pasta de compilação.
Clique na guia Settings
. Na navegação à esquerda, clique em emblemas. Copie o código de remarcação e cole-o no arquivo README.md .
Confirme seu código e envie para o GitHub. Se você fez tudo conforme as instruções, todos os testes devem passar e você deve ver seus novos emblemas brilhantes, conforme mostrado abaixo. Verifique novamente se você definiu as variáveis de ambiente no Travis e AppVeyor.

Agora é um bom momento para confirmar nossas alterações.
- A ramificação correspondente no meu repositório é 05-ci.
Adicionando um controlador
Atualmente, estamos processando a solicitação GET
para a URL raiz, /v1
, dentro de src/routes/index.js . Isso funciona como esperado e não há nada de errado com isso. No entanto, à medida que seu aplicativo cresce, você deseja manter as coisas organizadas. Você quer que as preocupações sejam separadas — você quer uma separação clara entre o código que trata da solicitação e o código que gera a resposta que será enviada de volta ao cliente. Para conseguir isso, escrevemos controllers
. Os controladores são simplesmente funções que lidam com solicitações provenientes de uma URL específica.
Para começar, crie uma pasta controllers/
dentro da pasta src/
. Os controllers
internos criam dois arquivos: index.js e home.js . Nós exportaríamos nossas funções de dentro do index.js . Você pode nomear home.js como quiser, mas normalmente deseja nomear os controladores de acordo com o que eles controlam. Por exemplo, você pode ter um arquivo usersController.js para armazenar todas as funções relacionadas aos usuários em seu aplicativo.
Abra src/controllers/home.js e digite o código abaixo:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
Você notará que apenas movemos a função que trata da requisição para a rota /
.
Abra src/controllers/index.js e digite o código abaixo.
// export everything from home.js export * from './home';
Exportamos tudo do arquivo home.js. Isso nos permite encurtar nossas instruções de importação para import { indexPage } from '../controllers';
Abra src/routes/index.js e substitua o código por este abaixo:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
A única mudança aqui é que fornecemos uma função para lidar com a solicitação para a rota /
.
Você acabou de escrever com sucesso seu primeiro controlador. A partir daqui é uma questão de adicionar mais arquivos e funções conforme necessário.
Vá em frente e brinque com o aplicativo adicionando mais algumas rotas e controladores. Você pode adicionar uma rota e um controlador para a página sobre. Lembre-se de atualizar seu teste, no entanto.
Execute yarn test
para confirmar que não quebramos nada. Seu teste passa? Isso é legal.
Este é um bom ponto para comprometer nossas mudanças.
- A ramificação correspondente no meu repositório é 06-controllers.
Conectando o banco de dados PostgreSQL
e escrevendo um modelo
Nosso controlador atualmente retorna mensagens de texto codificadas. Em um aplicativo do mundo real, muitas vezes precisamos armazenar e recuperar informações de um banco de dados. Nesta seção, conectaremos nosso aplicativo a um banco de dados PostgreSQL.
Vamos implementar o armazenamento e recuperação de mensagens de texto simples usando um banco de dados. Temos duas opções para configurar um banco de dados: podemos provisionar um de um servidor em nuvem ou podemos configurar nosso próprio banco de dados localmente.
Eu recomendaria que você provisionasse um banco de dados de um servidor em nuvem. O ElephantSQL tem um plano gratuito que oferece 20 MB de armazenamento gratuito, o que é suficiente para este tutorial. Visit the site and click on Get a managed database today
. Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.

If you would rather set up a database locally, you should visit the PostgreSQL and PgAdmin sites for further instructions.
Once we have a database set up, we need to find a way to allow our Express app to communicate with our database. Node.js by default doesn't support reading and writing to PostgreSQL
database, so we'll be using an excellent library, appropriately named, node-postgres.
node-postgres
executes SQL
queries in node and returns the result as an object, from which we can grab items from the rows key.
Let's connect node-postgres
to our application.
# install node-postgres yarn add pg
Open settings.js and add the line below:
export const connectionString = process.env.CONNECTION_STRING;
Open your .env
file and add the CONNECTION_STRING
variable. This is the connection string we'll be using to establish a connection to our database. The general form of the connection string is shown below.
CONNECTION_STRING="postgresql://dbuser:dbpassword@localhost:5432/dbname"
If you're using elephantSQL you should copy the URL from the database details page.
Inside your /src
folder, create a new folder called models/
. Inside this folder, create two files:
- pool.js
- model.js
Open pools.js and paste the following code:
import { Pool } from 'pg'; import dotenv from 'dotenv'; import { connectionString } from '../settings'; dotenv.config(); export const pool = new Pool({ connectionString });
First, we import the Pool
and dotenv
from the pg
and dotenv
packages, and then import the settings we created for our postgres database before initializing dotenv
. We establish a connection to our database with the Pool
object. In node-postgres
, every query is executed by a client. A Pool is a collection of clients for communicating with the database.
To create the connection, the pool constructor takes a config object. You can read more about all the possible configurations here. It also accepts a single connection string, which I will use here.
Open model.js and paste the following code:
import { pool } from './pool'; class Model { constructor(table) { this.pool = pool; this.table = table; this.pool.on('error', (err, client) => `Error, ${err}, on idle client${client}`); } async select(columns, clause) { let query = `SELECT ${columns} FROM ${this.table}`; if (clause) query += clause; return this.pool.query(query); } } export default Model;
We create a model class whose constructor accepts the database table we wish to operate on. We'll be using a single pool for all our models.
We then create a select
method which we will use to retrieve items from our database. This method accepts the columns we want to retrieve and a clause, such as a WHERE
clause. It returns the result of the query, which is a Promise
. Remember we said earlier that every query is executed by a client, but here we execute the query with pool. This is because, when we use pool.query
, node-postgres
executes the query using the first available idle client.
The query you write is entirely up to you, provided it is a valid SQL
statement that can be executed by a Postgres engine.
The next step is to actually create an API endpoint to utilize our newly connected database. Before we do that, I'd like us to create some utility functions. The goal is for us to have a way to perform common database operations from the command line.
Create a folder, utils/
inside the src/
folder. Create three files inside this folder:
- queries.js
- queryFunctions.js
- runQuery.js
We're going to create functions to create a table in our database, insert seed data in the table, and to delete the table.
Abra queries.js e cole o seguinte código:
export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';
Neste arquivo, definimos três strings de consulta SQL. A primeira consulta exclui e recria a tabela de messages
. A segunda consulta insere duas linhas na tabela de messages
. Sinta-se à vontade para adicionar mais itens aqui. A última consulta descarta/exclui a tabela de messages
.
Abra queryFunctions.js e cole o seguinte código:
import { pool } from '../models/pool'; import { insertMessages, dropMessagesTable, createMessageTable, } from './queries'; export const executeQueryArray = async arr => new Promise(resolve => { const stop = arr.length; arr.forEach(async (q, index) => { await pool.query(q); if (index + 1 === stop) resolve(); }); }); export const dropTables = () => executeQueryArray([ dropMessagesTable ]); export const createTables = () => executeQueryArray([ createMessageTable ]); export const insertIntoTables = () => executeQueryArray([ insertMessages ]);
Aqui, criamos funções para executar as consultas que definimos anteriormente. Observe que a função executeQueryArray
executa uma matriz de consultas e espera que cada uma seja concluída dentro do loop. (Não faça tal coisa no código de produção). Então, só resolvemos a promessa depois de executar a última consulta da lista. A razão para usar uma matriz é que o número dessas consultas aumentará à medida que o número de tabelas em nosso banco de dados aumentar.
Abra runQuery.js e cole o seguinte código:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
É aqui que executamos as funções para criar a tabela e inserir as mensagens na tabela. Vamos adicionar um comando na seção de scripts
do nosso package.json para executar este arquivo.
"runQuery": "babel-node ./src/utils/runQuery"
Agora execute:
yarn runQuery
Se você inspecionar seu banco de dados, verá que a tabela de messages
foi criada e que as mensagens foram inseridas na tabela.
Se você estiver usando o ElephantSQL, na página de detalhes do banco de dados, clique em BROWSER
no menu de navegação à esquerda. Selecione a tabela de messages
e clique em Execute
. Você deve ver as mensagens do arquivo query.js .
Vamos criar um controller e uma rota para exibir as mensagens do nosso banco de dados.
Crie um novo arquivo de controlador src/controllers/messages.js e cole o seguinte código:
import Model from '../models/model'; const messagesModel = new Model('messages'); export const messagesPage = async (req, res) => { try { const data = await messagesModel.select('name, message'); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
Importamos nossa classe Model
e criamos uma nova instância desse modelo. Isso representa a tabela de messages
em nosso banco de dados. Em seguida, usamos o método select
do modelo para consultar nosso banco de dados. Os dados ( name
e message
) que recebemos são enviados como JSON na resposta.
Definimos o controlador messagesPage
como uma função async
. Como as consultas node-postgres
retornam uma promessa, await
o resultado dessa consulta. Se encontrarmos um erro durante a consulta, o pegamos e exibimos a pilha para o usuário. Você deve decidir como escolher lidar com o erro.
Adicione o endpoint get messages a src/routes/index.js e atualize a linha de importação.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
Visite https://localhost:3000/v1/messages e você deverá ver as mensagens exibidas conforme mostrado abaixo.

Agora, vamos atualizar nosso arquivo de teste. Ao fazer TDD, você geralmente escreve seus testes antes de implementar o código que faz o teste passar. Estou adotando a abordagem oposta aqui porque ainda estamos trabalhando na configuração do banco de dados.
Crie um novo arquivo, hooks.js na pasta test/
e digite o código abaixo:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
Quando nosso teste é iniciado, o Mocha encontra esse arquivo e o executa antes de executar qualquer arquivo de teste. Ele executa o gancho before
para criar o banco de dados e inserir alguns itens nele. Os arquivos de teste são executados depois disso. Uma vez que o teste é finalizado, o Mocha executa o hook after
no qual nós soltamos o banco de dados. Isso garante que cada vez que executarmos nossos testes, o façamos com registros limpos e novos em nosso banco de dados.
Crie um novo arquivo de teste test/messages.test.js e adicione o código abaixo:
import { expect, server, BASE_URL } from './setup'; describe('Messages', () => { it('get messages page', done => { server .get(`${BASE_URL}/messages`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('name'); expect(m).to.have.property('message'); }); done(); }); }); });
Afirmamos que o resultado da chamada para /messages
é um array. Para cada objeto de mensagem, afirmamos que ele possui a propriedade name
e message
.
A etapa final nesta seção é atualizar os arquivos CI.
Adicione as seguintes seções ao arquivo .travis.yml :
services: - postgresql addons: postgresql: "10" apt: packages: - postgresql-10 - postgresql-client-10 before_install: - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf - sudo /etc/init.d/postgresql restart
Isso instrui o Travis a ativar um banco de dados PostgreSQL 10 antes de executar nossos testes.
Adicione o comando para criar o banco de dados como a primeira entrada na seção before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Crie a variável de ambiente CONNECTION_STRING
no Travis e use o valor abaixo:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
Adicione as seguintes seções ao arquivo .appveyor.yml :
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
Adicione a variável de ambiente da cadeia de conexão ao appveyor. Use a linha abaixo:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
Agora confirme suas alterações e envie para o GitHub. Seus testes devem passar no Travis CI e no AppVeyor.
- A ramificação correspondente no meu repositório é 07-connect-postgres.
Nota : Espero que tudo funcione bem do seu lado, mas caso você esteja tendo problemas por algum motivo, você sempre pode verificar meu código no repositório!
Agora, vamos ver como podemos adicionar uma mensagem ao nosso banco de dados. Para esta etapa, precisaremos de uma maneira de enviar solicitações POST
para nossa URL. Estarei usando o Postman para enviar solicitações POST
.
Vamos seguir o caminho do TDD e atualizar nosso teste para refletir o que esperamos alcançar.
Abra test/message.test.js e adicione o caso de teste abaixo:
it('posts messages', done => { const data = { name: 'some name', message: 'new message' }; server .post(`${BASE_URL}/messages`) .send(data) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('id'); expect(m).to.have.property('name', data.name); expect(m).to.have.property('message', data.message); }); done(); }); });
Esse teste faz uma solicitação POST para o endpoint /v1/messages
e esperamos que uma matriz seja retornada. Também verificamos as propriedades id
, name
e message
no array.
Execute seus testes para ver que este caso falha. Vamos agora corrigi-lo.
Para enviar solicitações de postagem, usamos o método post do servidor. Também enviamos o nome e a mensagem que queremos inserir. Esperamos que a resposta seja uma matriz, com um id
de propriedade e as outras informações que compõem a consulta. O id
é a prova de que um registro foi inserido no banco de dados.
Abra src/models/model.js e adicione o método insert
:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
Este é o método que nos permite inserir mensagens no banco de dados. Após inserir o item, ele retorna o id
, name
e message
.
Abra src/controllers/messages.js e adicione o controlador abaixo:
export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
Desestruturamos o corpo da solicitação para obter o nome e a mensagem. Em seguida, usamos os valores para formar uma string de consulta SQL que executamos com o método insertWithReturn
do nosso modelo.
Adicione o endpoint POST
abaixo a /src/routes/index.js e atualize sua linha de importação.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
Execute seus testes para ver se eles passam.
Abra o Postman e envie uma solicitação POST
para o terminal de messages
. Se você acabou de executar seu teste, lembre-se de executar a yarn query
para recriar a tabela de messages
.
yarn query


Confirme suas alterações e envie para o GitHub. Seus testes devem passar no Travis e no AppVeyor. Sua cobertura de teste cairá alguns pontos, mas tudo bem.
- A ramificação correspondente no meu repositório é 08-post-to-db.
Middleware
Nossa discussão sobre o Express não estará completa sem falar sobre middleware. A documentação do Express descreve um middleware como:
“[...] funções que têm acesso ao objeto de solicitação (req
), ao objeto de resposta (res
) e à próxima função de middleware no ciclo de solicitação-resposta da aplicação. A próxima função de middleware é comumente denotada por uma variável chamadanext
.”
Um middleware pode executar qualquer número de funções, como autenticação, modificação do corpo da solicitação e assim por diante. Consulte a documentação do Express sobre o uso de middleware.
Vamos escrever um middleware simples que modifica o corpo da solicitação. Nosso middleware anexará a palavra SAYS:
à mensagem recebida antes que ela seja salva no banco de dados.
Antes de começarmos, vamos modificar nosso teste para refletir o que queremos alcançar.
Abra test/messages.test.js e modifique a última linha de espera no caso de teste da posts message
:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
Estamos afirmando que a string SAYS:
foi anexada à mensagem. Execute seus testes para garantir que este caso de teste falhe.
Agora, vamos escrever o código para fazer o teste passar.
Crie uma nova pasta middleware/
dentro da pasta src/
. Crie dois arquivos dentro desta pasta:
- middleware.js
- index.js
Digite o código abaixo em middleware.js :
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
Aqui, anexamos a string SAYS:
à mensagem no corpo da solicitação. Depois de fazer isso, devemos chamar a função next()
para passar a execução para a próxima função na cadeia solicitação-resposta. Todo middleware precisa chamar a next
função para passar a execução para o próximo middleware no ciclo de solicitação-resposta.
Digite o código abaixo em index.js :
# export everything from the middleware file export * from './middleware';
Isso exporta o middleware que temos no arquivo /middleware.js . Por enquanto, temos apenas o middleware modifyMessage
.
Abra src/routes/index.js e adicione o middleware à cadeia de solicitação-resposta de mensagem post.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
Podemos ver que a função modifyMessage
vem antes da função addMessage
. Invocamos a função addMessage
chamando next
no middleware modifyMessage
. Como um experimento, comente a linha next()
no meio modifyMessage
e observe a solicitação travar.
Abra o Postman e crie uma nova mensagem. Você deve ver a string anexada.

Este é um bom ponto para comprometer nossas mudanças.
- A ramificação correspondente no meu repositório é 09-middleware.
Tratamento de erros e middleware assíncrono
Erros são inevitáveis em qualquer aplicação. A tarefa antes do desenvolvedor é como lidar com erros da maneira mais graciosa possível.
Em expresso:
“ Error Handling refere-se a como o Express captura e processa erros que ocorrem de forma síncrona e assíncrona.
Se estivéssemos apenas escrevendo funções síncronas, talvez não tivéssemos que nos preocupar tanto com o tratamento de erros, pois o Express já faz um excelente trabalho ao lidar com eles. De acordo com os documentos:
“Erros que ocorrem em código síncrono dentro de manipuladores de rotas e middleware não requerem trabalho extra.”
Mas uma vez que começamos a escrever manipuladores e middleware de roteador assíncrono, temos que fazer algum tratamento de erros.
Nosso middleware modifyMessage
é uma função síncrona. Se ocorrer um erro nessa função, o Express irá lidar com isso perfeitamente. Vamos ver como lidamos com erros em middleware assíncrono.
Digamos que, antes de criar uma mensagem, queremos obter uma imagem da API Lorem Picsum usando este URL https://picsum.photos/id/0/info
. Esta é uma operação assíncrona que pode ter sucesso ou falhar, e isso apresenta um caso para nós lidarmos.
Comece instalando o Axios.
# install axios yarn add axios
Abra src/middleware/middleware.js e adicione a função abaixo:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
Nesta função async
, await
uma chamada para uma API (na verdade, não precisamos dos dados retornados) e depois chamamos a next
função na cadeia de solicitações. Se a solicitação falhar, capturamos o erro e o passamos para o next
. Depois que o Express vê esse erro, ele ignora todos os outros middlewares da cadeia. Se não chamarmos next(err)
, a requisição irá travar. Se apenas chamássemos next()
sem err
, a solicitação prosseguirá como se nada tivesse acontecido e o erro não será detectado.
Importe esta função e adicione-a à cadeia de middleware da rota de mensagens post:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
Abra src/app.js e adicione o código abaixo logo antes da linha export default app
.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
Este é o nosso manipulador de erros. De acordo com o documento de tratamento de erros do Express:
“[...] funções de tratamento de erros têm quatro argumentos em vez de três: (err, req, res, next)
.”
Observe que esse manipulador de erros deve vir por último, após cada chamada app.use()
. Quando encontramos um erro, retornamos o rastreamento de pilha com um código de status de 400
. Você pode fazer o que quiser com o erro. Você pode querer registrá-lo ou enviá-lo para algum lugar.
Este é um bom lugar para confirmar suas alterações.
- A ramificação correspondente no meu repositório é 10-async-middleware.
Implantar no Heroku
- Para começar, acesse https://www.heroku.com/ e faça login ou registre-se.
- Baixe e instale a CLI do Heroku aqui.
- Abra um terminal na pasta do projeto para executar o comando.
# login to heroku on command line heroku login
Isso abrirá uma janela do navegador e solicitará que você faça login na sua conta Heroku.
Faça login para conceder ao seu terminal acesso à sua conta Heroku e crie um novo aplicativo heroku executando:
#app name is up to you heroku create app-name
Isso criará o aplicativo no Heroku e retornará dois URLs.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
Copie a URL à direita e execute o comando abaixo. Observe que esta etapa é opcional, pois você pode descobrir que o Heroku já adicionou a URL remota.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
Abra um terminal lateral e execute o comando abaixo. Isso mostra o login do aplicativo em tempo real, conforme mostrado na imagem.
# see process logs heroku logs --tail

Execute os três comandos a seguir para definir as variáveis de ambiente necessárias:
heroku config:set TEST_ENV_VARIABLE="Environment variable is coming across." heroku config:set CONNECTION_STRING=your-db-connection-string-here. heroku config:set NPM_CONFIG_PRODUCTION=false
Lembre-se em nossos scripts, definimos:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
Para iniciar o aplicativo, ele precisa ser compilado para ES5 usando babel na etapa de prestart
-inicialização porque o babel existe apenas em nossas dependências de desenvolvimento. Temos que definir NPM_CONFIG_PRODUCTION
como false
para poder instalá-los também.
Para confirmar que tudo está configurado corretamente, execute o comando abaixo. Você também pode visitar a guia de settings
na página do aplicativo e clicar em Reveal Config Vars
.
# check configuration variables heroku config
Agora execute git push heroku
.
Para abrir o aplicativo, execute:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
Se, como eu, você estiver usando o mesmo banco de dados PostgresSQL para desenvolvimento e produção, poderá descobrir que cada vez que executa seus testes, o banco de dados é excluído. Para recriá-lo, você pode executar um dos seguintes comandos:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
Implantação Contínua (CD) com Travis
Vamos agora adicionar a implantação contínua (CD) para concluir o fluxo de CI/CD. Estaremos implantando do Travis após cada execução de teste bem-sucedida.
O primeiro passo é instalar o Travis CI. (Você pode encontrar as instruções de instalação aqui.) Após instalar com sucesso o Travis CI, faça o login executando o comando abaixo. (Observe que isso deve ser feito no repositório do seu projeto.)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
Se o seu projeto estiver hospedado no travis-ci.org, remova o sinalizador --pro
. Para obter um token do GitHub, visite a página de configurações do desenvolvedor da sua conta e gere um. Isso só se aplica se sua conta estiver protegida com 2FA.
Abra seu .travis.yml e adicione uma seção de implantação:
deploy: provider: heroku app: master: app-name
Aqui, especificamos que queremos implantar no Heroku. A subseção app especifica que queremos implantar o branch master
do nosso repositório no app-name
no Heroku. É possível implantar diferentes ramificações em diferentes aplicativos. Você pode ler mais sobre as opções disponíveis aqui.
Execute o comando abaixo para criptografar sua chave de API Heroku e adicioná-la à seção de implantação:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
Isso adicionará a subseção abaixo à seção de implantação.
api_key: secure: very-long-encrypted-api-key-string
Agora confirme suas alterações e envie para o GitHub enquanto monitora seus logs. Você verá a compilação acionada assim que o teste do Travis for concluído. Dessa forma, se tivéssemos um teste com falha, as alterações nunca seriam implantadas. Da mesma forma, se a compilação falhar, toda a execução de teste falhará. Isso conclui o fluxo de CI/CD.
- O ramo correspondente no meu repositório é 11-cd.
Conclusão
Se você chegou até aqui, eu digo: “Polegares para cima!” Neste tutorial, configuramos com sucesso um novo projeto Express. Seguimos em frente para configurar as dependências de desenvolvimento, bem como a Integração Contínua (CI). Em seguida, escrevemos funções assíncronas para lidar com solicitações para nossos endpoints de API — completadas com testes. Em seguida, analisamos brevemente o tratamento de erros. Por fim, implantamos nosso projeto no Heroku e configuramos a implantação contínua.
Agora você tem um modelo para seu próximo projeto de back-end. Fizemos apenas o suficiente para você começar, mas você deve continuar aprendendo a continuar. Certifique-se de verificar os documentos do Express.js também. Se você preferir usar o MongoDB
em vez do PostgreSQL
, tenho um modelo aqui que faz exatamente isso. Você pode verificá-lo para a configuração. Tem apenas alguns pontos de diferença.
Recursos
- “Criar back-end de API expresso com MongoDB”, Orji Chidi Matthew, GitHub
- “Um pequeno guia para conectar middleware”, Stephen Sugden
- “Modelo de API expresso”, GitHub
- “AppVeyor vs Travis CI,” StackShare
- “A CLI do Heroku,” Centro de Desenvolvimento do Heroku
- “Implantação Heroku,” Travis CI
- “Usando middleware,” Express.js
- “Tratamento de erros”, Express.js
- “Começando”, Mocha
-
nyc
York (GitHub) - ElephantSQL
- Carteiro
- Expressar
- Travis CI
- Clima de código
- PostgreSQL
- pgAdmin