In questo articolo vedremo come implementare delle API in XML con ExpressJS.

La scelta di XML risiede nel fatto che non tutti i linguaggi alla base di implementazioni web supportano JSON con la stessa facilità di JavaScript. Per garantire quindi la massima compatibilità tra piattaforme e per evitare di costringere altri sviluppatori a dover installare moduli aggiuntivi, XML è storicamente un'ottima soluzione.

Il modo più semplice per trasformare dei dati ottenuti da una fonte (database, file, network eccetera) in XML è quello di serializzare tali dati come stringhe che andranno a comporre il documento XML finale. XML infatti è un sottotipo del formato testo e se viene servito come text/xml è visualizzabile nel browser proprio come JSON (application/xml è altrettanto valido).

Creiamo quindi una classe per generare un documento XML.


'use strict';

class XMLWriter {
    constructor({ root, data, itemName}) {
        this.root = root;
        this.data = data;
        this.itemName = itemName;
    }

    hasSpecialChars(str) {
        return /<|>|&/.test(str);
    }

    write() {
        let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
        xml += `<${this.root}>` + '\n';

        this.data.forEach(item => {
            xml += `<${this.itemName}>` + '\n';

            const keys = Object.keys(item);

            for(let key of keys) {
                let content = item[key];
                xml += `<${key}>`;
                if(this.hasSpecialChars(content)) {
                    xml += `<![CDATA[${content}]]>`; 
                } else {
                    xml += `${content}`; 
                }
                xml += `</${key}>` + '\n';
            }

            xml += `</${this.itemName}>` + '\n';
        });

        xml += `</${this.root}>`;

        return xml;
    }
}

module.exports = XMLWriter;

Il metodo write() assembla una stringa XML iterando su un array di dati passato al costruttore della classe. Tali dati sono oggetti, quindi il metodo estrae i nomi delle proprietà da ogni oggetto e li usa per creare gli elementi XML corrispondenti. Se nel contenuto di un elemento sono presenti caratteri che hanno una valenza speciale in XML, tale contenuto viene inserito in un blocco CDATA.

Le route delle nostre API dovranno avere tutte il corretto Content-Type per servire XML. Possiamo sfruttare in tal senso la funzionalità di middleware dell'oggetto Router di ExpressJS.


'use strict';

const express = require('express');
const router = express.Router();
const data = require('../data');
const XMLWriter = require('../classes/XMLWriter');

router.use((req, res, next) => {
    res.set('Content-Type', 'text/xml');
    next();
});

//...

module.exports = router;

I nostri endpoint useranno la classe XMLWriter per inviare il corretto output al client.


router.get('/posts', (req, res, next) => {
   const posts = data.slice(0, 3);
   const writer = new XMLWriter({ root: 'posts', data: posts, itemName: 'post'} );
   res.send(writer.write());
});

router.get('/posts/:id', (req, res, next) => {
    const { id } = req.params;
    const post = data.find(d => { return d.id === id; });
    if(post) {
        const writer = new XMLWriter({ root: 'posts', data: [post], itemName: 'post'} );
        res.send(writer.write());
    } else {
        res.status(404);
        res.send(new XMLWriter({ root: 'errors', data: [{ status: 404 }], itemName: 'error'} ).write());
    }
 });

Infine colleghiamo queste route all'endpoint radice /api nella nostra applicazione.


const apiRoutes = require('./routes/api');

app.use('/api', apiRoutes);

Demo

Heroku

Codice sorgente

GitHub