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:

  1. GET /posts/create: qui viene mostrato il form di creazione della risorsa.
  2. 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:

  1. GET /posts: restituisce l'elenco dei documenti della risorsa specificata.
  2. 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:

  1. GET /posts/:id/edit: mostra il form per la modifica della risorsa identificata tramite :id.
  2. 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.