NodeJs et ExpressJs : Générer des PDF contenant des tableaux avec pdfmake

Si l’on souhaite générer des PDF « riches » sur un projet utilisant NodeJs et ExpressJs, en particulier des PDF affichant des tableaux, le plus simple est d’utiliser pdfmake. On commence donc par installer la dépendance :

npm install pdfmake

La suite est juste un exemple concret d’utilisation sur une URL affichant directement le document PDF. Car je n’ai pas trouvé la documentation claire à ce sujet. Une fois que vous avez cette base il devient simple de mettre en page les éléments comme on le souhaite en utilisant les exemples de la documentation

router.get('/pdf', function (req, res) {
    //on importe les font à utiliser, voir https://pdfmake.github.io/docs/fonts/standard-14-fonts/ pour les font disponible par défaut
    var fonts = {Helvetica: {normal: 'Helvetica', bold: 'Helvetica-Bold', italics: 'Helvetica-Oblique', bolditalics: 'Helvetica-BoldOblique'}, };
    //exemple de contenu du PDF
    var dd = {
        //les differents contenus
        content: [
            {
                //en premier 2 colonnes contenant une imag et un texte
                columns: [
                    {image: './chemin/vers/mon-image.jpg', width: 100},//attention si l'image n'existe pas ça plante
                    //un texte avec différents style
                    {text: [
                            'MON TITRE ',
                            {text: 'N° 999', color: 'red'}//on indique directement la proriété de style color
                        ],
                        style: 'titleStyle'//style définis plus bas
                    }
                ]
            },
            '\n\n', //sauts de ligne
            {text: 'titre du tableau', style: 'tableHeader'},
            {style: 'tableExample',
                table: {
                    body: [
                        [{text: 'titre de colonne 1', style: 'tableHeader'}, {text: 'titre de colonne 2', style: 'tableHeader'}, ],
                        ['contenus de ma colonne', 'un autre contenu'],
                    ]
                }
            },
        ],
        //les différents styles à appliquer
        styles: {
            tableExample: {margin: [0, 5, 0, 15]},
            tableHeader: {bold: true, fontSize: 12, color: 'black'},
            titleStyle: {bold: true, fontsize: 14, alignment: 'right'}
        },
        defaultStyle: {font: 'Helvetica', columnGap: 20}
    };
    //on apelle pdfmake
    var PdfPrinter = require('pdfmake/src/printer');
    //avec les fonts à utiliser
    var printer = new PdfPrinter(fonts);
    //on passe notre contenu
    let pdf = printer.createPdfKitDocument(dd);
    //on termine et on envoie les bon headers
    pdf.pipe(res);
    pdf.end();
    res.statusCode = 200;
    res.setHeader('Content-type', 'application/pdf');
});

NodeJs : Exemples d’utilisation avec l’ORM Sequelize

Sequelize est un ORM pour node.js compatible avec différents moteurs de base de données comme Mysql, Sqlite…etc.
Dans cet article nous allons survoler quelques cas pratiques permettant de définir le modèle de la base, et d’effectuer des requêtes dessus.

Une fois installé nous allons commencer à structurer notre base de données en créant les différents objets la composant. Tout sera situé dans un fichier « Model.js » avec pour commencer les informations de connexion à la base (ici de type mysql) :

var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
logging: false,//passer a true pour voir les différentes requêtes effectuées par l'ORM
});
//on exporte pour utiliser notre connexion depuis les autre fichiers.
var exports = module.exports = {};
exports.sequelize = sequelize;

On va pouvoir créer notre modèle, partons sur un cas pratique simple, ou on va avoir des utilisateurs « User » qui ont un rôle « Role ». On déclare les tables et leurs champs de la manière suivante :

/**
 * ROLE
 */

const Role = sequelize.define('role', {
    id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
    name: {type: Sequelize.STRING(255), allowNull: false},
},
        {tableName: 'role', timestamps: false, underscored: true}//par default "tableName" serait "roles" (au pluriel), "timestamps" crée 2 champs automatique pour les dates de création et de modification (très pratique si nécessaire) et "underscored" permet de créer automatiquement des champs de "relation" entre les tables de type "role_id" plutôt que "UserId".
);
exports.Role = Role;

/*
 * USER
 */

const User = sequelize.define('user', {
    id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
    name: {type: Sequelize.STRING(255), allowNull: false, },
    email: {type: Sequelize.STRING(255), allowNull: false, unique: true},
},
        {tableName: 'user', timestamps: false, underscored: true}
);
exports.User = User;

On veux maintenant créer une relation entre ces 2 tables. L’utilisateur à un et un seul rôle. Pour cela il suffit d’ajouter la ligne suivante :

User.belongsTo(Role);//l'utilisateur à un rôle.

