Node and express basics

Libraries

Libraries

  • plain node (createServer)
  • connect (includes middleware)
  • express (includes middleware, routing, view rendering, ...)

most projects will use express

Libraries

middleware: via .use()

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

Hello world

Hello world

hello world with 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 without express (see 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 - complete setup

create a package.json file:

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

Hello world - complete setup

install express:

npm install express

Hello world

run via:

npm run start

open http://localhost:3000 in your browser to view the result

Request and response

Request and response

web development in node is based on request handler functions, e.g.:

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

Request and response

a request handler function receives two arguments:

Exercise

exercise: create a web app that displays the current time

Exercise

solution:

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);

The request object

The request object

example of a request object:

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

class in plain node / connect: IncomingMessage

class in express: Request

The request object

exercise: create a website with different responses based on the requested URL

The response object

The response object

class in plain node / connect: ServerResponse

class in express: Response

The response object

methods in express:

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

The response object

example:

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

The response object

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

The response object

setting a cookie:

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

or explicitly:

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

Routing and redirects

Routing

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

other methods: .post, .put, .delete, ...

Route parameters

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

Redirects

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

Rendering HTML

Rendering HTML

manually (dangerous):

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

Rendering HTML

via a template engine:

Rendering HTML

general procedure:

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 });
});

Rendering HTML

registering various 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());

Rendering HTML via express-react-views

install npm packages: express-react-views, react, react-dom

Rendering 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;

Exercises

exercises:

  • create a website with different pages (home, about, newsletter, ...)
  • create a website that displays information from a public API (e.g. https://pokeapi.co/)

Exercises

Pokeapi part 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');
});

Exercise: Pokeapi

Pokeapi part 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;

Configuration via environment variables

Configuration via environment variables

credentials and configuration should be supplied via environment variables

credentials should not be under version control

.env file

common way to supply configuration and credentials: store in a file named .env, load as environment variables via dotenv

make sure .env is not under version control (add it to the .gitignore file)

.env file

example .env file:

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

loading in JavaScript:

import dotenv from 'dotenv';

dotenv.config();

const PORT = process.env.PORT;
const DB_URL = process.env.DB_URL;

NODE_ENV

Environment variable NODE_ENV: important when using e.g. express

in production environments, NODE_ENV=production should always be set - otherwise the user will be able to see JavaScript error messages in detail (with stack traces)

Summary: basic setup

Summary: basic setup

  1. create package.json
  2. install packages
  3. add .gitignore file
  4. load configuration from .env file
  5. configure rendering engine and routes
  6. add views

Create package.json

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

Install packages

install express, dotenv and a template engine

template engines:

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

Add .gitignore file

example .gitignore file:

.env
node_modules

Load configuration from .env file

.env file example:

PORT=3000

loading configuration:

import dotenv from 'dotenv';

dotenv.config();

const PORT = process.env.PORT;

Create app with rendering engine and routes

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);

Add 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 options

online editors:

  • glitch
  • codepen

"real" hosting (free options available):

  • Heroku
  • Amazon Web Services (Elastic Beanstalk)
  • Google Cloud Platform (Google App Engine) (credit card information required for identity verification)
  • Microsoft Azure (App Service) (credit card information required for identity verification)
  • ...

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

Hosting on glitch.com

Hosting on glitch.com

on https://glitch.com, select "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 on Heroku

Hosting on Heroku

sign up for a Heroku account, specifying Node.js as primary development language

select create new app, choose a unique name and a server location

Connecting to GitHub

on the "deploy" tab, select "connect to GitHub"

click Deploy Branch for the first deploy

enable automatic deploys to deploy every time something is pushed to the main branch

Environment variables

to set environment variables for configuration, go to "settings" - "config vars"

the environment variable PORT is available automatically and does not have to be set here

Hosting on Heroku

more information: https://devcenter.heroku.com/articles/deploying-nodejs

Middleware

Middleware

middleware may be used in connect and in express

middleware can react / respond to requests or modify req / res objects

Middleware

example use:

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());

Example middleware

  • express.json, express.urlencoded, ... : parse the request body and make it available as req.body
  • cookie-parser: parses cookies and makes them available under req.cookies
  • compression: compresses the response content
  • express.static: sends static files (e.g. index.html) if available
  • express-session: stores session data (available under req.session)
  • express-openid-connect or passport: user authentication
  • morgan: logging
  • ... (see: list of available express middleware)

Forms

Request parameters

By default, the browser sends form contents are sent in URL-encoded format, e.g.:

foo=1&bar=2&baz=3

in get requests: as part of the URL, e.g. https://google.com/search?ei=xyzg&q=foo...

in post requests: in the request body

Getting request parameters

in a get request: read req.query

in a post request: use express.urlencoded middleware, read req.body

User authentication with an identity provider

Identity provider

an identity provider can verify the identity of users (can authenticate users)

examples:

the current end user is logged in as user "foo" on this domain

the current user is authenticated as user "x" by Google / as user "y" by facebook

Identity provider

mechanism for the user:

user clicks on login, is redirected to a login page, and then sent back to the original site once logged in

in the background the user will receive an identity token, a piece of data that can prove their identity with the identity provider

the identity token is usually set as a cookie (e.g. appSession)

Identity provider

standards:

  • authorization via OAuth2
  • authentication via OpenID Connect

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: Registration and setup

  • register for an Auth0 account on https://auth0.com
  • in the sidebar, select "Applications"
  • select the default application or create a new "Regular Web Application"; the selected name will be shown to users when they authenticate

Auth0: Regsitration and setup

Application Settings:

  • allowed redirect URLs for login completion: Allowed Callback URLs
  • allowed redirect URLs after logout: Allowed Logout URLs

callback URLs example:

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

logout URLs example:

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

Configuration

example .env configuration for local development (sources on next slide):

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

Configuration

under Settings:

each Auth0 registrant has at least one domain (e.g. dev-xxxxxxxx.eu.auth0.com)

each app has a specific client ID (e.g. jA0EAwMCxamDRMfOGV5gyZPnyX1BBPOQ)

self-generated:

secret: generate a long, random string yourself (recommendation: at least 32 characters)

Express and Auth0

guides:

guide using the most recent library (express-openid-connect): https://auth0.com/docs/quickstart/webapp/express

guide using older (more wide-spread) libraries: https://auth0.com/docs/quickstart/webapp/express

Express and Auth0

npm package: express-openid-connect

middleware for authentication

automatically adds URLs /login, /logout, /callback

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 and 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>'
    );
  }
});