Nel mondo dello sviluppo software, mantenere ordine, separazione delle responsabilità e scalabilità è fondamentale, soprattutto man mano che un progetto cresce. Uno dei pattern architetturali più utilizzati per ottenere tutto questo è il pattern MVC, acronimo di Model-View-Controller.
Quando sviluppiamo applicazioni web con Node.js ed Express, il pattern MVC ci aiuta a organizzare il codice, separare la logica di business dalla presentazione e rendere il progetto facilmente manutenibile.
In questa guida costruiremo una semplice applicazione usando il pattern MVC con Node.js ed Express: un'applicazione di gestione di utenti (User Manager). Andremo passo passo, spiegando ogni parte del codice e l'organizzazione della struttura.
Cos'è il Pattern MVC?
Il pattern MVC divide la logica in tre componenti principali:
- Model: gestisce i dati e la logica di business.
- View: si occupa dell'interfaccia utente e della presentazione.
- Controller: riceve input, coordina il Model e aggiorna la View.
expressejsbody-parser- Separazione delle responsabilità: ogni parte ha il suo ruolo.
- Riutilizzabilità del codice: il controller può servire diverse view.
- Facilità di test: i modelli possono essere testati indipendentemente.
- Manutenibilità: con la crescita del progetto, l'organizzazione aiuta a non perdere il controllo.
- Il Model si occupa della gestione dei dati, in questo caso in memoria, ma facilmente sostituibile con un database reale come MongoDB o MySQL
- Il Controller funge da intermediario intelligente, ricevendo richieste, processando dati tramite il model e decidendo quale view restituire.
- La View, costruita con EJS, ci permette di mostrare in maniera dinamica i dati, fornendo una UI semplice ma efficace.
- Persistenza dei dati: collega l'app a un database reale (es. MongoDB con Mongoose o PostgreSQL con Sequelize).
- Autenticazione utenti: implementa login, registrazione e gestione delle sessioni con librerie come Passport.js o JWT.
- Validazione dei dati: usa librerie come Joi per assicurarti che i dati ricevuti siano corretti prima di salvarli.
- Refactoring in API REST: separa completamente la logica server dalle view per creare una vera API RESTful da consumare lato frontend.
- Test automatizzati: inizia a scrivere test unitari per il Model e il Controller, migliorando la qualità del codice.
- Deploy: pubblica l'app su un hosting e rendila accessibile online.
Questa separazione favorisce una migliore organizzazione e facilita la collaborazione tra sviluppatori backend e frontend.
Iniziamo il progetto
Creiamo la cartella del progetto:
mkdir user-manager-mvc
cd user-manager-mvc
npm init -y
Installiamo i pacchetti:
npm install express ejs body-parser
Struttura del progetto MVC
Organizziamo il progetto come segue:
user-manager-mvc/
├── app/
│ ├── controllers/
│ │ └── userController.js
│ ├── models/
│ │ └── userModel.js
│ └── views/
│ ├── index.ejs
│ └── users.ejs
├── public/
├── routes/
│ └── userRoutes.js
├── app.js
├── package.json
Il file principale app.js
// app.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const userRoutes = require('./routes/userRoutes');
// Imposta il motore di template EJS
app.set('view engine', 'ejs');
app.set('views', './app/views');
// Middleware per dati POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
// Route principali
app.use('/', userRoutes);
// Avvia il server
app.listen(3000, () => {
console.log('App in esecuzione su http://localhost:3000');
});
// app.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const userRoutes = require('./routes/userRoutes');
// Imposta il motore di template EJS
app.set('view engine', 'ejs');
app.set('views', './app/views');
// Middleware per dati POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
// Route principali
app.use('/', userRoutes);
// Avvia il server
app.listen(3000, () => {
console.log('App in esecuzione su http://localhost:3000');
});Qui stiamo dicendo ad Express di usare EJS
Il Model: userModel.js
// app/models/userModel.js
let users = [];
const User = {
findAll: () => users,
create: (name, email) => {
const newUser = { id: Date.now(), name, email };
users.push(newUser);
return newUser;
},
findById: (id) => users.find(u => u.id === parseInt(id)),
delete: (id) => {
users = users.filter(u => u.id !== parseInt(id));
}
};
module.exports = User;
// app/models/userModel.js
let users = [];
const User = {
findAll: () => users,
create: (name, email) => {
const newUser = { id: Date.now(), name, email };
users.push(newUser);
return newUser;
},
findById: (id) => users.find(u => u.id === parseInt(id)),
delete: (id) => {
users = users.filter(u => u.id !== parseInt(id));
}
};
module.exports = User;Questo model gestisce un array di utenti in memoria. In un'app reale useremmo un database, per semplicità qui usiamo un array.
Controller: userController.js
// app/controllers/userController.js
const User = require('../models/userModel');
exports.home = (req, res) => {
res.render('index');
};
exports.listUsers = (req, res) => {
const users = User.findAll();
res.render('users', { users });
};
exports.createUser = (req, res) => {
const { name, email } = req.body;
User.create(name, email);
res.redirect('/users');
};
exports.deleteUser = (req, res) => {
const { id } = req.params;
User.delete(id);
res.redirect('/users');
};
// app/controllers/userController.js
const User = require('../models/userModel');
exports.home = (req, res) => {
res.render('index');
};
exports.listUsers = (req, res) => {
const users = User.findAll();
res.render('users', { users });
};
exports.createUser = (req, res) => {
const { name, email } = req.body;
User.create(name, email);
res.redirect('/users');
};
exports.deleteUser = (req, res) => {
const { id } = req.params;
User.delete(id);
res.redirect('/users');
};Il controller gestisce le richieste del client e coordina il modello e la view. Notiamo funzioni per la home, la lista, la creazione e l'eliminazione utenti.
Le Routes: userRoutes.js
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../app/controllers/userController');
// Rotte
router.get('/', userController.home);
router.get('/users', userController.listUsers);
router.post('/users', userController.createUser);
router.post('/users/delete/:id', userController.deleteUser);
module.exports = router;
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../app/controllers/userController');
// Rotte
router.get('/', userController.home);
router.get('/users', userController.listUsers);
router.post('/users', userController.createUser);
router.post('/users/delete/:id', userController.deleteUser);
module.exports = router;In questo file separiamo la logica delle route. Ogni URL è associato a una funzione del controller.
Le View: index.ejs
users.ejs
index.ejs
<!DOCTYPE html>
<html>
<head><title>Home</title></head>
<body>
<h1>Benvenuto</h1>
<a href="/users">Gestione utenti</a>
</body>
</html>
Ecco invece quella su users.ejs
<!DOCTYPE html>
<html>
<head><title>Utenti</title></head>
<body>
<h1>Lista Utenti</h1>
<form action="/users" method="POST">
<input type="text" name="name" placeholder="Nome" required>
<input type="email" name="email" placeholder="Email" required>
<button type="submit">Aggiungi</button>
</form>
<ul>
<% users.forEach(user => { %>
<li>
<%= user.name %> - <%= user.email %>
<form action="/users/delete/<%= user.id %>" method="POST" style="display:inline;">
<button type="submit">Elimina</button>
</form>
</li>
<% }) %>
</ul>
<a href="/">Torna alla home</a>
</body>
</html>
Le view sono in EJS e mostrano dinamicamente i dati del model. Vengono renderizzate dal controller.
Test dell'applicazione
node app.js
Visitiamo ora l'indirizzo http://localhost:3000
Vantaggi del Pattern MVC
Cosa abbiamo ottenuto
Abbiamo realizzato insieme un'applicazione semplice ma solida seguendo il pattern MVC con Node.js ed Express. Questo approccio ci ha permesso di organizzare il codice in modo modulare, favorendo chiarezza, riutilizzabilità e manutenibilità. Attraverso il progetto abbiamo toccato con mano i vantaggi pratici dell'architettura MVC:
Seguendo questa struttura, ogni parte del progetto è facilmente modificabile o espandibile senza rompere le altre. Ad esempio, potresti sostituire EJS con un framework frontend come React o Vue, o collegare il model ad un database relazionale o NoSQL con modifiche minime.
E ora? Cosa puoi fare dopo con il pattern MVC?
Quello che hai visto è un punto di partenza solido. Da qui, le possibilità sono tantissime. Eccone alcune:
Il pattern MVC è una colonna portante nello sviluppo web, padroneggiarlo in Node.js ti apre le porte per lavorare su progetti seri, sia personali che professionali. Saper strutturare bene un'app non è solo una buona pratica, ma è anche uno skill apprezzatissimo in ogni team di sviluppo.
Non fermarti qui: continua a esplorare, sperimenta, migliora e.. costruisci!