Cos’è Express.js?
Express.js è fondamentalmente un framework minimale per Node.js che ti permette di costruire applicazioni web e API. È stato creato per semplificare il processo di creazione di server web in JavaScript, cosa che con Node.js puro sarebbe molto più verbosa e complicata.
La cosa che lo rende così popolare è la sua semplicità e flessibilità. Non ti impone una struttura rigida come altri framework, ma ti dà gli strumenti necessari per costruire quello che vuoi, come vuoi. È un po’ come avere un set di mattoncini Lego invece di un modello già assemblato.
Se lo confrontiamo con altri framework come Koa, Fastify o NestJS, Express risulta essere più semplice e diretto. Koa è in realtà creato dagli stessi sviluppatori di Express e offre un approccio più moderno con l’uso di async/await. Fastify è ottimizzato per la performance ed è più veloce di Express in molti benchmark. NestJS invece è un framework completo che utilizza TypeScript e si ispira ad Angular, quindi è più strutturato e adatto a progetti enterprise.
Prerequisiti
Prima di tuffarti in Express, dovresti avere una conoscenza base di JavaScript e soprattutto di Node.js. Non devi essere un esperto, ma dovresti sapere come funzionano le callback, le promise e magari anche async/await.
È anche importante avere familiarità con i concetti HTTP e con le API REST. Devi capire cosa sono i metodi HTTP come GET, POST, PUT e DELETE, e come funzionano le richieste e le risposte HTTP.
1. Installazione e Configurazione Iniziale
Setup di un progetto Express.js
Iniziare con Express è davvero semplice. Prima di tutto devi creare un nuovo progetto Node.js:
mkdir il-mio-progetto-express
cd il-mio-progetto-express
npm init -y
Poi installi Express:
npm install express
E puoi creare un file app.js
con un server base:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Ciao Mondo!');
});
app.listen(port, () => {
console.log(`Server in ascolto sulla porta ${port}`);
});
Ecco fatto! Hai appena creato un server web che risponde “Ciao Mondo!” quando visiti la homepage.
Struttura di un progetto Express.js
Express non ti impone una struttura specifica, ma ci sono alcune convenzioni che è bene seguire. Una struttura comune è quella basata sul pattern MVC (Model-View-Controller):
/progetto
/controllers
/models
/views
/routes
/middlewares
/public
app.js
package.json
Per progetti più complessi, puoi utilizzare express-generator
, uno strumento che crea automaticamente la struttura di base:
npm install -g express-generator
express nome-progetto
2. Routing in Express.js
Definizione delle Route
Il routing è uno degli aspetti fondamentali di Express. Le route ti permettono di definire come l’applicazione risponde alle richieste del client.
// Route GET semplice
app.get('/utenti', (req, res) => {
res.send('Lista degli utenti');
});
// Route POST
app.post('/utenti', (req, res) => {
res.send('Utente creato');
});
// Route con parametro
app.get('/utenti/:id', (req, res) => {
res.send(`Dettagli dell'utente ${req.params.id}`);
});
Middleware nelle Route
I middleware sono funzioni che hanno accesso all’oggetto della richiesta (req
), all’oggetto della risposta (res
) e alla funzione next
nel ciclo richiesta-risposta. Possono eseguire codice, modificare gli oggetti req e res, terminare il ciclo o chiamare il middleware successivo.
// Middleware di logging
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
// Middleware per una route specifica
app.get('/admin', (req, res, next) => {
// Verifica se l'utente è autenticato
if (!req.isAuthenticated()) {
return res.redirect('/login');
}
next();
}, (req, res) => {
res.send('Pannello di amministrazione');
});
Router Modulari
Per organizzare meglio il codice, Express ti permette di creare router modulari:
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('Lista degli utenti');
});
router.get('/:id', (req, res) => {
res.send(`Dettagli dell'utente ${req.params.id}`);
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/utenti', usersRouter);
3. Gestione delle Richieste e Risposte
Request (req) e Response (res)
Gli oggetti req
e res
contengono tutte le informazioni sulla richiesta e i metodi per inviare la risposta.
app.get('/prodotti', (req, res) => {
// req.params: parametri della route
// req.query: parametri della query string (?nome=valore)
// req.body: corpo della richiesta (per POST, PUT, ecc.)
// Inviare una risposta
res.send('Testo semplice');
// oppure
res.json({ nome: 'Prodotto', prezzo: 19.99 });
// oppure
res.status(404).send('Non trovato');
});
Gestione dei Dati con Express.js
Per gestire i dati inviati nelle richieste, devi utilizzare dei middleware di parsing:
// Per parsing application/json
app.use(express.json());
// Per parsing application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// Per upload di file
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
res.send('File caricato');
});
4. Middleware Avanzati
Middleware di Terze Parti
Ci sono molti middleware utili sviluppati dalla community:
// Morgan per il logging delle richieste
const morgan = require('morgan');
app.use(morgan('dev'));
// Helmet per migliorare la sicurezza
const helmet = require('helmet');
app.use(helmet());
// CORS per gestire le richieste cross-origin
const cors = require('cors');
app.use(cors());
Scrivere Middleware Custom
Puoi scrivere i tuoi middleware personalizzati:
// Middleware di autenticazione con JWT
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
// Middleware per la gestione degli errori
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Qualcosa è andato storto!');
});
5. Template Engine e Viste
Configurazione di Pug/EJS/Handlebars
Express-js può essere configurato per utilizzare dei template engine come Pug, EJS o Handlebars:
// Configurazione di EJS
app.set('view engine', 'ejs');
app.set('views', './views');
// Rendering di una vista
app.get('/', (req, res) => {
res.render('index', { titolo: 'Homepage', messaggio: 'Benvenuto' });
});
Esempio pratico con EJS
Un esempio di template EJS (views/index.ejs
):
<!DOCTYPE html>
<html>
<head>
<title><%= titolo %></title>
</head>
<body>
<h1><%= messaggio %></h1>
<ul>
<% for(let i=0; i<articoli.length; i++) { %>
<li><%= articoli[i].nome %></li>
<% } %>
</ul>
</body>
</html>
6. Connessione a Database
Integrazione con MongoDB (Mongoose)
Mongoose è una libreria che semplifica l’interazione con MongoDB:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/il-mio-db', { useNewUrlParser: true, useUnifiedTopology: true });
// Definizione di uno schema
const utenteSchema = new mongoose.Schema({
nome: String,
email: String,
password: String
});
const Utente = mongoose.model('Utente', utenteSchema);
// CRUD operations
app.post('/utenti', async (req, res) => {
try {
const utente = new Utente(req.body);
await utente.save();
res.status(201).send(utente);
} catch (error) {
res.status(400).send(error);
}
});
app.get('/utenti', async (req, res) => {
try {
const utenti = await Utente.find();
res.send(utenti);
} catch (error) {
res.status(500).send(error);
}
});
Utilizzo con SQL (Sequelize o Knex)
Per database SQL, puoi utilizzare Sequelize o Knex:
// Esempio con Sequelize e PostgreSQL
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres'
});
// Definizione di un modello
const Utente = sequelize.define('Utente', {
nome: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
});
// Sincronizzazione con il database
sequelize.sync();
// CRUD operations
app.post('/utenti', async (req, res) => {
try {
const utente = await Utente.create(req.body);
res.status(201).send(utente);
} catch (error) {
res.status(400).send(error);
}
});
7. Autenticazione e Sicurezza
JWT (JSON Web Tokens)
JWT è un metodo popolare per l’autenticazione nelle API:
const jwt = require('jsonwebtoken');
// Login
app.post('/login', async (req, res) => {
// Verifica delle credenziali
const utente = await Utente.findOne({ email: req.body.email });
if (!utente || !utente.verificaPassword(req.body.password)) {
return res.status(401).send('Credenziali non valide');
}
// Generazione del token
const token = jwt.sign({ id: utente._id }, process.env.TOKEN_SECRET, { expiresIn: '1h' });
res.send({ token });
});
// Route protetta
app.get('/profilo', authenticateToken, async (req, res) => {
const utente = await Utente.findById(req.user.id);
res.send(utente);
});
Best Practice per la Sicurezza
Alcune best practice per la sicurezza:
// Protezione da XSS
app.use(helmet());
// Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minuti
max: 100 // limite di 100 richieste per IP
});
app.use(limiter);
// Validazione dei dati
const { body, validationResult } = require('express-validator');
app.post('/utenti',
body('email').isEmail(),
body('password').isLength({ min: 6 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Procedi con la creazione dell'utente
}
);
8. Deployment e Performance
Preparazione per l’ambiente di Produzione
// Variabili d'ambiente
require('dotenv').config();
const port = process.env.PORT || 3000;
// Logging avanzato
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
Ottimizzazione
// Compressione
const compression = require('compression');
app.use(compression());
// Clustering
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
const app = express();
// ... configurazione dell'app
app.listen(port);
console.log(`Worker ${process.pid} started`);
}
Deployment su Piattaforme Cloud
Express.js può essere facilmente deployato su piattaforme come Heroku, Vercel, AWS o Railway. Per esempio, per Heroku basta aggiungere un file Procfile
:
web: node app.js
E poi eseguire:
heroku create
git push heroku main
9. Approfondimenti e Alternative
GraphQL con Express.js
Express può essere integrato con GraphQL:
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
hello: String
}
`);
const root = {
hello: () => 'Hello world!'
};
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}));
Serverless Express.js (AWS Lambda)
Express può essere utilizzato anche in un ambiente serverless:
// handler.js
const serverless = require('serverless-http');
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello Serverless!');
});
module.exports.handler = serverless(app);
Differenze con Fastify e NestJS
Come ho accennato prima, ci sono alcune differenze chiave:
- Fastify è più veloce di Express e ha un supporto integrato per la validazione JSON Schema.
- NestJS è un framework completo costruito sopra Express (o Fastify) che utilizza TypeScript e un’architettura modulare ispirata ad Angular.
Conclusione
Express.js rimane uno dei framework più popolari e versatili per lo sviluppo di applicazioni web con Node.js. La sua semplicità e flessibilità lo rendono adatto sia per piccoli progetti che per applicazioni più complesse.
È particolarmente utile se vuoi avere il controllo completo sulla struttura della tua applicazione e non vuoi essere limitato da framework troppo opinionati.
Per approfondire, puoi consultare la documentazione ufficiale di Express, seguire tutorial su piattaforme come Udemy o MDN, e partecipare alla community su GitHub o Stack Overflow.
Express non è sempre la scelta migliore per tutti i progetti. Se hai bisogno di prestazioni estreme, potresti considerare Fastify. Se preferisci un framework più strutturato con TypeScript, NestJS potrebbe essere più adatto. Ma per la maggior parte dei casi d’uso, Express offre un ottimo equilibrio tra semplicità e potenza.