Node and express basics

Libraries

Libraries

  • reines node (createServer)
  • connect (beinhaltet Middleware)
  • express (beinhaltet Middleware, Routing, Rendering, ...)

die meisten Projekte verwenden express

Libraries

middleware: via .use()

routing: via .get(), .post(), ...

Hello world

Hello world

hello world mit express:

// server.js
import express from 'express';

const app = express();

// provide a function that handles a request to "/"
// and sends a response
app.get('/', (req, res) => {
  // note: we should actually return a complete HTML file
  res.send('<h1>Hello World!</h1>\n');
});

// listen on localhost:3000
app.listen(3000);

Hello world

hello world ohne express (siehe https://nodejs.org/en/docs/guides/getting-started-guide/):

// server.js
import http from 'http';

const handler = (req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/html; charset=UTF-8');
  res.end('<h1>Hello World!</h1>\n');
};

const server = http.createServer(handler);
server.listen(3000);

Hello world - komplettes Setup

Erstellen von package.json:

{
  "type": "module",
  "eslintConfig": {
    "sourceType": "module"
  },
  "scripts": {
    "start": "node server.js"
  }
}

Hello world - komplettes Setup

Installation von express:

npm install express

Hello world - komplettes Setup

ausführen via:

npm run start

öffne http://localhost:3000 im Browser für das Resultat

Request und Response

Request und Response

Web-Entwicklung in node geschieht über Request-Hanlder-Funktionen, z.B.:

(req, res) => {
  res.send('<h1>Hello World!</h1>\n');
};

Request und Response

Ein Request Handler bekommt zwei Argumente:

  • req - repräsentiert den eingehenden request (Klasse IncomingMessage in node, Unterklasse Request in express)
  • res - repräsentiert die response / Antwort, die gensendet wird (Klasse ServerResponse in node, Unterklasse Response in express)

Ãœbung

Ãœbung: erstelle eine Seite, die die aktuelle Uhrzeit anzeigt

Ãœbung

Lösung:

import express from 'express';

const app = express();

app.use((req, res) => {
  const timeString = new Date().toTimeString();
  res.send('<h1>current time: ' + timeString + '</h1>');
});

app.listen(3000);

Das Request-Objekt

Das Request-Objekt

Beispiel für ein Request-Objekt:

{
  "url": "/",
  "method": "GET",
  "headers": {
    "user-agent": "Mozilla/5.0 (Windows ..."
  }
}

Klasse in reinem Node bzw in connect: IncomingMessage

Klasse in express: Request

Das Request-Objekt

Ãœbung: Erstelle eine Website mit unterschiedlichen Antworten basierend auf der angefragten URL

Das Response-Objekt

Das Response-Objekt

Klasse in reinem Node bzw in connect: ServerResponse

Klasse in express: Response

Das Response-Objekt

Methoden in Express:

  • .send()
  • .set()
  • .status()

Das Response-Objekt

Beispiel:

res.set({ 'Content-Type': 'text/plain' });
res.send('Date:\n' + new Date().toLocaleDateString());

Das Response-Objekt

res.status(404);
res.set({ 'Content-Type': 'text/plain' });
res.send('Document not found.\n');

Das Response-Objekt

Setzen eines Cookies:

res.cookie('a', '1');

bzw explizit:

res.set({ 'Set-Cookie': 'a=1' });

Routing und Redirects

Routing

app.get('/', (req, res) => {
  res.send('<h1>Home</h1>\n');
});
app.get('/about', (req, res) => {
  res.send('<h1>About</h1>\n');
});

andere Methoden: .post, .put, .delete, ...

Routenparameter

app.get('/articles/:id', (req, res) => {
  const articleId = req.params.id;
  // ...
});

Redirects

app.get('/home', (req, res) => {
  res.redirect('/');
});

Rendern von HTML

Rendern von HTML

manuell (gefährlich):

app.get('/', (req, res) => {
  const name = 'world';
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>Hello ${name}</title></head>
      <body>Hello ${name}</body>
    </html>
  `);
});

Rendern von HTML

mit Hilfe einer Template Engine:

Rendern von HTML

allgemeines Prozedere:

import express from 'express';
import myengine from 'myengine';

const app = express();

app.engine('myengine', myengine());
app.set('view engine', 'myengine');

app.get('/', (req, res) => {
  const name = 'world';
  // renders 'views/index.myengine'
  res.render('index', { name: name });
});

Rendern von HTML

registrieren verschiedener Template Engines:

import ejs from 'ejs';
import expressHandlebars from 'express-handlebars';
import expressReactViews from 'express-react-views';

app.engine('ejs', ejs);
app.engine('handlebars', expressHandlebars());
app.engine('jsx', expressReactViews.createEngine());

Rendern von HTML via express-react-views

npm-Pakete: express-react-views, react, react-dom

Rendern von HTML via express-react-views

views/index.jsx:

import React from 'react';

const Index = ({ name }) => {
  return (
    <html>
      <head>
        <title>Hello, {name}!</title>
      </head>
      <body>
        <h1>Hello, {name}!</h1>
      </body>
    </html>
  );
};

export default Index;

Ãœbungen

Ãœbungen:

  • erstelle eine Website mit verschiedenen Seiten (home, about, newsletter, ...)
  • erstelle eine Website, die Informationen von einem öffentlichen API anzeigt (z.B. https://pokeapi.co/)

Ãœbungen

Pokeapi Teil 1:

app.get('/:id', async (req, res) => {
  const id = req.params.id;
  const dataRes = await fetch(
    `https://pokeapi.co/api/v2/pokemon/${id}`
  );
  const data = await dataRes.json();
  await res.render('pokemon', { id: id, data: data });
});
app.get('/', (req, res) => {
  res.redirect('/1');
});

