📄
Webbserverprogrammering
  • Start
  • Utvecklarmiljö
    • Windows Subsystem for Linux
  • Teknisk orientering
    • Viktiga begrepp
  • Webbserver
    • Introduktion
    • Hur fungerar det?
    • Design - inte sĂ„ webbserver
    • Aktiv sida
    • Praxis
  • Databas
    • SQL
    • Ett exempel
    • SQL och Node
    • Databasdesign
    • CRUD Meeps
    • CRUD Filmdatabas
  • SĂ€kerhet
    • Introduktion
    • Inloggning
  • MVC
    • Model
    • Controller
    • View
  • Tester
    • Automatiserade tester
    • Testdriven utveckling
  • Containers
    • Docker
  • Laravel
    • Laravel, Nginx, Mysql
Powered by GitBook
On this page
  • Koppla upp
  • Konfiguration
  • Databasmodell
  • DatabasfrĂ„ga
  • Databasresultat till en view
  • Övning
  • Databas selektion
  • Selektion och node
  • Övning
  • Databasmodell, asynkrona frĂ„gor
  • Flera frĂ„gor

Was this helpful?

  1. Databas

SQL och Node

PreviousEtt exempelNextDatabasdesign

Last updated 4 years ago

Was this helpful?

Installera för node med npm.

Installera paketen och för Node.

npm install mysql
  • mysql paketet innehĂ„ller drivrutiner för att anvĂ€nda mysql med node.

  • dotenv Ă€r ett paket för att skapa konfiguarationsfiler. Att frĂ„n koden Ă€r god praxis.

En databas behöver anvÀndaruppgifter för att koppla upp sig till en databasserver. Denna data Àr kÀnslig och du behöver skydda den, dÀrför Àr det viktigt att inte ladda upp denna data pÄ GitHub. För att underlÀtta det anvÀnds paketet . Med dotenv kan vi skapa en konfigurationsfil och med hjÀlpa av .gitignore sÄ sÀkerstÀller vi att den inte laddas upp pÄ GitHub.

npm install mysql dotenv

Koppla upp

Detta exempel Àr plockat frÄn mysql-paketets , den Äterfinns Àven i . Ofta ser Node-hjÀlp ut pÄ detta sÀttet och det kan vara svÄrt att fÄ svar pÄ var detta ska skrivas(vi kommer till det). Du behöver inte koda detta, men om du gör det sÄ kan du lÀgga koden i en route.

const mysql = require('mysql');
const connection = mysql.createConnection({
  host     : 'example.org',
  user     : 'bob',
  password : 'secret'
});
 
connection.connect(function (err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
    return;
  }
 
  console.log('connected as id ' + connection.threadId);
});

Först skapas en const för mysql-paketet, drivrutinen krÀvs. Efter det sÄ skapas en uppkoppling till mysql-servern utifrÄn den angivna konfigurationen. DÀrefter testats uppkopplingen med connect metoden och vid eventuellt fel sÄ loggas det, annars sÄ loggas uppkopplingen. Koden kan testas i app.js eller i en route fil för express om sÄ önskas.

Node kan fÄ problem med auth till MySql 8. DÄ behöver du uppdatera din mysql-anvÀndare pÄ mysql servern. SQL frÄgan för detta finns hÀr nedanför.

ALTER USER 'username'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
FLUSH privileges;

Konfiguration

Att arbeta med konfigurationen för ett projekt i en separat fil Àr en god praxis som lÄter dig konfigurera upp ett projekt utan att Àndra i koden. Att separera konfigurationen skyddar kÀnslig konfigurationsdata(lösenord, anvÀndare api-nycklar) frÄn att laddas upp pÄ till exempel GitHub. dotenv filen skapas i projektets root.

  1. Skapa först en fil med namnet .env-example utan konfigurationsvÀrden.

  2. Kopiera sedan filen till en ny fil med namnet .env och fyll i konfigurationsvÀrdena.

För en databaskonfiguration behövs, host, username, password och databas.

.env-example
DB_HOST=
DB_USER=
DB_PASS=
DB_DATABASE=
.env
DB_HOST=127.0.0.1
DB_USER=username
DB_PASS=password
DB_DATABASE=databasename

Skapa sedan en .gitignore fil i projektets root för att kontrollera sÄ att dotenv filen inte laddas upp pÄ GitHub.

.gitignore
# dotenv environment variables file
.env

Utan .gitignore kvittar det att du skapar .env filerna.

För att komma Ät vÀrdena frÄn dotenv filen sÄ behöver paketet laddas in sÄ tidigt som möjligt i applikationen. VÀrdena Äterfinns sedan i objektet process.env.

Kom ihÄg att starta om din server efter du Àndrat i dotenv filen, sÄ att Àndringarna laddas.

app.js
require('dotenv').config();

Med dotenv paketet laddat kan det ovanstÄende exemplet Àndras till.

const connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_DATABASE
});
 
connection.connect(function (err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
    return;
  }
 
  console.log('connected as id ' + connection.threadId);
});

Databasmodell

Skapa en ny mapp models i projektets root och i models en fil med namn, db.js.

models/db.js
const mysql = require('mysql');

const pool = mysql.createPool({
  connectionLimit: 10,
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_DATABASE
});

module.exports = pool;

Skapa sedan en ny route fil för att testa modellen, routes/dbtest.js.

routes/dbtest.js
const express = require('express');
const router = express.Router();
const pool = require('../models/db');

pool.getConnection((error, connection) => {
  if (error) {
    throw error;
  }

  res.send('connected as id ' + connection.threadId);

  connection.release();
});

module.exports = router;

App.js behöver uppdateras för att ladda routen, gör följande Àndring dÀr de andra routerna laddas och anvÀnds.

app.js
...
const dbtestRouter = require('./routes/dbtest');
...
app.use('/dbtest', dbtestRouter);

Databasmodellen sparas i variabeln pool för anvÀndning. Sedan anvÀnds metoden .getConnection() för att hÀmta en uppkoppling frÄn poolen. Sidan visar sedan uppkopplingens id innan uppkopplingen slÀpps, release. Testa vad som sker utan connection.release().

DatabasfrÄga

wget https://raw.githubusercontent.com/jensnti/Webbserverprogrammering/master/exempel/meeps.sql

NÀr du laddat ned filen sÄ förbered för att importera den med att skapa en databas(om det behövs).

sudo service mysql restart
mysql -u USERNAME -p DATABASE < FILENAME

Ändra sedan test-routen till att innehĂ„lla en faktisk SQL frĂ„ga. Routen kommer nu att svara pĂ„ /test och svara med resultatet av databasfrĂ„gan i JSON. Du kan Ă€ven logga(console.log eller console.table) resulatet frĂ„n databasfrĂ„gan för att se hur objektet ser ut(det skrivs dĂ„ i terminalen dĂ€r du startat node).

routes/dbtest.js
router.get('/', function (req, res, next) {
  const sql = 'SELECT * FROM meeps';

  pool.query(sql, function (err, result, fields) {
    if (err) throw err;
    console.table(result);
    res.json({
      status: 200,
      result
    });
  });
});

Databasresultat till en view

I det hÀr steget ska vi skapa en view som kan visa resultatet av en SQL frÄga. Detta för att sammankoppla alla delarna. För att göra detta sÄ behöver routen Àndras. Test-routens respons ska anvÀnda render metoden med en test-view och resultatets data.

routes/dbtest.js
router.get('/', function (req, res, next) {
  const sql = 'SELECT * FROM meeps';
  
  pool.query(sql, function (err, result, fields) {
    if (err) throw err;
    
    res.render('test', { result: result });
  });
});

AnvÀnd console.log() i routen för att felsöka och undersöka data. Detta dumpar datan till den terminal dÀr du startat upp node.

Resultatet mÄste itereras, eftersom det Àr en array. För att göra det anvÀnds Pugs each funktion.

views/dbtest.pug
extends layout

block content
  each row in result
    p #{row.body}
    | #{row.updated_at}

För att fÄ ut den fullstÀndiga information, med författarens namn sÄ behöver SQL frÄgan anvÀnda en join pÄ users tabellen. Uppdatera koden som följer.

routes/dbtest.js
const sql = 'SELECT meeps.*, users.name FROM meeps JOIN users ON meeps.user_id = users.id';

NÀsta steg blir sedan att utveckla test-viewen. Detta kan med fördel göras som en mixin. Koden som följer skapar ett kort för en post.

views/dbtest.pug
  .card
    .card-body
      p.card-text= result.body
    .card-footer.d-flex
      p.small Posted by 
        a(href="#")= result.name
        |  on 
        time(date="#{updated}")= result.updated_at

Övning

  • Skapa en mixin som du kan kalla pĂ„ för varje rad i databasen, antingen skriver du kod för ett eget kort med css eller sĂ„ anvĂ€nder du ett Bootstrap kort.

Databas selektion

I SQL sÄ kan WHERE anvÀndas för att vÀlja rader utifrÄn ett logiskt uttryck. För att vÀlja en specifik meeps sÄ anvÀnds ID kolumnen. Det ser ut som följer med resultat.

