Recentemente, eu quis criar e hospedar um servidor Node, e descobri que o Heroku é um excelente serviço de plataforma de nuvem que tem hospedagem gratuita de passatempo para Node e PostgreSQL, entre muitos outros idiomas e bancos de dados.
Este tutorial aborda a criação de uma API REST local com o Node usando um servidor Express e um banco de dados PostgreSQL. Também lista as instruções para implantar no Heroku.
Pré-requisitos
Este guia usa instruções de instalação para o macOS e supõe um conhecimento prévio de:
Objetivos
Vamos criar uma API REST simples e local em Node.js que é executada em um servidor Express e utiliza o PostgreSQL para um banco de dados. Então vamos implantá-lo para Heroku.
Eu também tenho algumas dicas de produção para validação e limitação de taxa.
Configurar banco de dados PostgreSQL
Estava indo para:
- Instalar o PostgreSQL
- Crie um usuário
- Crie um banco de dados, tabela e entrada para a tabela
Essa será uma solução muito rápida – se for a primeira vez que você usa o PostgreSQL ou o Express, recomendo ler Configurando uma API RESTful com Node.js e PostgreSQL .
Instale e inicie o PostgreSQL.
brew install postgresql
brew services start postgresql
Login para postgres
.
psql postgres
Crie um usuário e senha e dê a eles acesso ao banco de dados.
CREATE ROLE api_user WITH LOGIN PASSWORD 'password';
ALTER ROLE api_user CREATEDB;
Efetue logout do usuário root e efetue login no usuário recém-criado.
\q
psql -d postgres -U api_user
Crie um books_api
banco de dados e conecte-se a ele.
CREATE DATABASE books_api;
\c books_api
Criar uma books
tabela com ID
, author
e title
.
CREATE TABLE books (
ID SERIAL PRIMARY KEY,
author VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL
);
Insira uma entrada na nova tabela.
INSERT INTO books (author, title)
VALUES ('J.K. Rowling', 'Harry Potter')
Criar API expressa
A API Express configurará um servidor expresso e roteará para dois pontos de extremidade GET
e POST
.
Crie os seguintes arquivos:
.env
– arquivo contendo variáveis de ambiente (não é controlado por versão)package.json
– informações sobre o projeto e dependênciasinit.sql
– arquivo para inicializar a tabela do PostgreSQLconfig.js
– criará a conexão do banco de dadosindex.js
– o servidor expresso
touch .env package.json init.sql config.js index.js
Variáveis ambientais
Defina seu nome de banco de dados, senha, host, porta e banco de dados..env
DB_USER=api_user
DB_PASSWORD=password
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=books_api
Inicialização do banco de dados
Crie um arquivo para inicializar a tabela com uma entrada. Nós vamos usar isso para o banco de dados Heroku.init.sql
CREATE TABLE books (
ID SERIAL PRIMARY KEY,
author VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL
);
INSERT INTO books (author, title)
VALUES ('J.K. Rowling', 'Harry Potter')
Configure a conexão do PostgreSQL
Use o pacote node-postgres para criar um Pool , que será usado para fazer consultas ao banco de dados.
Crie uma string de conexão que siga o padrão de postgresql://USER:PASSWORD@HOST:PORT/DATABASE
. Vou usar as variáveis de ambiente do .env
uso process.env.VARIABLE
. Inicializando com require('dotenv').config()
permitirá que você use essas variáveis de ambiente.
Eu também criei uma inProduction
string – em um ambiente como o Heroku, NODE_ENV
será configurado para production
que você possa ter um comportamento diferente entre os ambientes. Heroku nos fornecerá uma string chamada DATABASE_URL
connectionString, então não teremos que construir uma nova.config.js
require('dotenv').config()
const { Pool } = require('pg')
const isProduction = process.env.NODE_ENV === 'production'
const connectionString = `postgresql://${process.env.DB_USER}:${process.env.DB_PASS}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_DATABASE}`
const pool = new Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: isProduction,
})
Configure o servidor expresso. Configurar uma API RESTful com Node.js e PostgreSQL vai entrar em muito mais detalhes sobre este processo, e ir através da criação de todos os endpoints CRUD mais importantes – GET
, POST
, PUT
, e DELETE
. Eu propositadamente fiz este exemplo muito simples apenas para obter um produto viável mínimo em funcionamento.
Configurar o servidor expresso
index.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const { pool } = require('./config')
const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors())
const getBooks = (request, response) => {
pool.query('SELECT * FROM books', (error, results) => {
if (error) {
throw error
}
response.status(200).json(results.rows)
})
}
const addBook = (request, response) => {
const { author, title } = request.body
pool.query('INSERT INTO books (author, title) VALUES ($1, $2)', [author, title], error => {
if (error) {
throw error
}
response.status(201).json({ status: 'success', message: 'Book added.' })
})
}
app
.route('/books')
// GET endpoint
.get(getBooks)
// POST endpoint
.post(addBook)
// Start server
app.listen(process.env.PORT || 3002, () => {
console.log(`Server listening`)
})
Dependências
O package.json
arquivo listará suas dependências / devDependencies e outras informações.
- express – estrutura do servidor da web
- pg – cliente PostgreSQL para Node
- dotenv – permite que você carregue variáveis de ambiente do
.env
arquivo - cors – ativar o CORS
Também instalaremos o nodemon para desenvolvimento, que reinicia automaticamente o servidor toda vez que você fizer uma alteração.
Não esqueça de incluir a engines
propriedade para a versão do Node.package.json
{
"name": "books-api",
"version": "1.0.0",
"private": true,
"description": "Books API",
"main": "index.js",
"engines": {
"node": "11.x"
},
"scripts": {
"start": "node index.js",
"start:dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
Agora você só precisa instalar todas as dependências.
npm i cors dotenv express pg
npm i -D nodemon
Tudo está configurado, assim você pode executar npm start
, iniciar o servidor uma vez ou npm run start:dev
reiniciar o servidor após cada alteração.
npm start
Você pode testar a API fazendo uma ligação para o Postman:
Ou usando cURL.
curl http://localhost:3002/books
# [{"id":1,"author":"J.K. Rowling","title":"Harry Potter"}]
Não sabe usar o carteiro ou o cURL? Leia Fazendo solicitações de API com o Postman ou o cURL .
Implante o aplicativo no Heroku
Agora, temos tudo funcionando localmente, para que possamos criar uma conta Heroku, instalar o Heroku CLI e configurar o servidor de aplicativos e o servidor de banco de dados.
Configurar o Heroku CLI e o aplicativo
Vá para Heroku e crie uma conta.
Instale o Heroku CLI.
brew install heroku/brew/heroku
Entre para o Heroku CLI. Isso abrirá uma janela do navegador, que você pode usar para efetuar login.
heroku login
Criar aplicativo
# this can be whatever you want, but must be unique
heroku create example-node-api
Creating app... done, ⬢ example-node-api
https://<example-node-api>.herokuapp.com/ | https://git.heroku.com/<example-node-api>.git
Se você não passar um nome, ele criará um nome aleatório para você.
heroku create # generates random name
Configurar o Heroku Postgres
Vá até Heroku Add-ons e selecione Heroku Postgres . Clique em “Instalar o Heroku Postgres”. Clique em “Aplicar ao aplicativo”.
Pode levar até 5 minutos para se propagar. Depois que o tempo passar, verifique se o seu add-on existe via Heroku CLI.
heroku addons
example-node-api postgresql-whatever-00000 heroku-postgresql:hobby-dev free created
Você verá sua nova instância do PostgreSQL como algum nome gerado automaticamente postgresql-whatever-00000
.
Faça o login na instância do Heroku PostgreSQL.
heroku pg:psql postgresql-whatever-00000 --app example-node-api
A partir da raiz do projeto onde você está init.sql
, execute o seguinte comando para criar sua tabela e entradas no Heroku Postgres.
cat init.sql | heroku pg:psql postgresql-whatever-00000 --app example-node-api
Teste e implante
Neste ponto, tudo deve ser configurado e pronto para ir para Heroku. Você pode testar isso executando o seguinte comando:
heroku local web
Com isso, você pode http://localhost:5000/books
ver como será seu aplicativo no Heroku.
Se tudo parece bom, adicione, envie e empurre para Heroku.
git add .
git commit -m "init"
git push heroku master
-----> Launching... done
http://<example-node-api>.herokuapp.com deployed to Heroku
Dicas de Produção
Vou listar algumas dicas para ajudar a tornar sua API do Node um pouco mais segura e eficiente na produção.
- capacete – cabeçalhos HTTP seguros em um aplicativo Express
- compactação – middleware de compactação
- express-rate-limit – limite solicitações repetidas para endpoints
- express-validator – validadores de strings e santizers
Vamos trazer todas as novas dependências.index.js
const helmet = require('helmet')
const compression = require('compression')
const rateLimit = require('express-rate-limit')
const { body, check } = require('express-validator')
Cabeçalhos HTTP e compactação
helmet
e compression
não requer configuração adicional – eles apenas adicionarão alguma segurança útil e segurança de cabeçalho HTTP.
const app = express()
...
app.use(compression())
app.use(helmet())
Protegendo o CORS
Vamos adicionar algumas opções à biblioteca de cors . Quando usamos cors()
, disponibilizamos o aplicativo para uso em qualquer navegador. Isso é bom para o desenvolvimento, porque estaremos usando localhost
, mas na produção, só queremos que nosso aplicativo seja acessado por meio de nosso próprio domínio.index.js
const isProduction = process.env.NODE_ENV === 'production'
const origin = {
origin: isProduction ? 'https://www.example.com' : '*',
}
app.use(cors(origin))
Não que a proteção CORS se aplique somente aos navegadores – ela não protege seu aplicativo de ser acessado por meio de cURL e Postman.
Limitação de taxa
Para ajudar a proteger contra ataques de força bruta / DDoS, podemos limitar a quantidade de solicitações usando o limite de taxa expressa . O windowMs
determina a quantidade de tempo e max
determina quantas solicitações. Aqui eu posso dizer que qualquer cliente só pode acessar qualquer endpoint 5 vezes em 1 minuto.index.js
const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 5, // 5 requests,
})
app.use(limiter)
Com app.use()
isso, ele será aplicado a todos os endpoints, mas também podemos tornar certos endpoints mais restritos a outro rateLimit
.
const postLimiter = rateLimit({
windowMs: 1 * 60 * 1000
max: 1,
})
app.post('/books', postLimiter, addBook)
Validação
Se alguém conseguir enviar dados inválidos para o banco de dados PostgreSQL, o aplicativo poderá travar – por exemplo, se mais de 255 caracteres forem enviados para uma VARCHAR(255)
entrada do banco de dados. Podemos usar o express-validator para garantir que qualquer solicitação recebida seja válida, caso contrário, exibir um erro.
app.post(
'/books',
[
check('author')
.not()
.isEmpty()
.isLength({ min: 5, max: 255 })
.trim(),
check('title')
.not()
.isEmpty()
.isLength({ min: 5, max: 255 })
.trim(),
],
postLimiter,
(request, response) => {
const errors = validationResult(request)
if (!errors.isEmpty()) {
return response.status(422).json({ errors: errors.array() })
}
const { author, title } = request.body
pool.query('INSERT INTO books (author, title) VALUES ($1, $2)', [author, title], error => {
if (error) {
throw error
}
response.status(201).json({ status: 'success', message: 'Book added.' })
})
}
)
Chave API
Para proteger seu aplicativo, convém usar JSON Web Tokens (JWTs) para criar um token de acesso / atualização que você enviaria como um cabeçalho para a API. A implementação de JWTs é um pouco mais de um processo envolvido, mas a maneira mais simples de restringir o tráfego a um terminal é usar uma chave de API. Isso pode ser tão simples quanto um par chave / valor de cabeçalho e valor.
Em Heroku, podemos definir uma variável de ambiente com heroku config:set
.
heroku config:set API_KEY=hunter2
Se a solicitação para o nó de extremidade não contiver o cabeçalho adequado, ele poderá retornar um erro não autorizado.
const deleteBook = (request, response) => {
if (!request.header('apiKey') || request.header('apiKey') !== process.env.API_KEY) {
return response.status(401).json({ status: 'error', message: 'Unauthorized.' })
}
// ...
}
Com esse código, seria necessário definir cabeçalhos com a solicitação por meio de Postman ou cURL.
curl -X DELETE \
https://<example-node-api>.herokuapp.com/books/1 \
-H 'Content-Type: application/json' \
-H 'ApiKey: hunter2'
Usando a API no front end
Se você não estiver familiarizado com a forma de trabalhar com uma API no front end, leia Como se conectar a uma API com JavaScript . Como uma revisão rápida, veja como você pode usar a API de busca integrada para fazer GET
e as POST
solicitações para os pontos de extremidade.
Pegue
try {
const response = await fetch('https://<example-node-api>.herokuapp.com/books')
const books = await response.json()
console.log(books)
} catch (error) {
console.log(error)
}
Postar
const newBook = {
title: 'Game of Thrones',
author: 'George R. R. Martin',
}
try {
const response = await fetch('https://<example-node-api>.herokuapp.com/books', {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'post',
body: JSON.stringify(newBook),
})
} catch (error) {
console.log(error)
}
Conclusão
Estas são todas as etapas necessárias para configurar um servidor de API REST do Node, Express e PostgreSQL muito básico e implementá-lo no Heroku. Se alguma coisa sobre o aplicativo não faz sentido, eu recomendo a leitura, eu recomendo a leitura Configurando uma API RESTful com Node.js e PostgreSQL