Ãœbungen

Pokeapi Teil 2:

// views/pokemon.jsx
import React from 'react';

const Pokemon = (props) => {
  const id = Number(props.id);
  const data = props.data;
  return (
    <div>
      <article>
        <h1>{data.species.name}</h1>
        <img src={data.sprites.front_default} />
      </article>
      <a href={`/${id - 1}`}>prev</a>
      <br />
      <a href={`/${id + 1}`}>next</a>
    </div>
  );
};

export default Pokemon;

Konfiguration mittels Umgebungsvariablen

Konfiguration mittels Umgebungsvariablen

Zugangsdaten und Konfiguration werden üblicherweise via Umgebungsvariablen bereitgestellt

Zugangsdaten sollten nicht unter Versionskontrolle stehen

.env-Datei

verbreitete Möglichkeit, um Zugangsdaten bereit zu stellen: speichern in einer Datei namens .env, laden als Umgebungsvariablen mittels dotenv

Stelle sicher, dass .env nicht unter Versionskontrolle steht (füge es zur Datei .gitignore hinzu)

.env-Datei

Beispiel für .env-Datei:

PORT=3000
NODE_ENV=production
DB_URL=mongodb+srv://...
AUTH0_DOMAIN=xxx.eu.auth0.com

laden in JavaScript:

import dotenv from 'dotenv';

dotenv.config();

const PORT = process.env.PORT;
const NODE_ENV = process.env.NODE_ENV;

NODE_ENV

Umgebungsvariable NODE_ENV: spielt z.B. bei express eine wichtige Rolle

in Produktivumgebungen sollte immer NODE_ENV=production gesetzt sein - ansonsten werden z.B. dem Endnutzer JavaScript-Fehlermeldungen im Detail angezeigt (mit Stack Traces)

Zusammenfassung: grundlegendes Setup

Zusammenfassung: grundlegendes Setup

  1. erstelle package.json
  2. installiere Pakete
  3. erstelle .gitignore
  4. lade Konfiguration aus .env
  5. erstelle App mit Rendering Engine und Routen
  6. erstelle Views

Erstelle package.json

{
  "type": "module",
  "eslintConfig": {
    "sourceType": "module"
  },
  "scripts": {
    "start": "node server.js"
  }
}

Installiere Pakete

installiere express, dotenv und eine Template Engine

