expressjs — Api Rest avec sequelize


Cet article est la suite d’un article sur la connexion à une base de données grâce à Sequelize. Dans cet article, notre objectif est de créer une API (Application Programming Interface) en REST (REpresentational State Transfer).

API en JSON

On modifie app.js et on ajoute

// Api rest Controllers
var apiRestRouter = require('./routes/rest/api');

Pour définir l’url /api, on ajoute également ceci dans le même fichier

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

On crée ensuite le fichier routes/rest/api.js. Le contenu du fichier sera comme suit

var express = require('express');

const db = require('../../models');

var router = express.Router();

/**
 * @GET /api/
 */
router.get('/', function(req, res, next) {
    db.api.findAll().then(apis => res.json({
        error: false,
        data: apis,
    }))
    .catch(error => res.json({
        error: true,
        data: [],
        error: error
    }));
});

module.exports = router;

On lance ensuite POSTMAN . On va à l’URL http://localhost:3000/api

img

Ajoutons d’autres actions: CRUD (Create, Read, Update, Delete) à notre router.

Rappelons les données contenues dans notre table api

img

CREATE

passons à la création d’une entrée dans cette table. Ajoutons le code suivant.

/**
 * @POST /api/
 */
router.post('/', function(req, res, next) {
    const {
        name,
        created_at,
        updated_at
    } = req.body;

    db.api.create({
        name: name,
        created_at: created_at,
        updated_at: updated_at
    })
    .then(api => res.status(201).json({
        error: false,
        data: api,
        message: 'New api created.'
    }))
    .catch(error => res.json({
        error: true,
        data: [],
        error: error
    }));
});

Et dans postman, appelons l’url comme ceci

img

Une ligne a bien été ajoutée dans la table

img

… mais, les dates “created_at” et “updated_at” enregistrées dans la table ne correspondent pas aux données que j’ai envoyées c’est-à-dire 2018–06–18 00:00:00 mais à la date de l’appel de l’URL. Comment forcer les dates ?

C’est simple, on retourne dans le model models/api.js et on ajoute timestamp: false

{
  tableName: 'api',
  underscored: true,
  timestamps: false
}

On refait le test en modifiant légèrement le champ “name

img

Observons la ligne enregistrée

img

La date donnée a bien été enregistrée avec l’information que nous lui avons transmise 😉

READ

On sait déja comment récupérer toutes les lignes. On va maintenant récupérer une ligne dans la table. Cette ligne par exemple …

img

On ajoute le code suivant :

/**
 * @GET /api/1
 */
router.get('/:id', function(req, res, next) {
    const apiId = req.params.id;
    db.api.findByPk(apiId).then(api => res.json({
        error: false,
        data: api,
    }));
});

Autre variante de l’appel avec le même résultat: (pratique pour chercher un autre champ que “id”)

db.api.findOne({ where: { id: apiId } }).then(api => res.json({
    error: false,
    data: api,
}));

On a le résultat suivant

img

UPDATE

Modifions maintenant le champ “name” de ligne suivante

img

en “Android Flutter

On va modifier le fichier routes/rest/api.js et ajouter le code suivant :

router.put('/:id', function(req, res, next) {

    const apiId = req.params.id;

    const {
        name,
        created_at,
        updated_at
    } = req.body;

    db.api.update({
        name: name,
        created_at: created_at,
        updated_at: updated_at
    },{
        where: {
            id: apiId
        }
    })
    .then(api => res.status(201).json({
        error: false,
        message: 'Api updated.'
    }))
    .catch(error => res.json({
        error: true,
        error: error
    }));
});

Dans postman, on a ceci

img

Et dans la table, on voit bien la ligne modifiée.

img

DELETE

On veut maintenant supprimer une ligne.

Le code est similaire à UPDATE (PUT).

/**
 * @DELETE /api/1 - Delete
 */
