common layers in web applications:
controller: receives HTTP request, calls a service function for processing, sends HTTP response
service: gets called by a controller; can call other services and models (multiple); can return data to the controller; contains the main logic (business logic)
model: provides database access to services
often a route (HTTP query) corresponds directly to a model (database table)
e.g. in a shopping application:
/products?category=phones&max_price=500
corresponds to:
SELECT * FROM products WHERE category = 'phones' AND price <= 500;
sometimes there can be a lot going on in the service layer
example: sending a new message via POST
to /messages
messageService could use:
common layers in express applications:
can be simplified for smaller projects, e.g.:
typical source code structure:
We can restructure our routes definitions by using Router
objects
having everything in one place:
app.get('/accounts', () => {
// ...
});
app.get('/accounts/:id', () => {
// ...
});
app.post('/accounts', () => {
// ...
});
app.get('/transactions', () => {
// ...
});
// ...
cleaner structure:
app.use('/accounts', accountsRouter);
app.use('/transactions', transactionsRouter);
definition of accountsRouter
in a separate file (e.g. routes/accounts.route.js)
import { Router } from 'express';
export const accountsRouter = Router();
accountsRouter.get('/', () => {
// ...
});
accountsRouter.get('/:id', () => {
// ...
});
topics:
examples for roles and permissions on a news site:
typical auth flow with tokens:
session tokens are sent from the client to a server in HTTP headers:
example on the client: logging in via username and password and getting the token:
const loginRes = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'foo',
password: 'bar',
}),
});
const loginData = await loginRes.json();
const token = loginData.token;
example on the client: accessing some data via the token:
const notesRes = await fetch('/api/notes', {
headers: { Authorization: `Bearer ${token}` },
});
const notes = await notesRes.json();
plain data:
name | password
--------+--------------------
alice | 123456
bob | 123456
charlie | abc123
data with hashed passwords:
name | password hash
--------+---------------------------------
Alice | e10adc3949ba59abbe56e057f20f883e
Bob | e10adc3949ba59abbe56e057f20f883e
Charlie | e99a18c428cb38d5f260853678922e03
hashing algorithms - sorted from most secure to not secure:
app.post('/api/login', async (req, res) => {
const success = await authService.validateLogin(
req.body.username,
req.body.password
);
if (success) {
const token = authService.createRandomToken();
await authService.saveSession(req.body.username, token);
res.json({ token: token });
} else {
res.status(401).send();
}
});
app.get('/api/notes/:id', async (req, res) => {
const authHeader = req.header('Authorization');
const token = authHeader.split(' ')[1];
const session = await authService.getSession(token);
if (!session) {
// not authenticated
res.status(401).send();
return;
}
const note = await notesService.getNote(req.params.id);
const role = await authService.getRole(session);
if (role === 'user' && session.userId === note.userId) {
res.json(note);
} else if (role === 'admin') {
res.json(note);
} else {
// not authorized
res.status(403).send();
}
});
credentials (e.g. for databases) 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)
hosting providers with free options: