most projects will use express
middleware: via .use()
routing: via .get()
, .post()
, ...
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 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);
create a package.json file:
{
"type": "module",
"eslintConfig": {
"sourceType": "module"
},
"scripts": {
"start": "node server.js"
}
}
install express:
npm install express
run via:
npm run start
open http://localhost:3000 in your browser to view the result
web development in node is based on request handler functions, e.g.:
(req, res) => {
res.send('<h1>Hello World!</h1>\n');
};
a request handler function receives two arguments:
req
- represents the incoming request (class IncomingMessage in node, subclass Request in express)res
- respresents the response that will be sent (class ServerResponse in node, subclass Response in express)exercise: create a web app that displays the current time
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);
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
exercise: create a website with different responses based on the requested URL
class in plain node / connect: ServerResponse
class in express: Response
methods in express:
.send()
.set()
.status()
example:
res.set({ 'Content-Type': 'text/plain' });
res.send('Date:\n' + new Date().toLocaleDateString());
res.status(404);
res.set({ 'Content-Type': 'text/plain' });
res.send('Document not found.\n');
setting a cookie:
res.cookie('a', '1');
or explicitly:
res.set({ 'Set-Cookie': 'a=1' });
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
, ...
app.get('/articles/:id', (req, res) => {
const articleId = req.params.id;
// ...
});
app.get('/home', (req, res) => {
res.redirect('/');
});
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>
`);
});
via a template engine:
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 });
});
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());
install npm packages: express-react-views, react, react-dom
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:
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');
});
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;
credentials and configuration should be supplied via environment variables
credentials should not be under version control
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)
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;
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)
{
"type": "module",
"eslintConfig": {
"sourceType": "module"
},
"scripts": {
"start": "node server.js"
}
}
install express, dotenv and a template engine
template engines:
example .gitignore file:
.env
node_modules
.env file example:
PORT=3000
loading configuration:
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT;
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);
// 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>
);
};
online editors:
"real" hosting (free options available):
see also: codeinwp: 9 of the Best Node.js Hosting Platforms for 2021 (Free and Paid)
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);
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
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
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
more information: https://devcenter.heroku.com/articles/deploying-nodejs
middleware may be used in connect and in express
middleware can react / respond to requests or modify req / res objects
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());
req.body
req.cookies
req.session
)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
in a get request: read req.query
in a post request: use express.urlencoded
middleware, read req.body
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
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
)
standards:
Auth0 (auth-zero) is a widely-used identity provider
supports authentication via "internal" accounts or external identity providers (e.g. Google, Apple, Facebook, ...)
Application Settings:
callback URLs example:
http://localhost:3000/callback,
https://mydomain.com/callback
logout URLs example:
http://localhost:3000,
https://mydomain.com
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
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)
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
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,
})
);
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>'
);
}
});