SELECT * FROM meeps WHERE id = 2;
+----+-------------+--------------------------+---------------------+---------------------+---------+
| id | title       | body                     | created_at          | updated_at          | user_id |
+----+-------------+--------------------------+---------------------+---------------------+---------+
|  2 | Hello there | Lorem ipsum dolor sit... | 2020-09-25 13:04:34 | 2020-09-25 13:19:53 |       1 |
+----+-------------+--------------------------+---------------------+---------------------+---------+

Du kan sjÀlvklart vÀlja data utifrÄn andra fÀlt som user_id för att vÀlja alla poster frÄn en specifik anvÀndare. För wildcard sÄ anvÀnds %.

Övning

  • Prova att skriva en SQL frĂ„ga som lĂ„ter dig söka efter mailadresser i databasen. TĂ€nka att anvĂ€ndaren ska kunna söka efter ett förnamn och fĂ„ tillbaka matchande adresser. AnvĂ€nd SQL wildcards, %.

Selektion och node

För att kunna vÀlja rader frÄn databasen med specifika ID, sÄ mÄste servern kunna ta emot ID parametern. För att skicka en parameter behöver en route skapas eller Àndras för att tillÄta detta. DÄ anvÀnds :PARAMETERNAMN i routen.

router.get('/:id', function (req, res, next) {
  // console.log(req.params); hela objeketet
  console.log(req.params.id); // id parameterns vÀrde
});

Denna route pÄ / lÀser in en parameter med namnet :id. VÀrdet pÄ parametrarna Äterfinns i requestens(req) parameter-objekt. Du kommer Ät detta genom punkt-notation.

routes/dbtest.js
router.get('/:id', function (req, res, next) {
  const sql = 'SELECT * FROM meeps WHERE id = ?';

  pool.query(sql, [req.params.id], function (err, result, fields) {
    if (err) throw err;
    res.json({
      status: 200,
      id: req.params.id,
      result: result
    });
  });
});

Ändringen i test-routen skapar en route för get som tillĂ„ter en id parameter. Rad 2 skapar SQL-frĂ„gan dĂ€r frĂ„getecknet Ă€r en parameter. FrĂ„gan körs sedan pĂ„ rad 4, dĂ„ ersĂ€tts ? med [req.params.id]. Det gĂ„r utmĂ€rkt att anvĂ€nda flera parameterar(tĂ€nk post med ett formulĂ€r). Med flera parametrar Ă€r det viktigt att de Ă€r satta i rĂ€tt ordning.

Om frÄgan exekveras korrekt sÄ returneras svaret som json till webblÀsaren.

Övning

Skapa lÀnkar till enskilda meeps.

  • test.pug, ge varje meep en lĂ€nk till /test/:id

  • skapa /test/:id routen

  • skapa SQL som hĂ€mtar vald meep utifrĂ„n :id (inklusive join)

  • skapa en meep.pug views som visar vald meep(ersĂ€tt json i exemplet ovan)

Databasmodell, asynkrona frÄgor

Ibland sÄ uppstÄr problem med databasuppkopplingen eller sÄ behövs det flera uppkopplingar. Vid dessa tillfÀllen sÄ kan du behöva större kontroll över vad som sker och hur data levereras till klienten. En lösning pÄ detta Àr att göra asynkrona anrop, dÄ kan du styra över att din kod ska invÀnta svar frÄn databasen.

För att möjliggöra asynkrona anrop Àndras databasmodellen till följande.

models/db.js
const mysql = require('mysql');

const pool = mysql.createPool({
  connectionLimit: 10,
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_DATABASE
});

function query(sql, params) {
  return new Promise((resolve, reject) => {
    pool.query(sql, ...params, function (err, result, fields) {
      if (err) reject(err);
      resolve(result);
    });
  });
}

module.exports = { pool, query };

Query funktionen accepterar en SQL frÄga samt tillhörande parametrar.

Flera frÄgor

Nu kan query funktionen anvÀndas i en route genom att requira den. Med query funktionen kan routen göras asynkron med nyckelordet async.

I koden nedan sÄ Àndras users routen för att tillÄta anvÀndaren att köra den med en parameter :id, anvÀndarens id. För att illustrera dubbla SQL-frÄgor sÄ hÀmtas dÄ Àven den anvÀndarens alla tillhörande meeps. Utan att anvÀnda async och await sÄ vÀntar aldrig scriptet pÄ att dessa frÄgor ska köras, utan returnerar ingen data. JÀmför koden hÀr nedan och prova bÄda versionerna.

