- Criação de uma web api para gerir escolas, cursos e alunos.
- Node JS
- Prisma + PostgreSQL
- Conhecer as Arquiteturas de Aplicações Web.
- Conhecer a Arquitetura de Desenvolvimento Model View Controller.
- Desenvolver aplicações Web, utilizando bibliotecas e frameworks de produtividade nas várias camadas do desenvolvimento.
- Produzir Aplicações Web adaptadas aos vários tipos de dispositivos.
- Saber disponibilizar e integrar informação a partir de APIs e/ou Web Services.
- Conhecer Soluções de desenvolvimento e publicação em modelos de computação na nuvem (cloud computing).
- Criar conta no GitHub;
- Criar um repositório – AW-P-EXEMPLO-{{número-de-aluno}};
- Instalar GitHub desktop e mapear o repositório;
- Instalar node;
- Instalar Postman;
- Abrir projeto mapeado com o VSCode;
-
Abrir terminal e executar:
npm init
-
Preencher os dados pedidos;
-
Instalar dependências:
npm install express nodemon cors dotenv morgan --save
-
Criar ficheiro .env (environment) na raiz do projeto:
SERVER_HOST=localhost SERVER_PORT=4242
-
Criar um ficheiro na raiz do projeto server.js;
-
Alterar no package.json "main": "server.js";
-
Adicionar script no package.json:
"start": "nodemon server.js“
-
package.json deverá ficar assim:
{ "name": "aw-p-exemplo-1778", "version": "1.0.0", "description": "Educational project", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js" }, "author": "Marcelo Antunes Fernandes", "license": "ISC", "dependencies": { "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "morgan": "^1.10.0", "nodemon": "^3.0.1" } }
-
Alterar server.js
require('dotenv').config(); const bodyParser = require('body-parser'); const cors = require('cors'); const express = require('express'); const router = require('../routes/index'); const app = express(); app.use(bodyParser.json()); app.use(cors()); app.use('/api/', router); const port = process.env.SERVER_PORT || 8080; app.listen(port, () => { console.log('Express server listening on port', port) });
-
Criar a seguinte estrutura de pastas e ficheiros:
📦AW-P-EXEMPLO-1778 ┣ 📂controllers ┃ ┃ ┗ 📜student.js ┣ 📂routes ┃ ┃ ┗ 📜index.js ┃ ┃ ┗ 📜student.js ┣ 📜.env ┣ 📜.gitignore ┣ 📜package-lock.json ┣ 📜package.json ┣ 📜readme.md ┗ 📜server.js
-
Alterar routes
- student.js
const studentRouter = require('express').Router(); const controller = require('../controllers/student'); //students CRUD studentRouter.get('/', controller.getAll); //read all studentRouter.get('/:number', controller.getById); //read one by his id (student number) studentRouter.post('/create', controller.create); //create new student studentRouter.put('/update', controller.update); //update student studentRouter.delete('/delete/:number', controller.delete); //delete student module.exports = studentRouter;
- index.js
const router = require('express').Router(); const studentRouter = require('./students'); router.use('/students', studentRouter); module.exports = router;
-
Alterar controller:
- student.js
const fs = require('fs'); //return all students exports.getAll = async (req, res) => { return res.send("ok"); } //return student by his id (student number) exports.getById = async (req, res) => { //get student id requested const id = req.params.number; //just return same id return res.send(id); } //creates student exports.create = async (req, res) => { //get requested student properties const {number, name, city, birthday } = req.body; //just return same new student return res.status(201).send(req.body); } //updates student exports.update = async (req, res) => { //get requested student properties const {number, name, city, birthday } = req.body; //just return same new student return res.send(req.body); } //delete student by his id (student number) exports.delete = async (req, res) => { //get student id requested const id = req.params.number; //just return ok return res.send("ok"); }
-
Executar
npm start
; -
Testar no Postman;
-
Push para o GIT;
-
Criar data -> local -> data.json
📦AW-P-EXEMPLO-1778 ┣ 📂controllers ┃ ┃ ┗ 📜student.js ┣ 📂data ┃ ┃ ┗ 📂local ┃ ┃ ┗ 📜data.json ┣ 📂routes ┃ ┃ ┗ 📜index.js ┃ ┃ ┗ 📜student.js ┣ 📜.env ┣ 📜.gitignore ┣ 📜package-lock.json ┣ 📜package.json ┣ 📜readme.md ┗ 📜server.js
-
Estrutura exemplo data.json
{ "students": [ { "number": "1778", "name": "Marcelo Filipe", "city": "Braga", "birthday": "17-09-1991" } ] }
-
Alterar controller:
- student.js
const fs = require('fs'); //return all students exports.getAll = async (req, res) => { //read local data json file const datajson = fs.readFileSync("data/local/data.json", "utf-8"); //parse to json const data = JSON.parse(datajson); //returns students array return res.send(data.students); } //return student by his id (student number) exports.getById = async (req, res) => { //get student id requested const id = req.params.number; //read local data json file const datajson = fs.readFileSync("data/local/data.json", "utf-8"); //parse to json const data = JSON.parse(datajson); //finds student by his id const student = data.students.filter(student => student.number == id); //return student res.send(student); } //creates student exports.create = async (req, res) => { //get requested student properties const {number, name, city, birthday } = req.body; //read local data json file const datajson = fs.readFileSync("data/local/data.json", "utf-8"); //parse to json const data = JSON.parse(datajson); //add to students array data.students.push(req.body); //add to students array fs.writeFileSync('data/local/data.json', JSON.stringify(data)); //return new student return res.status(201).send(req.body); } //updates student exports.update = async (req, res) => { const { number, name, city, birthday } = req.body; //read local data json file const datajson = fs.readFileSync("data/local/data.json", "utf-8"); //parse to json const data = JSON.parse(datajson); //find student to update const student = data.students.find(student => student.number == number); //update properties student.name = name; student.city = city; student.birthday = birthday; //update local database fs.writeFileSync('data/local/data.json', JSON.stringify(data)); //return updated student return res.send({number, name, city, birthday }); } //delete student by his id (student number) exports.delete = async (req, res) => { //get student id requested const id = req.params.number; //read local data json file const datajson = fs.readFileSync("data/local/data.json", "utf-8"); //parse to json const data = JSON.parse(datajson); //find student to delete const student = data.students.filter(student => student.number == id); //delete student data.students.splice(student, 1); //update local database fs.writeFileSync('data/local/data.json', JSON.stringify(data)); //return ok return res.status(200).send("ok"); }
-
Executar
npm start
; -
Testar no Postman;
-
Push para o GIT;
- Escolas:
- Identificador, nome, sigla, morada, website.
- Cursos:
- Identificador, nome, sigla e escola a que pertence.
- Alunos
- Identificador, nº de aluno, nome, morada, data de nascimento e curso a que pertence.
Não esquecer de validar:
-
Antes de retornar um registo verificar se:
- o registo efetivamente existe;
-
Antes de gravar verificar se:
- o registo efetivamente existe;
- todas os campos estão devidamente preenchidos;
- (no caso de create) se já existe um registo com o mesmo identitificador (número);
-
Antes de eliminar verificar se:
- o registo efetivamente existe;
-
Official website https://www.postgresql.org/
-
Download https://www.postgresql.org/download/windows/
- PgAdmin 4
- PWD superuser exemplo
postgres
-
Official website https://www.prisma.io/
-
Client reference https://www.prisma.io/docs/reference/api-reference/prisma-client-reference
-
CRUD https://www.prisma.io/docs/concepts/components/prisma-client/crud
-
Instalar PostgreSQL;
-
Instalar Prisma Extension no VSCode;
-
Instalar PRISMA
npm install -g prisma
-
Instalar PRISMA client
npm install @prisma/client
-
Versionar a API
- v1 -> dados locais
- v2 -> base de dados
- Estrutura de pastas e ficheiros:
📦AW-P-EXEMPLO-1778 ┣ 📂controllers ┃ ┣ 📂v1 ┃ ┃ ┗ 📜student.js ┃ ┗ 📂v2 ┃ ┃ ┗ 📜student.js ┣ 📂data ┃ ┣ 📂local ┃ ┃ ┗ 📜data.json ┃ ┗ 📂prisma ┣ 📂routes ┃ ┣ 📂v1 ┃ ┃ ┣ 📜index.js ┃ ┃ ┗ 📜students.js ┃ ┗ 📂v2 ┃ ┃ ┣ 📜index.js ┃ ┃ ┗ 📜students.js ┣ 📂slides ┃ ┗ 📜2 - API - Intro.pdf ┣ 📜.env ┣ 📜.example.env ┣ 📜.gitignore ┣ 📜AW-P-EXEMPLO-1778.postman_collection.json ┣ 📜package-lock.json ┣ 📜package.json ┣ 📜readme.md ┗ 📜server.js
-
Alterar no server.js o versionamento na API
require('dotenv').config(); const bodyParser = require('body-parser'); const cors = require('cors'); const express = require('express'); const router_v1 = require('./routes/v1/index'); const router_v2 = require('./routes/v2/index'); const app = express(); app.use(bodyParser.json()); app.use(cors()); app.use('/api/v1/', router_v1); app.use('/api/v2/', router_v2); const port = process.env.SERVER_PORT || 8080; app.listen(port, () => { console.log('Express server listening on port', port) });
-
Adicionar configuração do prisma no package.json
"prisma": { "schema": "data/prisma/schema.prisma" },
-
No terminal executar o seguinte comando para ser adicionado URL da BD no ficheiro .env
prisma init
- .env
SERVER_HOST=localhost SERVER_PORT=4242 DATABASE_URL="postgres://postgres:superuser@localhost:5432/AW-P-EXEMPLO-1778"
- .env
-
No terminar executar:
prisma generate
-
Alterar o schema.prisma
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Students { id Int @id @default(autoincrement()) number String @unique name String city String birthday String }
-
No terminal executar (para criar ou atualizar a configuração da BD)
prisma db push
-
Verificar no PgAdmin se a BD foi criada com sucesso
-
Criar as novas rotas routes -> v2 -> students
const studentRouter = require('express').Router(); const controller = require('../../controllers/v2/student'); //students CRUD studentRouter.get('/', controller.getAll); //read all studentRouter.get('/:number', controller.getById); //read one by his id (student number) studentRouter.post('/create', controller.create); //create new student studentRouter.put('/update', controller.update); //update student studentRouter.delete('/delete/:number', controller.delete); //delete student module.exports = studentRouter;
-
Criar o CRUD controllers -> v2 -> student
const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient() //return all students exports.getAll = async (req, res) => { try { //read all from database const response = await prisma.students.findMany(); res.status(200).json(response) } catch (error) { res.status(500).json({ msg: error.message }) } } //return student by his id (student number) exports.getById = async (req, res) => { //get student id requested const id = req.params.number; try { //finds student by his id (number) const response = await prisma.students.findUnique({ where: { number: id, }, }) //return student res.status(200).json(response) } catch (error) { res.status(404).json({ msg: error.message }) } } //creates student exports.create = async (req, res) => { //get requested student properties const { number, name, city, birthday } = req.body; try { //creates new student const student = await prisma.students.create({ data: { number: number, name: name, city: city, birthday: birthday }, }) //return student created res.status(201).json(student) } catch (error) { res.status(400).json({ msg: error.message }) } } //updates student exports.update = async (req, res) => { const { number, name, city, birthday } = req.body; try { //find student to update their data const student = await prisma.students.update({ where: { number: number, }, data: { name: name, city: city, birthday: birthday }, }) //return student updated res.status(200).json(student) } catch (error) { res.status(400).json({ msg: error.message }) } } //delete student by his id (student number) exports.delete = async (req, res) => { //get student number requested const number = req.params.number; try { //delete student await prisma.students.delete({ where: { number: number, }, }) //just return ok res.status(200).send("ok"); } catch (error) { res.status(400).json({ msg: error.message }) } }
-
Executar
npm start
; -
Testar no Postman;
-
Push para o GIT;
- Escolas:
- Identificador, nome, sigla, morada, website.
- Cursos:
- Identificador, nome, sigla e escola a que pertence.
- Alunos
- Identificador, nº de aluno, nome, morada, data de nascimento e curso a que pertence.
Não esquecer de validar:
-
Antes de retornar um registo verificar se:
- o registo efetivamente existe;
-
Antes de gravar verificar se:
- o registo efetivamente existe;
- todas os campos estão devidamente preenchidos;
- (no caso de create) se já existe um registo com o mesmo identitificador (número);
-
Antes de eliminar verificar se:
- o registo efetivamente existe;
- Authentication is the process of verifying a user’s identification through the acquisition of credentials and using those credentials to confirm the user’s identity.
- A autorização é o processo que permite aos utilizadores autenticados acederem a recursos, determinando se têm permissões de acesso ao sistema. Ao conceder ou negar licenças específicas a um utilizador autenticado, a autorização permite-lhe controlar os privilégios de acesso.
-
Os JSON Web Tokens (JWT) são um padrão aberto da indústria RFC 7519 para representar reivindicações entre duas partes.
-
Os JWTs são um objeto singular de três cadeias de caracteres concatenadas, separadas por um .
Um exemplo de JWT teria o seguinte aspeto: aaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccc
-
A primeira cadeia (aaa...a) é o cabeçalho que contém o algoritmo utilizado para o encriptar e o tipo de token que, obviamente, é o JWT. A segunda cadeia (bbb...b) é o payload que contém toda a carne. Pode conter o que quiser, mas geralmente inclui o ID do utilizador e o nome de utilizador, etc. A terceira cadeia (ccc...c) é a assinatura que contém um hash (encriptação) do cabeçalho e da carga útil. O hash é feito com uma chave secreta que é fornecida pelo programador.
-
Instalar bcrypt (https://www.npmjs.com/package/bcrypt)
npm install bcryptjs
-
Instalar jsonwebtoken (https://www.npmjs.com/package/jsonwebtoken)
npm install jsonwebtoken
-
Criar estrutura de pastas e ficheiros:
📦AW-P-EXEMPLO-1778 ┣ 📂controllers ┃ ┣ 📂v1 ┃ ┃ ┗ 📜student.js ┃ ┗ 📂v2 ┃ ┃ ┣ 📜auth.js (+) ┃ ┃ ┗ 📜student.js ┣ 📂data ┃ ┣ 📂local ┃ ┃ ┗ 📜data.json ┃ ┗ 📂prisma ┃ ┃ ┗ 📜schema.prisma ┣ 📂middlewares (+) ┃ ┗ 📜auth.js (+) ┣ 📂routes ┃ ┣ 📂v1 ┃ ┃ ┣ 📜index.js ┃ ┃ ┗ 📜students.js ┃ ┗ 📂v2 ┃ ┃ ┣ 📜auth.js (+) ┃ ┃ ┣ 📜index.js ┃ ┃ ┗ 📜students.js ┣ 📂slides ┃ ┗ 📜2 - API - Intro.pdf ┣ 📂utils (+) ┃ ┗ 📜authenticate.js (+) ┣ 📜.env ┣ 📜.example.env ┣ 📜.gitignore ┣ 📜AW-P-EXEMPLO-1778.postman_collection.json ┣ 📜package-lock.json ┣ 📜package.json ┣ 📜readme.md ┗ 📜server.js
-
Criar estrutura tabela utilizadores no prisma.schema
model Users{ id String @id @default(uuid()) name String @db.VarChar(255) email String @unique password String isAdmin Boolean @default(false) }
-
Executar no terminal e verificar se a tabela foi efetivamente criada no PgAdmin:
prisma db push
-
Adioncar hash ao ficheiro .env
JWT_SECRET=sdfksADDFGMLKCZdjfl34ksdfdb323
-
Criar as rotas de signin e signup: routes -> v2 -> auth.js
const authRouter = require('express').Router(); const controller = require('../../controllers/v2/auth'); authRouter.post('/signin', controller.signin); authRouter.post('/signup', controller.signup); module.exports = authRouter;
-
Adicionar as novas rotas ao routes -> v2 -> index
const router = require('express').Router(); const authRouter = require('./auth'); const studentRouter = require('./students'); router.use('/auth', authRouter); router.use('/students', studentRouter); module.exports = router;
-
Criar utils para gerar o token e validar: utils -> authenticate
const jwt = require('jsonwebtoken'); const secret = process.env.JWT_SECRET; exports.generateAccessToken = information => jwt.sign(information, secret, { expiresIn: '7d' }); exports.certifyAccessToken = token => { return new Promise((resolve, reject) => { jwt.verify(token, secret, (err, decoded) => { if (err) { reject(err); } else { resolve(decoded); } }); }); }
-
Criar middleware para validar o utilizador: middlewares -> auth
const authenticateUtil = require('../utils/authenticate.js'); module.exports = async (req, res, next) => { const accessToken = req.headers['authorization']; // req.headers['x-access-token']; if (!accessToken) { return res.status(401).send("unauthorized"); } try { const bearer = accessToken.split(' '); const bearerToken = bearer[1]; const result = await authenticateUtil.certifyAccessToken(bearerToken); req.body.loggedUserName = result.Name; return next(); } catch (err) { return res.status(401).send("unauthorized"); } }
-
Alterar controller: controllers -> v2 -> auth
const bcrypt = require('bcryptjs/dist/bcrypt'); const authenticateUtil = require('../../utils/authenticate.js'); const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient() exports.signin = async (req, res) => { try { const { email, password } = req.body; const user = await prisma.users.findUnique({ where: { email: email, }, }) if (user) { var passwordIsValid = bcrypt.compareSync( password, user.password ); if (passwordIsValid) { const accessToken = authenticateUtil.generateAccessToken({ id: user.id, name: user.name, isAdmin : user.isAdmin }); res.status(200).json({ name: user.name, token: accessToken }); } } } catch (error) { res.status(401).json({ msg: error.message }) } } exports.signup = async (req, res) => { try { const { name, email, password, isAdmin } = req.body; await prisma.users.create({ data: { email: email, name: name, password: bcrypt.hashSync(password, 8), isAdmin: isAdmin }, }) return this.signin(req, res); } catch (error) { res.status(401).json({ msg: error.message }) } }
-
Executar
npm start
; -
Testar no Postman;
-
Push para o GIT;
- Criar conta:
- : identificador, name, email, password, isAdmin.
- Iniciar sessão:
- email e password.
- Proteger rotas:
- student, school e courses.
Não esquecer de validar:
-
Antes de criar uma conta verificar se:
- todos os campos estão devidamente preenchidos;
- (no caso de create) se já existe um registo com o mesmo identitificador (email);
- password segue padrões de segurança;
-
Admin Middlware
- Proteger rotas de criar schools e cources apenas para utilizadores com perfil administrador.
app.get('/', (req, res) => {
res.status(200).json('Welcome, your app is working well');
})
- Adiconar migrations
npx prisma migrate --name init
- Adicionar comandos postinstall e build no package.json
"scripts": {
"postinstall": "prisma generate && prisma migrate deploy",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js",
"build": "node server.js"
},
{
"version": 2,
"builds": [
{
"src": "server.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "server.js"
}
]
}