In questo articolo vedremo come implementare una semplice API REST senza usare ExpressJS per dimostrare come questo approccio sia poco pratico.

Definiamo il modello di dati in Mongoose:

'use strict';

const mongoose  = require('mongoose');

const { Schema }  = mongoose;

const PostSchema = new Schema({
    title: String,
    description: String

},{collection: 'posts'});

module.exports = mongoose.model('posts', PostSchema);

Poichè non avremmo accesso all'oggetto request.body, dobbiamo implementare da zero una funzione helper per i metodi HTTP POST e PUT.

'use strict';

const body = req => {
    return new Promise((resolve, reject) => {
        try {
            let data = [];

            req.on('data', chunk => {
                data.push(chunk);
            });

            req.on('end', () => {
                resolve(JSON.parse(Buffer.concat(data).toString()));
            });

            req.on('error', err => {
                throw err;
            });
        } catch (err) {
            reject(err);
        }
    })
};

module.exports = {
    body
};

In pratica il flusso di dati della richiesta viene assemblato fino ad ottenere una stringa che verrà convertita in un oggetto JSON.

A questo punto possiamo creare il controller che gestirà le route.

'use strict';

const Post = require('../models/post');
const { body } = require('../lib');

class PostController {
    async index(req, res) {
        try {
            const posts = await Post.find();
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(posts));
        } catch(err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: err }));
        }
    }

    async single(req, res, id) {
        try {

            const post = await Post.findById(id);
            if(!post) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Not Found' })); 
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(post));
            }

        } catch(err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: err }));
        }
    }

    async create(req, res) {
        try {
            const data = await body(req);
            const newPost = await new Post(data).save();

            res.writeHead(201, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(newPost));
        } catch(err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: err }));
        }
    }

    async update(req, res, id) {
        try {
            const post = await Post.findById(id);

            if(!post) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Not Found' })); 
            } else {
                const data = await body(req);
                const updatedPost = await Post.findByIdAndUpdate(id, data);

                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(updatedPost));
            }
        } catch(err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: err }));
        }
    }

    async remove(req, res, id) {
        try {
            const post = await Post.findById(id);

            if(!post) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Not Found.' })); 
            } else {
                const removedPost = await Post.findByIdAndRemove(id);

                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(removedPost));
            }
        } catch(err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: err }));
        }
    }
};

module.exports = PostController;

Poichè non disponiamo del metodo middleware response.json() di ExpressJS, dobbiamo manualmente servire JSON impostando l'header HTTP corretto e usando JSON.stringify().

A questo punto dobbiamo definire le route dell'applicazione. Non disponendo delle utility di ExpressJS, dobbiamo far ricorso alle espressioni regolari sulla proprietà request.url e ad un confronto sulla proprietà request.method per stabilire come rispondere alla richiesta in base al verbo HTTP usato.

'use strict';

module.exports = (req, res, controller) => {
    const { url, method } = req;

    if(url === '/api/posts' && method === 'GET') {
        return controller.index(req, res);
    }

    if(/\/api\/posts\/[a-z0-9]+/.test(url) && method === 'GET') {
        const id = req.url.split('/')[3];
        return controller.single(req, res, id);
    }

    if(url === '/api/posts' && method === 'POST') {
        return controller.create(req, res);
    }

    if(/\/api\/posts\/[a-z0-9]+/.test(url) && method === 'PUT') {
        const id = req.url.split('/')[3];
        return controller.update(req, res, id);
    }

    if(/\/api\/posts\/[a-z0-9]+/.test(url) && method === 'DELETE') {
        const id = req.url.split('/')[3];
        return controller.remove(req, res, id);
    }

    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not Found' }));
};

Come si può notare, il ricorso ai blocchi if può solo essere mitigato usando il costrutto return per evitare inutili blocchi else.

La nostra applicazione avrà questa struttura finale:

'use strict';

const http = require('http');
const mongoose = require('mongoose');
const PORT = process.env.PORT || 3000;
const PostController = require('./controllers/PostController');
const postRoutes = require('./routes/posts');
const dbURL = 'mongodb://127.0.0.1:27017/database';

mongoose.connect(dbURL, {useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false });

const app = http.createServer((req, res) => {
    postRoutes(req, res, new PostController());
}).listen(PORT);

Conclusione

Come si può notare, usando questa soluzione non si possono evitare alcune notevoli ridondanze nella scrittura del codice. Una soluzione di questo tipo non è scalabile, in quanto qualora dovesse aumentare il livello di complessità delle API, il codice richiesto aumenterebbe notevolmente.