Cela va créer un champ « role_id » dans la table « user » (roleId si on laisse la configuration « underscored » à false par default).
Pour générer ces tables on peux utiliser la ligne suivante (tables générées seulement si elles n’existent pas)

sequelize.sync({logging: console.log});

Voyons maintenant comment récupérer nos données à travers ces objets :

    //on importe le modèle
    var Model = require('./Model');
    //recherche de tous les utilisateurs
    Model.User.findAll().then(users => {
        //on récupère ici un tableau "users" contenant une liste d'utilisateurs
        console.log(users);
    }).catch(function (e) {
        //gestion erreur
        console.log(e);
    });

La partie import du modèle et gestion des erreurs restant toujours la même, ci-dessous des exemple plus concis de différentes requêtes.

    //requête avec critère et ordre
    Model.User.findAll({
        where: {role_id: 2}, //on veux uniquement ceux qui ont le role "2"
        order: [['name', 'ASC']] //classer par ordre alphabétique sur le nom
    }).then(users => {
       //traitement terminé...
    });

    //requête d'un utilisateur par son identifiant avec inclusion de la relation "Role"
    let id =  19; //id
    Model.User.findById(id, {
        include: [{model: Model.Role}]
    }).then(user => {
        //on peux directement afficher le nom du rôle de l'utilisateur
        console.log(user.role.name);
    });

    //exemple de création d'un utilisateur, puis de sa suppression dans la foulée. Ce qui permet de voir comment effectuer des requêtes successives.
    Model.User.create({
        name: 'Test',
        email : 'test@testmail.com'
    }).then(user => {
        return user.destroy();
    }).then(destroy => {
        //traitement terminé...
    }).catch(function (e) {
        //gestion erreur
    });

    //exemple de requête d'update d'un utilisateur
    let id = 19;//id
    Model.User.update(
            {name: 'Numa'},
            {where: {id: id}}
    ).then(user => {
        //traitement terminé...
    });

Pour la suite et voir une utilisation un peu plus poussé, nous allons apporter des modifications à notre modèle. Pour l’instant on peux récupérer le rôle d’un utilisateur, mais on voudrait aussi pouvoir faire l’inverse. C’est à dire, à partir d’un rôle connaitre la liste des utilisateurs correspondant. Pour cela il nous faut ajouter une relation « hasMany » :

Role.hasMany(User);

Exemple de requête :

    //recuperations des utilisateurs correspondants au différents rôles
    Model.Role.findAll({include: [{model: Model.User}]}).then(roles => {
        //pour chaque role on peux parcourir la liste des ses utilisateurs
        roles.forEach((role) => {
            console.log(role.name);
            role.users.forEach((user) => {
                console.log(user.name);
            });
        });
    //...
    });

Revenons à nouveau sur notre modèle, et regardons comment indiquer une référence sur la même table. Ici nos utilisateurs « Operator » peuvent avoir d’autre utilisateurs qui sont leurs « Manager ».
On veux donc une clé manager_id sur notre table user. Et comme il sera intéressants de récupérer la liste des « Operators » d’un « Manager » nous créons aussi la liaison inverse « hasMany », Voici comment faire :

User.hasMany(User, {foreignKey: 'manager_id', as: 'Operators'});//l'utilisateur peux avoir des "Operators"
User.belongsTo(User, {foreignKey: 'manager_id', as: 'Manager'});//l'utilisateur peux avoir un "Manager"

Ici lors des « include » dans nos requêtes nous devrons répéter les éléments « as ». Nous allons aussi voir que nous pouvons imbriquer ces « include », et leur appliquer des critères.

    Model.User.findAll({
        include: [
            //j'inclus les roles de mon utilisateur
            {model: Model.Role},
            //mais aussi la liste de ses "Operators" qui ont comme valeur role_id ="1" je récupére également leur propre role.
            {model: Model.User, as: "Operators", where: {role_id: '149999900000000002'}, include: [{model: Model.Role}]}
        ]
    }).then(users => {
        users.forEach((user) => {
            console.log('----');
            console.log(user.name + " : " + user.role.name);
            console.log('----');
            user.Operators.forEach((operator) => {
                console.log(operator.name + " : " + operator.role.name);
            });
        });
    });

On peux bien sur aussi effectuer des requête brutes, et utiliser l’échappement de données dans celles-ci, voici un exemple :

    //parametre à echaper
    let param_name = '%a%';
    let param_role = 1;
    //sql brut
    let sql = 'SELECT (ROUND(id / 5)+123) as nimportequoi FROM user WHERE name LIKE $1 AND role_id > $2';
    Model.sequelize.query(sql, {bind: [param_name, param_role], type: Model.sequelize.QueryTypes.SELECT}).then(results => {
        console.log(results);
    });

Ce sera tout pour ce premier article au sujet de nodeJs et Sequelize !