Template Engines:

  • pug
  • express-handlebars
  • ejs
  • express-react-views, react, react-dom

Erstelle .gitignore-Datei

Beispiel für .gitignore:

.env
node_modules

Lade Konfiguration aus .env

Beispiel für .env:

PORT=3000

Laden der Konfiguration:

import dotenv from 'dotenv';

dotenv.config();

const PORT = process.env.PORT;

Erstelle App mit Rendering Engine und Routen

import express from 'express';
import expressReactViews from 'express-react-views';
const app = express();

app.engine('jsx', expressReactViews.createEngine());
app.set('view engine', 'jsx');

app.get('/', (req, res) => {
  res.render('index', { name: 'world' });
});

app.listen(PORT);

Erstelle Views

// views/index.jsx
import React from 'react';

export default ({ name }) => {
  return (
    <html>
      <head>
        <title>Hello, {name}!</title>
      </head>
      <body>
        <h1>Hello, {name}!</h1>
      </body>
    </html>
  );
};

Hosting

Hosting-Möglichkeiten

Online-Editoren:

  • glitch
  • codepen

"echtes" Hosting (kostenlose Optionen verfügbar):

  • Heroku
  • Amazon Web Services (Elastic Beanstalk)
  • Google Cloud Platform (Google App Engine) (Kreditkarteninformation zur Identitätsfeststellung benötigt)
  • Microsoft Azure (App Service) (Kreditkarteninformation zur Identitätsfeststellung benötigt)
  • ...

siehe auch: codeinwp: 9 of the Best Node.js Hosting Platforms for 2021 (Free and Paid)

Hosting auf glitch.com

Hosting auf glitch.com

auf https://glitch.com, wähle "get started" - "hello express"

// server.js
const express = require('express');

const app = express();

app.use((req, res) => {
  // note: we should actually return a complete HTML file
  res.send('<h1>Hello World!</h1>\n');
});

app.listen(process.env.PORT);

Hosting auf Heroku

Hosting auf Heroku

registriere dich für einen Heroku-Account, wähle Node.js als "primary development language"

wähle create new app, wähle einen eindeutigen Namen und eine Server-Location

Verbindung zu GitHub

im "deploy"-Tab, wähle "connect to GitHub"

klicke Deploy Branch zum erstmaligen Deployment

aktiviere automatische Deployments, um bei jeder Änderung des Repositories automatisch zu deployen

Umgebungsvariablen

setzen von Umgebungsvariablen zur Konfiguration unter "settings" - "config vars"

die Umgebungsvariable PORT ist in Programmen automatisch verfügbar und muss hier nicht gesetzt werden

Hosting auf Heroku

mehr Informationen: https://devcenter.heroku.com/articles/deploying-nodejs

Middleware

Middleware

Middleware kann in express und in connect verwendet werden

Middleware kann auf Requests reagieren / antworten sowie die req / res - Objekte abändern

Middleware

Beispiel:

import compression from 'compression';
import express from 'express';
import morgan from 'morgan';

const app = express();

// log all incoming requests
app.use(morgan('common'));
// serve content of the "public" folder as-is if found
app.use(express.static('public'));
// compress all responses
app.use(compression());

Beispiele für Middleware

  • express.json, express.urlencoded, ... : parsen den Inhalt eines Requests und stellen ihn als req.body zur Verfügung
  • cookie-parser: liest Cookies aus und stellt sie unter req.cookies zur Verfügung
  • compression: komprimiert den Inhalt der Response
  • express.static: antwortet mit vorhandenen statischen Dateien (z.B. index.html), falls vorhanden
  • express-session: speichert Sitzungsdaten (verfügbar unter req.session)
  • express-openid-connect oder passport: Benutzer-Authentifizierung
  • morgan: Logging
  • ... (siehe: list of available express middleware)

Formulare

Request-Parameter

Standardmäßig sendet der Browser Formular-Inhalte im URL-encoded Format, z.B.:

foo=1&bar=2&baz=3

in get-Requests: als Teil der URL, z.B. https://google.com/search?ei=xyzg&q=foo...

in post-Requests: im Request-Body

