In questo articolo vedremo come trasformare un blog in WordPress in un blog in Node.js con ExpressJS e MySQL mantenendo il database originale.

Per MySQL utilizzeremo il modulo NPM mysql.


npm install express ejs helmet mysql --save

La struttura di base della nostra applicazione sarà la seguente:


'use strict';

const path = require('path');
const express = require('express');
const app = express();
const helmet = require('helmet');
const port = process.env.PORT || 3000;
const { locals } = require('./config');

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

app.locals = locals;

app.disable('x-powered-by');
app.set('view engine', 'ejs');

app.use(helmet());
app.use('/public', express.static(path.join(__dirname, 'public')));
app.use('/', routes);


app.listen(port);

app.locals definisce le variabili comuni alle nostre view che abbiamo salvato nel file config.js.


'use strict';

module.exports = {
    locals: {
        siteName: 'Gabriele Romanato',
        siteDescription: 'Just another WordPress blog'
    }
};

Prima di definire le nostre route dobbiamo creare la connessione al database ed un metodo di utility per le query.


'use strict';

const mysql = require('mysql');
    
const params = {
    host: 'localhost',
    user: 'username',
    password: 'password',
    database: 'database'
};
    
const connection = mysql.createConnection(params);
    
connection.connect();
    
const query = (queryString, values = []) => {
    return new Promise((resolve, reject) => {
        connection.query(queryString, values, (err, results, fields) => {
            if(err) {
                reject(err);
            }
            resolve(results);
        });
    });
};
    
module.exports = {
    query
};

La route della home page reperirà gli ultimi tre post dal database ordinandoli per data di pubblicazione. Se è presente la variabile GET s della ricerca, la query iniziale verrà modificata aggiungendo una clausola LIKE che cercherà una corrispondenza nel titolo dei post.


'use strict';

const express = require('express');
const router = express.Router();
const {query} = require('../db');

router.get('/', async (req, res, next) => {
    const { s } = req.query;
    let queryString = "SELECT * FROM wp_posts WHERE post_type = ? AND post_status = ? ORDER BY post_date DESC LIMIT 3";
    let values = ['post', 'publish'];
    if(s) {
        let like = `%${s}%`;
        queryString = "SELECT * FROM wp_posts WHERE post_type = ? AND post_status = ? AND post_title LIKE N? ORDER BY post_date DESC LIMIT 3";
        values = ['post', 'publish', like];
    }
    try {
        const results = await query(queryString, values);
        const { siteName, siteDescription } = req.app.locals;
        const title = s ? `Search Results | ${siteName}` : `${siteName} | ${siteDescription}`;
        res.render('themes/twentytwenty/index', {
            title,
            posts: results
        });
    } catch(err) {
        res.sendStatus(500);
    }
});

module.exports = router;

Il funzionamento delle query al database è simile alla modalità vista con PHP per quanto riguarda le prepared query tramite caratteri segnaposto. In questo caso il carattere ? viene sostituito con i valori presente nell'array che segue la query dopo averne effettuato l'escaping al fine di prevenire tentativi di SQL injection.

La view della home terrà conto degli eventuali risultati della ricerca.


<%- include('header') %>

<main id="site-content" role="main">
    <% if(posts.length > 0) { %>
    <% posts.forEach(post => { %>
    <%- include('loop', { post: post }) %>
    <% }); %>
    <% } else { %>
        <article class="post">
    <header class="entry-header">
        <div class="entry-header-inner section-inner medium">
            <h1 class="entry-title heading-size-1">
                No Results
            </h1>
            <figure class="featured-media">
                <div class="featured-media-inner section-inner medium">
                    <img src="/public/themes/twentytwenty/assets/images/2020-three-quarters-1.png" alt="">
                </div>
            </figure>
            <div class="post-inner thin">
                <div class="entry-content">
                    <%- include('searchform') %>
                </div>
            </div>
        </div>
    </header>
</article>
        
    <% } %>
</main>

<%- include('footer') %>

Ciascun post all'interno del loop viene gestito da un template specifico a cui viene passato l'oggetto post tramite la funzione include().


<article class="post">
    <header class="entry-header">
        <div class="entry-header-inner section-inner medium">
            <h2 class="entry-title heading-size-1">
                <a href="/<%= post.post_name %>"><%= post.post_title %></a>
            </h2>
            <figure class="featured-media">
                <div class="featured-media-inner section-inner medium">
                    <img src="/public/themes/twentytwenty/assets/images/2020-three-quarters-1.png" alt="">
                </div>
            </figure>
            <div class="post-inner thin">
                <div class="entry-content">
                    <%= post.post_excerpt %>
                </div>
                <div class="wp-block-button is-style-outline">
                    <a class="wp-block-button__link" href="/<%= post.post_name %>">Read More</a>
                </div>
            </div>
        </div>
    </header>
</article>

Per quanto riguarda la gestione della route del singolo post, possiamo sfruttare il campo post_type di ciascun post per creare delle view in base al tipo di post, come ad esempio single-post.ejs, single-page.ejs e così via.


router.get('/:slug', async (req, res, next) => {
    const { slug } = req.params;
    try {
        const results = await query('SELECT * FROM wp_posts WHERE post_name = ?', [slug]);
        const { siteName } = req.app.locals;
        const post = results[0];
        res.render(
            `themes/twentytwenty/single-${post.post_type}`,
        {
            title: `${post.post_title} | ${siteName} `,
            post
        });
    } catch(err) {
        res.sendStatus(500);
    }
});

Codice sorgente

GitHub