routes/users.js
var express = require('express');
var router = express.Router();
const { query } = require('../models/db');

router.get('/', function (req, res, next) {
  res.render('users', { title: 'Userpage', users: ['Hans', 'Moa', 'Bengt', 'Frans', 'Lisa'] });
});

router.get('/:id', function (req, res, next) {
  try {
    const user = query(
      'SELECT * FROM users WHERE id = ?',
      req.params.id
    );

    const meeps = query(
      'SELECT * FROM meeps WHERE user_id = ?',
      req.params.id
    );

    res.render('users', {
      id: req.params.id,
      user: user,
      meeps: meeps
    });
  } catch (e) {
    console.error(e);
    next(e);
  }
});

module.exports = router;
routes/users.js
var express = require('express');
var router = express.Router();
const { query } = require('../models/db');

router.get('/', function (req, res, next) {
  res.render('users', { title: 'Userpage', users: ['Hans', 'Moa', 'Bengt', 'Frans', 'Lisa'] });
});

router.get('/:id', async function (req, res, next) {
  try {
    const user = await query(
      'SELECT * FROM users WHERE id = ?',
      req.params.id
    );

    const meeps = await query(
      'SELECT * FROM meeps WHERE user_id = ?',
      req.params.id
    );

    res.render('users', {
      id: req.params.id,
      user: user,
      meeps: meeps
    });
  } catch (e) {
    console.error(e);
    next(e);
  }
});

module.exports = router;
views/users.pug
extends layout

block head
  -var title = "AnvÀndare"
  title Webbserverprogrammering - #{title}

block content
  main
    .container
      h1= title
      if users
        p.lead HÀr Àr ett exempel pÄ iteration
        ul.list
          each user in users
            li= user

      if user[0]
        p= user[0].name
        h3 AnvÀndarens meeps
        if meeps
          ul.list
            each meep in meeps
              li= meep.body

I anvÀndar-vyn sÄ anvÀnds selektion i Pug-koden för att det inte ska visas fel nÀr data saknas(testa att köra utan if-satserna).

Det första som behöver ÄtgÀrdas Àr konfigurationsvÀrdena i exemplet och för det sÄ ska vi anvÀnda paketet. En dotenv fil Àr en konfigurationsfil(eller en fil med miljövariabler, environment). Filnamnet börjar med en punkt, vilket Àr en Linux standard för att visa att det rör sig om en dold fil(vilket ofta konfigurationsfiler Àr).

För att anvÀnda mysql kommer vi att skapa en ÄteranvÀndbar modell. Denna modell kommer Àven att anvÀnda en med uppkopplingar. Modellen kommer att exportera en skapad pool för anvÀndning.

För att testa att kommunicera med en databas behöver du starta en mysql-server och importera databasen frÄn föregÄende kapitels .

Databasdump finns . AnvÀnd wget för att hÀmta den.

Du kan ocksÄ anvÀnda för att importera databaser.

LÀr dig anvÀnda console funktionera för att felsöka din kod. Det finns ett antal olika du kan anvÀnda.

Skapa sedan en view som heter test. I en första test skriver vi ut vÀrdena frÄn mysql resultatet med i Pug. Det Àr dÄ viktigt att se till att vÀrden som skrivs ut , detta för att skadlig kod eventuellt kan sparas i en databas och sedan reproduceras för en anvÀndare pÄ webbplatsen.

VÀrdet som skickats med en parameter kan sedan anvÀndas i SQL frÄgan, det görs genom en sÄ kallad prepared statment. Prepared statements(Àven parameterized query) anvÀnds av sÀkerhetsskÀl för att undvika .

Async tillÄter anvÀndningen av await vilket tillÄter koden att invÀnta svar. Det görs genom nÄgot som kallas för promises. En promise resulterar i att den antingen resolves eller rejects, körs eller misslyckas. .

Koden vÀntar pÄ att för bÄda SQL-frÄgorna ska bli "klar". NÀr den Àr klar sÄ renderas users-view med databas-frÄgornas data. FrÄgorna körs i ett try block för att fÄnga eventuella fel med catch. Om ett fel uppstÄr sÄ fÄngas det upp och vi anvÀnder Express inbyggda , next(error).

mysql paketet
mysql
dotenv
spara konfigurationsdata separerat
dotenv
hjÀlpsida
Express-dokumentation
dotenv
pool
exempel
hÀr
Tableplus
console funktioner
interpolation
escapas
SQL-injektioner
MDN async function
promises
felhanterare
Koden för exempel-projektet finner du hÀr i DB grenen.
Samtliga moment i detta kapitel finns visade i denna speedrun.