router.delete('/:id', function(req, res, next) {
    const apiId = req.params.id;

    db.api.destroy({ where: { id: apiId } })
        .then(api => res.json({
            error: false,
            message: 'Api deleted.'
        }))
        .catch(error => res.json({
            error: true,
            error: error
        }));
});

Dans postman

img

Et voilà. On a fait le tour des méthodes.

GET avec query complexes

Dans la vraie vie d’une application, il est rare est de vouloir afficher une liste simple d’une table dans une base de données.

Imaginons le cas où on voulait avoir une page avec des lignes contenant le nom “desk” ou ne récupérer que 2 lignes par page ou une liste triée par date, ou de combiner plusieurs conditions…

Pour celà, il serait non productif de créer une fonction par “requête”. Nous allons nous débrouiller pour mettre tout ça dans une même route qui est:

router.get('/', function(req, res, next) {

Pour ajouter un opérateur du type “LIKE” à une requête, Sequelize nous impose d’ajouter la variable Op. On va donc ajouter au début:

const Sequelize = require('sequelize');
const Op = Sequelize.Op;

dans la fonction get(), on ajoute

// queryStrings
let { name, order, sort, limit, offset } = req.query;

Ceci permet de restreindre la liste des “queryString” autorisées. Grâce à cette variable, on sera limité aux query strings suivants :

http://localhost:3000/api?name=&order=&sort=&limit=&offset=

  • name: recherche dans le champ “name” de la table api
  • order: le nom du champ de la table à trier.
  • sort: l’ordre du tri : (asc ou desc)
  • limit: le nombre d’enregistrement à retourner
  • offset: le offset

On définit une variable qui sera alimentée au fur et à mesure des query strings passés

let paramQuerySQL = {};

On donne ensuite une valeur par défaut à la variable “sort” si elle est vide. Cette valeur sera “ASC” pour “ASCENDING order”.

// sort par defaut si param vide ou inexistant
if (typeof sort === 'undefined' || sort == '') {
    sort = 'ASC';
}

On force la valeur de la variable “sort” à ASC si la valeur est remplie mais incorrecte. En effet, en SQL un tri accepte uniquement 2 valeurs: ASC et DESC.

// Si sort n'est pas vide et n'est ni asc ni desc
if (typeof sort !== 'undefined' && !['asc','desc'].includes(sort.toLowerCase())) {
    sort = 'ASC';
}

Si le query string name existe et est rempli ==> on fait une recherche LIKE

// Recherche LIKE '%%'
if (name != '' && typeof name !== 'undefined') {
    paramQuerySQL.where = {
            name: {
                [Op.like]: '%' + name + '%'
            }
        }

}

Si le query String order est rempli

// order by
if (order != '' && typeof order !== 'undefined' && ['name'].includes(order.toLowerCase())) {
    paramQuerySQL.order = [
        [order, sort]
    ];
}

Si le query String limit est un entier supérieur à 0.

// limit
if (limit != '' && typeof limit !== 'undefined' && limit > 0) {
    paramQuerySQL.limit = parseInt(limit);
}

Si le query String offset est un entier supérieur à 0.

// offset
if (offset != '' && typeof offset !== 'undefined' && offset > 0) {
    paramQuerySQL.offset = parseInt(offset);
}

On a maintenant créé l’ensemble des possibilités dont on a besoin. On modifie un peu en utilisant findAndCountAll au lieu de findAll car on veut récupérer le nombre total de lignes retournées par notre requête.

db.api.findAndCountAll(paramQuerySQL).then(apis => res.json({
    error: false,
    count: apis.count,
    data: apis.rows,
}))

Voici un exemple d’URL http://localhost:3000/api?order=name&sort=DESC&limit=1&offset=2 . Cette URL se traduit par : retourne moi une ligne (limit=1) à partir de la 2ème ligne (offset=2) et ordonne cette liste par name (order=name) de façon décroissante (sort=DESC).

et le résultat dans postman

img

Sources: https://github.com/rabehasy/nodejs-express/tree/step2

Ce post est publié aussi sur Medium