Auslesen von Request-Parametern

in einem get-Request: lies req.query

in einem post-Request: verwende express.urlencoded als Middleware, lies req.body

Benutzer-Authentifizierung mit einem Identity Provider

Identity Provider

Ein Identity Provider kann die Identität eines Benutzers überprüfen (kann den Benutzer authentifizieren)

Beispiele:

der aktuelle Endnutzer ist auf dieser Domain als Benutzer "foo" eingeloggt

der aktuelle Endnutzer ist al Benutzer "x" bei Google / als Benutzer "y" bei Facebook authentifiziert

Identity Provider

Mechanismus für den Benutzer:

Benutzer klickt auf login, wird zu einer Login-Seite weitergeleitet und nach erfolgreichem Login zur ursprünglichen Seite zurückgeleitet

im Hintergrund erhält der Benutzer ein Identity Token, einen kleinen Datensatz, der die Identität des Benutzers im Zusammenspiel mit dem Identity Provider belegen kann

das Identity Token wird üblicherweise als Cookie gesetzt (z.B. appSession)

Identity Provider

Standards:

  • Authentifizierung via OpenID Connect
  • Authorisierung via OAuth2

Auth0

Auth0 (auth-zero) is a widely-used identity provider

supports authentication via "internal" accounts or external identity providers (e.g. Google, Apple, Facebook, ...)

Auth0: Registrierung und Einrichtung

  • registriere dich für einen Auth0-Account unter https://auth0.com
  • in der Sidebar, wähle "Applications"
  • wähle die "default application" oder erstelle eine neue "Single Page Web Application"; der gewählte Name wird Benutzern bei der Authentifizierung angezeigt

Auth0: Registrierung und Einrichtung

Application Settings:

  • erlaubte Redirect-Ziele zum Abschließen des Logins: Allowed Callback URLs
  • erlaubte Redirect-Ziele nach dem Logout: Allowed Logout URLs

Beispiel für Callback URLs:

http://localhost:3000/callback,
https://mydomain.com/callback

Beispiel für Logout URLs:

http://localhost:3000,
https://mydomain.com

Konfiguration

Beispiel für .env Datei für lokale Entwicklung (Quellen auf nächster Slide)

ISSUER_BASE_URL=https://dev-xxxxxxxx.eu.auth0.com
CLIENT_ID=jA0EAwMCxamDRMfOGV5gyZPnyX1BBPOQ
SECRET=7qHciKUpXk7pCXqG45bweRBQxBTMpztB
BASE_URL=http://localhost:3000
PORT=3000

Konfiguration

unter Settings:

jeder Auth0-Klient hat zumindest eine domain (z.B. dev-xxxxxxxx.eu.auth0.com)

jede App hat eine bestimmte client ID (z.B. jA0EAwMCxamDRMfOGV5gyZPnyX1BBPOQ)

selbst erstellt:

secret: erstelle eine lange, zufällige Zeichenfolge (empfohlen: mindestens 32 Zeichen)

Express und Auth0

Guides:

Guide mit der aktuellsten Library (express-openid-connect): https://auth0.com/docs/quickstart/webapp/express

Guide mit älteren (verbreiteteren) Libraries: https://auth0.com/docs/quickstart/webapp/express

Express und Auth0

npm-Paket: express-openid-connect

Middleware für Authentifizierung

stellt automatisch die URLs /login, /logout, /callback bereit

app.use(
  expressOpenidConnect.auth({
    authRequired: false,
    auth0Logout: true,
    baseURL: process.env.BASE_URL,
    clientID: process.env.CLIENT_ID,
    issuerBaseURL: process.env.ISSUER_BASE_URL,
    secret: process.env.SECRET,
  })
);

Express und Auth0

app.get('/', (req, res) => {
  if (req.oidc.isAuthenticated()) {
    res.send(
      `<p>logged in <a href="/logout">log out</a></p>
       <pre>${JSON.stringify(req.oidc.user)}</pre>`
    );
  } else {
    res.send(
      '<div>not logged in <a href="/login">log in</a></div>'
    );
  }
});