Para criar um servidor express.js para servir de API:
npx express-generator --no-view nome-do-projeto
Para criar um servidor express.js a servir páginas HTML com Pug:
npx express-generator --view=pug nome-do-projeto
Para adicionar uma base de dados mongo precisamos de criar e rodar um docker container com o dataset fornecido e adaptar o servidor express.js.
Para criar e rodar o container basta correr o script setup-container.py
deste gist:
python3 setup-container.py db-name collection-name json-file-path
Atenção: O formato do JSON do dataset fornecido ao script deve ser uma lista de objetos e os id's devem ter o formato de
_id
.
Depois basta adicionar as seguintes linhas de código ao app.js
e alterar o url de db-name
para o nome passado ao scritp de setup do container:
var mongoose = require("mongoose");
var mongoDB = "mongodb://127.0.0.1/db-name";
mongoose.connect(mongoDB);
var db = mongoose.connection;
db.on("error", console.error.bind(console, "Erro de conexão ao MongoDB"));
db.once("open", () => {
console.log("Conexão ao MongoDB realizada com sucesso");
});
Para organizar os modelos e controladores, tal como ensinado nas aulas, basta criar as pastas /models
e /controllers
.
O exemplo de um model será tipo isto:
const mongoose = require('mongoose');
const compositorSchema = new mongoose.Schema({
id: String,
nome: String,
bio: String,
periodo: String,
dataNasc: String,
dataObito: String,
}, { versionKey: false });
module.exports = mongoose.model('compositores', compositorSchema);
E o respetivo controlador:
const Compositor = require('../models/compositor');
module.exports.list = async () => {
return await Compositor
.find()
.exec();
}
module.exports.findById = id => {
return Compositor
.findOne({ id: id })
.exec();
}
module.exports.findByPeriodo = periodo => {
return Compositor
.find({ periodo: periodo })
.exec();
}
module.exports.insert = compositor => {
return Compositor.create(compositor);
}
module.exports.removeById = id => {
return Compositor.deleteOne({ id: id });
}
module.exports.update = (id, compositor) => {
return Compositor.updateOne({ id: id }, compositor);
}
Caso queiramos ter um forms com capacidade de submissão de ficheiros, temos de seguir os seguintes passos:
1. Criar uma pasta para guardar os ficheiros submetidos
Para armazenar os ficheiros enviados temporariamente, criaremos uma pasta chamada /uploads
na raiz do nosso projeto.
Posteriormente, para guardar os ficheiros de forma permanente, criaremos uma pasta chamada /public/filestore
.
2. Configurar package multer
Este pedaço de código deve ser adicionado no início do router e configura a utilização do Multer para o upload de ficheiros.
O Multer é uma biblioteca Node.js usada para manipular multipart/form-data
, o que é essencial para realizar uploads de ficheiros em aplicações web.
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
Na segunda linha desta configuração, está-se a criar uma instância do Multer que especifica a diretoria onde os ficheiro enviados devem ser armazenados temporariamente antes de serem movidos para o destino final.
3. Criar uma rota capaz de receber esse ficheiro
O seguinte código é uma rota de submissão de ficheiros, configurada para receber um ficheiro enviado pelo cliente, movê-lo para uma localização específica no servidor, e atualizar um arquivo JSON que mantém um registo dos ficheiros submetidos.
/* File submission */
router.post('/upload', upload.single('file'), function (req, res, next) {
let form = req.body;
let file = req.file;
console.log(form);
console.log(file);
// Move file to another folder
var oldPath = __dirname + '/../' + file.path;
var newPath = __dirname + '/../public/filestore/' + file.originalname;
fs.rename(oldPath, newPath, function (err) {
if (err) {
res.render('error', { error: err });
return;
}
res.redirect("/");
});
});
- O
upload.single('myFile')
é um middleware fornecido pelo Multer que processa o ficheiro enviado no campo myFile do formulário. Ele anexa a informação do ficheiro processado ao objetoreq.file
. - O
var oldPath = __dirname + '/../' + file.path;
define o caminho atual do ficheiro temporário. - O
var newPath = __dirname + '/../public/fileStore/' + file.originalname;
define o novo caminho onde o ficheiro será movido. - O
fs.rename(oldPath, newPath, function (err) { ... });
usa a função rename do módulo fs para mover o ficheiro do caminho temporário para o novo caminho. Se houver um erro durante este processo, uma página de erro é renderizada.
3. Front-end com forms de submissão de ficheiros
Por fim, temos apenas de configurar o código HTML para suportar a submissão de ficheiros:
.w3-card-4
header.w3-indigo
h1 File Upload
form.w3-container(action="/files", method="POST", enctype="multipart/form-data")
label.w3-text-teal
b Select file
input.w3-input.w3-border.w3-light-grey(type="file", name="myFile")
label.w3-text-teal
b Description
input.w3-input.w3-border.w3-light-grey(type="text", name="desc")
input.w3-btn.w3-blue-grey(type="submit", value="Send")
Atenção: O
enctype
do forms deve sermultipart/form-data
de modo a suportar ficheiros e o nome do ficheiro deve ter o mesmo nome usado na rota ("myFile").
Para utilizar uma API com auth wall é necessário fazer pedidos para registar/loggar o utilizador e posteriormente utilizar a cookie fornecida pelo servidor para fazer pedidos à API.
Primeiro é preciso criar uma rota para processar o pedido de login:
router.post('/login', function(req, res, next) {
axios.post('http://localhost:2204/users/login', req.body)
.then(resposta => {
res.cookie('token', resposta.data.token);
res.redirect('/');
})
.catch(erro => {
res.render('error', { error: erro, message: "Credenciais inválidas" });
});
});
Neste exemplo, está a ser enviando um pedido POST para '/login' com as credenciais do utilizador. Se as credenciais forem válidas, o servidor enviará de volta um token de autenticação no corpo da resposta. Este token é então armazenado em um cookie chamado 'token'.
Agora que o utilizador está autenticado e tem um token válido armazenado em um cookie, podemos usá-lo para fazer pedidos à API protegida. Por exemplo, aqui está como se pode fazer um pedido de dados protegidos pela autenticação:
router.get('/ucs', function(req, res, next) {
var token = '';
if (req.cookies && req.cookies.token) {
token = req.cookies.token;
axios.get('http://localhost:2204/ucs?token=' + token)
.then(resposta => {
res.render('uc', { lista: resposta.data });
})
.catch(erro => {
res.render('error', { error: erro, message: "Erro ao obter lista de UCs" });
});
}
});
Neste exemplo, verificamos se o cookie 'token' está presente na requisição. Se estiver, recuperamos o token e o anexamos ao pedido GET para '/ucs'. Se o pedido for bem-sucedido, renderizamos a página com a lista de UCs.