Um pool de conexões visa evitar overhead de abertura de conexões ao reutilizá-las quando se tornam ociosas.
mysql2
(para versões MySQL > 5)sequelize
Usando 1 conexão:
import mysql from 'mysql2/promise'
const db = mysql.createConnection({
host: 'localhost',
database: 'nome-do-db',
user: 'me',
password: 'secret',
port: 3306
// configuração das conexões
multipleStatements: true,
})
Usando um pool de conexões: ✅
import mysql from 'mysql2/promise'
const db = mysql.createPool({
host: 'localhost',
database: 'nome-do-db',
user: 'me',
password: 'secret',
port: 3306
// configuração das conexões
multipleStatements: true,
// configuração da pool
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
// chamada assíncrona com await para FAZER UMA CONSULTA
// para conseguirmos non-blocking I/O - woot woot
const [result] = await db.execute('SELECT * FROM meals') // db.query(...) ou db.execute(...) <-- melhor
console.log(`First meal is: ${result[0].mealName}`)
As consultas são feitas por meio do método:
// só faz consulta
db.query(query)
// consulta e armazena plano de execução ✅
db.execute(query)
query
é uma string
contendo uma consulta SQLresult
, um vetor em que cada elemento é um registro do resultado da
consulta (para SELECT
s)fields
, contém metadados adicionais dos resultadosdb.execute(...)
faz a consulta e armazena o plano de execução para agilizar próximas solicitações dessa mesma consultaSELECT
:
const [result, fields] = await db.execute('SELECT name, time FROM meals')
result.forEach(meal => console.log(`${meal.time} - ${meal.name}`)
SELECT
, vejamos também INSERT
, DELETE
e UPDATE
…db.execute(query, params)
, mas há duas diferenças:
result.affectedRows
tem
o número de registros afetados na operação e result.insertId
o código de
auto-incremento do registro (se houver)Exemplo de INSERT
:
const nomeDaRefeicao = 'Tea'
const [result] = await db.execute('INSERT INTO food (id, name) VALUES (NULL, ?)', [nomeDaRefeicao])
console.log(`Comidas inseridas: ${result.affectedRows} com id ${result.insertId}`)
Daria pra concatenar o valor de parâmetro na string de consulta (em vez do ?
),
mas deve-se tomar cuidado com ataque SQL-injection. Faça como mostrado, ou concatene
usando db.escape(nomeDaRefeicao)
.
const [result] = await db.execute('DELETE FROM food WHERE id=?', [foodId])
console.log(`Comidas excluídas ${result.affectedRows}`)
DELETE FROM food WHERE id=${foodId}
)callback
possui result.changedRows
com o número de
registros alterados.const [result] = await db.execute(`
UPDATE food
SET name="bad"
WHERE name LIKE '%?%'`, ['elvish'])
console.log(`Comidas alteradas: ${result.changedRows}')
mysql2/promises
devemos proteger as chamadas de db.query/execute
:
try/catch
, se usando async/await
.catch(...)
ou 2º argumento de .then
, se promessas ⬇️try {
const [result] = await db.execute('...', [...])
// faz coisas com result
} catch (erro) {
console.error(erro)
erro.mensagemAmigavel = 'Erro ao ...'
throw erro
}
db.execute('...', [...])
.then(([result]) => {
// faz coisas com result
})
.catch(erro => {
console.error(erro)
erro.mensagemAmigavel = 'Erro...'
throw erro
})
const transaction = await db.getConnection()
try {
await transaction.beginTransaction()
await transaction.query('SET FOREIGN_KEY_CHECKS = 0;')
await transaction.query('TRUNCATE TABLE `zombies`.`person`;')
await transaction.query('TRUNCATE TABLE `zombies`.`zombie`;')
await transaction.query('SET FOREIGN_KEY_CHECKS = 1;')
await transaction.query("INSERT INTO `zombies`.`zombie` ...")
await transaction.query("INSERT INTO `zombies`.`person` ...")
await transaction.commit()
} catch (error) {
await transation.rollback()
} finally {
await transaction.release()
}
<form>
GET
/POST
<form>...</form>
<form action="cadastrar-usuario.php"> <!-- que "página" receberá os dados -->
<label>Nome: <input name="nome" type="text"></label>
<label>E-mail: <input name="email" type="email"></label>
<label>Senha: <input name="senha" type="password"></label>
<button type="submit">Enviar</button> <!-- veja no próximo -->
<button type="reset">Limpar</button> <!-- slide -->
</form>
<form action="/" method="GET" enctype="application/x-www-form-urlencoded">
...
</form>
<form>
é submetido, o navegador envia requisição
do tipo method="TIPO"
para action="ENDERECO"
contendo
todos os campos preenchidos
<input>
, <select>
, <textarea>
action
~ URL para onde os dados serão enviados
method
~ GET
: dados são colocados na querystring da URL
~ POST
: dados enviados no corpo da requisição
enctype
~ application/x-www-form-urlencoded
codifica dados “URL-friendly”
~ multipart/form-data
para upload de arquivos
O que é terrível, verde, come pedras e mora debaixo da terra??
Conheça o Incrível Monstro Verde</style> que Come Pedras e Mora Debaixo da Terra
O terrível monstro verde (etc. etc.) está com fome e você deve dar comida para ele. Ele acaba de ir para a superfície e para que ele não comece a comer pessoas, você deve dar a ele seu segundo alimento preferido: pedras.
Para isso, você deve ir até onde ele está e enviar algumas pedras para ele. Atualmente, ele está neste endereço: https://terrivel.cyclic.app/monster. Para dar comida a ele, você deve encomendá-las a partir de um formulário html.
GET
e com POST
::: figure . height: 90%; width: 270px;
Grota do monstro
:::
<form action="https://terrivel.cyclic.app/monster" method="GET">
<input name="nome" type="text">
<input name="num_pedras" type="number" step="1" min="0">
<input name="corCeu1" id="corCeu1" type="color">
<select name="tipo_pedras">
<option value="marroada">Marroada</option>
<option value="ametista">Ametista</option>
</select>
<input name="tipo_pedras_sortidas" type="radio" value="não" checked>não
<input name="tipo_pedras_sortidas" type="radio" value="sim">sim
<button type="submit">alimentar monstro</button>
<button type="submit" formmethod="POST">alimentar monstro</button>
</form>
name
submit
podem alterar o <form>
:
formaction
formmethod
formenctype
Em uma aplicação Express:
// método GET
app.get('/monster', (req, res) => {
// pega valores da querystring
// via req.query.NAME
//
res.render('monster', {
nome: req.query.nome,
num_pedras: req.query.num_pedras
corCeu1: req.query.corCeu1,
tipo_pedras: req.query.tipo_pedras,
/*...*/
})
})
// método POST
app.post('/monster', (req, res) => {
// pega valores do corpo da requisição
// via req.body.NAME
//
res.render('monster', {
nome: req.body.nome,
num_pedras: req.body.num_pedras
corCeu1: req.body.corCeu1,
tipo_pedras: req.body.tipo_pedras,
/*...*/
})
})
GET
e POST
GET /monster HTTP/1.1
Host: terrivel.cyclic.app?nome=Fl%C3%A1vio+Coutinho
&num_pedras=5&tam_pedras=3.5&corCeu1=%2384d6d7
&corCeu2=%233572e3&tipo_pedras=espinela
&tipo_pedras_sortidas=n%C3%A3o
GET /monster HTTP/1.1
Host: terrivel.cyclic.app
nome=Fl%C3%A1vio+Coutinho&num_pedras=5&tam_pedras=3.5
&corCeu1=%2384d6d7&corCeu2=%233572e3...
Característica | GET | POST |
---|---|---|
Visibilidade | Dados visíveis ao usuário | Dados “ocultos” |
Segurança | Menos seguro | Mais seguro |
Restrição de tamanho | Tamanho da URL (~2048) | Sem restrição |
Restrição de tipo de dados | Apenas ASCII | Sem restrição |
Botão voltar | Ok | Dados serão ressubmetidos |
Ad. aos favoritos | Ok | Não é possível |
Histórico do navegador | Parâmetros são salvos | Parâmetros não são salvos |
GET
e POST
DELETE
, PUT
e outrosmethod-override
: npm i method-override
app.use(methodOverride('_method', { methods: ['GET', 'POST'] }))
?_method=XXX
. Exemplos:
<a href="/people/?_method=DELETE" class="link-danger">Excluir</a>
<form action="/people/eaten?_method=PUT" method="POST">
...
</form>
GET
, POST
…)GET, DELETE, POST, PUT
etc.)API: conjunto de métodos públicos de um programa
API REST: conjunto de métodos públicos expostos por meio de um web service na arquitetura REST
.html, .json, .xml
)/zombies/
)/people/
)
jardim-zumbi.com/people/
GET /people/
)GET /zombies/
)GET /zombies/8
)POST /zombies/brains/
)DELETE /people/4
)app.get('/people/', async (req, res) => {
const [result] = await db.execute('SELECT * FROM person')
res.render('list-of-people', { pessoas: result })
})
app.get('/zombies/:id', async (req, res) => {
const id = db.escape(req.params.id) // pega o parâmetro "id" no caminho da rota (eg, 4 em /zombies/4)
try {
const [result] = await db.execute(`SELECT * FROM zombie WHERE id=${id}`)
if (result.length === 0) {
throw new Error(`Ninguém conhece um zumbi com id ${id}.`)
}
res.render('detail-of-zombie', { zumbi: result[0] })
} catch (error) {
res.send(404, "Zumbi inexistente")
}
})
app.delete('/zombies/:id', async (req, res) => {
const id = req.params.id
const [result] = await db.execute(`DELETE FROM zombie WHERE id=?`, id)
res.redirect('/zombies/') // ou 'back' para voltar à mesma URL de antes
})
Accept
Accept-Charset
Accept-Encoding
Accept-Language
A Star Wars API (https://swapi.dev) tem diferentes representações para um mesmo recurso:
Formato
~ HTML ou JSON
~ Via cabeçalho Accept: text/html
ou Accept: application/json
Língua
~ Inglês ou Wookie
~ Via parâmetro de query string format=wookiee
Testando usando o comando curl
:
$ curl -H "Accept: application/json" https://swapi.dev/api/people/1
app.verbo('caminho', async (req, res) => {
const [dados] = await db.execute(...)
const contexto = {
dados
}
res.format({ // <-- res.format recebe um objeto cujas props. são callbacks
html: () => res.render('view', contexto),
json: () => res.send(dados)
})
})
GET /entidade
~ Lista todos os membros da entidade
POST /entidade
~ Cria nova entidade
GET /entidade/:id
~ Detalhes da entidade com certo id
HEAD /entidade/:id
~ Apenas cabeçalhos da resposta
DELETE /entidade/:id
~ Exclui a entidade com certo id
PUT /entidade/:id
~ Atualiza campos da entidade com certo id
200 Ok
201 Created
204 No content
400 Bad Request
401 Unauthorized
404 Not Found
405 Method Not Allowed
GET /account/12345
:
HTTP/1.1 200 OK
{
"account": {
"account_number": 12345,
"balance": {
"currency": "usd",
"value": 100.00
},
"links": {
"deposits": "/accounts/12345/deposits",
"withdrawals": "/accounts/12345/withdrawals",
"transfers": "/accounts/12345/transfers",
"close": "/accounts/12345/close"
}
}
}
router/people.js
, que
descreve as rotas que começam com /people/
Router
s para representar cada parte do fluxo
// routes/zombies.js
import express from 'express'
const router = express.Router()
router.get('/', cb) // lista todos zumbis
router.post('/', cb) // cria novo zumbi
router.delete('/:id', cb) // exclui
router.put('/:id', cb) // atualiza
export default router
// app.js
import express from 'express'
import index from './routes/index.js'
import people from './routes/people.js'
import zombies from './routes/zombies.js'
const app = express()
// roteador é um middleware
app.use('/', index)
app.use('/people', people)
app.use('/zombies', zombies)
// ...
/zombies
(todas suas rotas o possuem)Exemplo: layout.hbs
:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<!-- inclui 2 partials -->
} <!-- "buraco"
do body -->
</body>
</html>
index.hbs
:
<main>
<h1>Título</h1>
<div>
<!-- ...-->
</div>
</main>
header.hbs
:
<header>
<img src="..." alt="..">
<nav>...</nav>
</header>
layout.hbs
é usado por padrãores.render('nome-da-view', { layout: 'nome-do-layout'})
…ou…
app.set('view options', { layout: 'nome-do-layout' }) // mesmo layout para todas views
import hbs from 'hbs'
const __dirname = new URL('.', import.meta.url).pathname
hbs.registerPartials(`${__dirname}/views/partials`, console.error)
import session from 'express-session'
import flash from 'connect-flash' // habilita req.flash(nome, valor)
app.use(session({ secret: 'lalala', resave: false, saveUninitialized: true }))
app.use(flash())
app.post('/zombies', (req, res) => {
// ...faz coisas...
req.flash('sucesso', 'Zumbi nasceu')
// req.flash('erro', 'Erro ao criar')
req.redirect('/zombies')
})
app.get('/zombies', (req, res) => {
// ...faz coisas...
res.render('zombie-list', {
zombies: [...],
sucesso: req.flash('sucesso'),
erro: req.flash('erro')
})
})