In questo articolo vedremo come il pattern MVC si applichi al modello CRUD in ExpressJS.
Il modello CRUD (Create, Read, Update, Delete) definisce le azioni da eseguire su ciascuna risorsa presente.
Come primo passo, definiamo il modello (Model) per la nostra risorsa utilizzando il modulo ODM Mongoose per MongoDB.
'use strict';
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
excerpt: {
type: String,
required: true,
trim: true
},
content: {
type: String,
required: true,
trim: true
}
}, {
timestamps: true
})
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
L'opzione timestamps
definita nella documentazione di Mongoose ci permette di aggiungere al documento i campi createdAt
e updatedAt
di tipo Date
che hanno uno scopo analogo a quelli di Laravel, ossia salvare la data di creazione e di aggiornamento di una risorsa.
A questo punto possiamo definire il Controller che gestirà le richieste HTTP e le view.
'use strict';
const Post = require('../models/post');
class PostController {
}
module.exports = PostController;
Per le View possiamo creare una sottodirectory in views
nominandola come la nostra risorsa ma al plurale. Nel nostro caso avremo views/posts
. Il nome di ciascuna view, se richiesta, sarà coerente con quello dei metodi del controller.
Ora possiamo vedere in dettaglio ciascuno dei quattro verbi che compongono il modello CRUD.
Create
Create è diviso in due richieste HTTP distinte:
GET /posts/create
: qui viene mostrato il form di creazione della risorsa.POST /posts
: qui viene effettivamente creata una nuova risorsa.
Definiamo quindi le azioni nel controller:
class PostController {
static create(req, res) {
res.render('posts/create');
}
static store(req, res) {
try {
const post = new Post(req.body);
post.save();
res.status(201).render('posts/create', { post });
} catch(error) {
res.status(400).render('errors/400', { error } );
}
}
}
Creiamo quindi le nostre route.
'use strict';
const express = require('express');
const router = express.Router();
const PostController = require('../controllers/PostController');
router.get('/posts/create', PostController.create);
router.post('/posts', PostController.store);
module.exports = router;
Read
Questo verbo viene implementato con due azioni distinte:
GET /posts
: restituisce l'elenco dei documenti della risorsa specificata.GET /posts/:id
: restituisce un singolo documento utilizzando l'identificativo:id
.
che si concretizzano nei seguenti metodi del controller:
class PostController {
static async index(req, res) {
try {
const posts = await Post.find();
res.render('posts/index', { posts });
} catch(error) {
res.status(500).render('errors/500', { error } );
}
}
static async show(req, res) {
const { id } = req.params;
try {
const post = await Post.findById(id);
res.render('posts/show', { post });
} catch(error) {
res.status(404).render('errors/404', { error } );
}
}
}
Le nostre route saranno le seguenti:
router.get('/posts', PostController.index);
router.get('/posts/create', PostController.create);
router.get('/posts/:id', PostController.show);
router.post('/posts', PostController.store);
Update
Questo verbo viene implementato con due azioni distinte:
GET /posts/:id/edit
: mostra il form per la modifica della risorsa identificata tramite:id
.PUT|PATCH /posts/:id
: aggiorna la risorsa specificata tramite:id
.
che si concretizzano nei seguenti metodi del controller:
class PostController {
static edit(req, res) {
const { id } = req.params;
res.render('posts/edit', { id });
}
static async update(req, res) {
const { id } = req.params;
try {
const post = await Post.findByIdAndUpdate(id, req.body);
res.render('posts/edit', { post });
} catch(error) {
res.status(400).render('errors/400', { error } );
}
}
}
Le nostre route saranno le seguenti:
router.get('/posts', PostController.index);
router.get('/posts/create', PostController.create);
router.get('/posts/:id', PostController.show);
router.get('/posts/:id/edit', PostController.edit);
router.post('/posts', PostController.store);
router.put('/posts/:id', PostController.update);
Delete
Quest'ultimo verbo viene implementato con l'azione DELETE /posts/:id
ed elimina la risorsa specificata tramite :id
.
È l'unico caso in cui non è necessaria una view.
Il metodo del controller è il seguente:
class PostController {
static async destroy(req, res) {
const { id } = req.params;
try {
const post = await Post.findOneAndDelete({ _id: id });
res.send(post);
} catch(error) {
res.status(500).send(error);
}
}
}
Le nostre route saranno le seguenti:
router.get('/posts', PostController.index);
router.get('/posts/create', PostController.create);
router.get('/posts/:id', PostController.show);
router.get('/posts/:id/edit', PostController.edit);
router.post('/posts', PostController.store);
router.put('/posts/:id', PostController.update);
router.delete('/posts/:id', PostController.destroy);
Conclusione
Il modello MVC si rivela essere molto utile nell'implementazione del modello CRUD in ExpressJS. Cambiando semplicemente il tipo di view usate, possiamo adattarlo a diversi casi d'uso.