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
Ajoutons d’autres actions: CRUD (Create, Read, Update, Delete) à notre router.
Rappelons les données contenues dans notre table api
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
Une ligne a bien été ajoutée dans la table
… 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”
Observons la ligne enregistrée
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 …
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
UPDATE
Modifions maintenant le champ “name” de ligne suivante
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
Et dans la table, on voit bien la ligne modifiée.
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
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
Sources: https://github.com/rabehasy/nodejs-express/tree/step2
Ce post est publié aussi sur Medium