Notes-demo 2 (3/3)
Middleware backend
Viimeinen versio backendistä notesdemolle jossa otettu käyttöön middlewaret.

Asenna ensimmäiseksi express
npx express-generator --no-view --git
Asenna tämän jälkeen tarvittavat kirjastot
npm install npm install cors --save npm install nodemon --save-dev npm install dotenv --save npm install mysql2 --save npm install knex --save npm install bcryptjs --save npm install jsonwebtoken --save npm install ajv --
Varmista, että .gitignore sisältää ainakin seuraavat tiedostot:
node_modules/ *.env build/
Lisää .env:
.env
PORT=3001 DB_USER=root DB_PASS=mypass123 DB_HOST=localhost DB_PORT=3306 DB_TYPE=mysql2 DB_DATABASE=notesdemo_db SECRET=tosisalainensalasanainen DEBUG=notesmiddleware:*
Ota dotenv käyttöön app.js-tiedostossa:
require('dotenv').config()
Lisää kansio utils ja sinne tiedosto config.
config.js
let PORT = process.env.PORT
let SECRET = process.env.SECRET
let DATABASE_OPTIONS = {
client: process.env.DB_TYPE,
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_DATABASE
}
}
module.exports = {
DATABASE_OPTIONS,
PORT,
SECRET
}
Lisää scripts tiedostoon package.json:
"startdev": "nodemon ./bin/www"
Käynnistä backend:
npm run startdev
Lisää kansioon utils tiedosto dbConnection.
dbConnection.js
const config = require('./config.js')
const options = config.DATABASE_OPTIONS;
const db = require('knex')(options)
module.exports = db;
Routers
login.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');
const config = require('../utils/config.js')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
router.post('/', (req, res, next) => {
const user = req.body;
console.log(user);
knex('users').select('*').where('username', '=', user.username)
.then((dbuser) => {
if (dbuser.length == 0) {
return res.status(401).json(
{ error: "invalid username or password" }
)
}
const tempUser = dbuser[0];
bcrypt.compare(user.password, tempUser.password)
.then((passwordCorrect) => {
if (!passwordCorrect) {
return res.status(401).json(
{ error: "invalid username or password" }
)
}
const userForToken = {
username: tempUser.username,
id: tempUser.id
}
const token = jwt.sign(userForToken, config.SECRET)
res.status(200).send({
token,
username: tempUser.username,
role: "regularuser"
})
})
})
.catch((err) => {
res.status(500).json(
{ error: err }
)
})
})
module.exports = router;
register.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');
const bcrypt = require('bcryptjs')
router.post('/', (req, res, next) => {
const user = req.body;
const saltRounds = 10;
console.log(user);
bcrypt.hash(user.password, saltRounds)
.then((passwordHash) => {
const newUser = {
username: user.username,
password: passwordHash,
email: user.email
}
knex('users').insert(newUser)
.then(() => {
res.status(204).end()
})
.catch((err) => {
console.log(err);
res.status(500).json(
{ error: err }
)
})
})
})
module.exports = router;
Reititys notes-tiedoille
notesRouter.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');
router.get('/', (req, res, next) => {
const decodedTokenId = res.locals.auth.userId;
knex('notes').select('*').where('user_id', '=', decodedTokenId)
.then((rows) => {
res.json(rows);
})
.catch((err) => {
console.log('SELECT * NOTES failed')
res.status(500).json(
{ error: err }
)
})
})
router.post('/', (req, res, next) => {
const note = req.body;
console.log(note);
note.user_id = res.locals.auth.userId;
const newNote = {
content: note.content,
important: note.important,
date: new Date(note.date),
user_id: note.user_id
}
knex('notes').insert(newNote)
.then(id_arr => {
console.log(id_arr);
note.id = id_arr[0];
res.json(note);
})
.catch((err) => {
console.log(err);
res.status(500).json(
{ error: err }
)
})
})
router.delete('/:id', (req, res, next) => {
const id = req.params.id;
console.log(id);
const decodedTokenId = res.locals.auth.userId;
knex('notes').where('user_id', "=", decodedTokenId).andWhere('id', '=', id).del()
.then(status => {
console.log("deleted ok")
res.status(204).end();
})
.catch((err) => {
console.log(err);
res.status(500).json(
{ error: err }
)
})
})
router.put('/:id', (req, res, next) => {
const id = req.params.id;
const note = req.body;
const decodedTokenId = res.locals.auth.userId;
const updatedNote = {
content: note.content,
important: note.important,
date: new Date(note.date)
}
knex('notes').update(updatedNote).where('user_id', "=", decodedTokenId)
.andWhere('id', '=', id)
.then((response) => {
console.log(response)
res.status(204).end();
})
.catch((err) => {
console.log(err);
res.status(500).json(
{ error: err }
)
})
})
module.exports = router;
Middlewares
auth.js
Lisää kansio middleware ja sinne tiedosto auth.js
const jwt = require('jsonwebtoken')
const config = require('../utils/config')
const getTokenFrom = req => {
const authorization = req.get('authorization');
if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
return authorization.substring(7)
} else {
return null
}
}
const isAuthenticated = (req, res, next) => {
const token = getTokenFrom(req);
console.log(token);
if (!token) {
return res.status(401).json(
{ error: "auth token missing" }
)
}
let decodedToken = null;
try {
decodedToken = jwt.verify(token, config.SECRET);
}
catch (error) {
console.log("jwt error")
}
if (!decodedToken || !decodedToken.id) {
return res.status(401).json(
{ error: "invalid token" }
)
}
res.locals.auth = { userId: decodedToken.id };
next();
}
module.exports = isAuthenticated;
Ota käyttöön app.js-tiedostolla
var isAuthenticated = require('./middleware/auth');
Muuta routerit
app.use('/', indexRouter);
app.use('/notes', isAuthenticated, notesRouter);
app.use('/login', loginRouter);
app.use('/register', registerRouter);
Lisää cors
const cors = require('cors')
app.use(cors())
Tietojen validointi scheman avulla
Lisää middleware-kansioon tiedosto validate.js
validate.js
const Ajv = require('ajv');
var ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}
const validateSchema = (schema) => {
return function (req, res, next) {
console.log("starting middleware2")
const reqmethod = req.method;
if (reqmethod === "POST" || reqmethod === "PUT") {
const body = req.body;
var validate = ajv.compile(schema);
var valid = validate(body);
if (!valid) {
console.log(validate.errors);
return res.status(401).json(
{ error: "check json-data" })
} else {
next();
}
}
else {
next();
}
}
}
module.exports = validateSchema;
Tee uusi kansio schemas ja luo sinne tiedosto userSchema.json
userSchema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "user",
"type": "object",
"properties": {
"email": {
"type": "string",
"pattern": "^\\S+@\\S+\\.\\S+$",
"minLength": 5,
"maxLength": 50
},
"username": {
"type": "string",
"minLength": 6,
"maxLength": 32
},
"password": {
"type": "string",
"minLength": 8,
"maxLength": 32
},
"phonenumber": {
"type": "string",
"pattern": "\\+(9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\\d{1,14}$",
"minLength": 8,
"maxLength": 32
}
},
"required": [
"username",
"password"
]
}
notesSchema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "note",
"type": "object",
"properties": {
"content": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"date": {
"type": "string",
"pattern": "\\b[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z\\b"
},
"important": {
"type": "boolean"
},
"user_id": {
"type": "integer"
}
},
"required": [
"content",
"date",
"important"
]
}
Ota käyttöön app.js-tiedostolla
var userSchema = require('./schemas/userSchema.json');
var notesSchema = require('./schemas/notesSchema.json');
var validateSchema = require('./middleware/validate');
app.use('/register', validateSchema(userSchema), registerRouter);
app.use('/login', loginRouter);
app.use('/notes', isAuthenticated, validateSchema(notesSchema), notesRouter);