Il y a 2 années · 14 minutes · Back

Node.js : Créer une API REST avec les outils de Strongloop

L’usage des API REST s’est considérablement démocratisé dans le monde du web. Les applications web, comme les applications mobiles, sont devenues des clients d’API. Aujourd’hui je vous propose de découvrir l’outillage fourni par Strongloop, un acteur reconnu du monde Node.js.

Avec cet outillage, vous verrez qu’il est possible de créer rapidement une API REST, de gérer la sécurité, la supervision et le déploiement sur une machine distante.

Avant de commencer, installons les outils en local. Le seul pré-requis est d’avoir une installation de Node.js. Ensuite, c’est très simple :

npm install -g strongloop

Projet et modèles

Notre objectif est de créer une API pour une clinique vétérinaire. Cette API doit permettre de gérer la liste des clients de la clinique, ainsi que la liste de leurs animaux. Commençons par créer notre projet à l’aide de la commande slc (StrongLoop Controller) qui a été installée via le module Strongloop :

$ slc loopback

     _-----_
    |       |    .--------------------------.
    |--(o)--|    |  Let's create a LoopBack |
   `---------´   |       application!       |
    ( _´U`_ )    '--------------------------'
    /___A___\
     |  ~  |
   __'.___.'__   

 ´   `  |° ´ Y ` 

[?] Enter a directory name where to create the project: veterinary
   create veterinary/
     info change the working directory to veterinary

[?] What's the name of your application? veterinary
   create .editorconfig
   create .jshintignore
   create .jshintrc
   create .npmignore
   create server/boot/authentication.js
   create server/boot/explorer.js
   create server/boot/rest-api.js
   create server/boot/root.js
   create server/server.js
   create client/README.md

I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself.

Ensuite créons le modèle « Customers » pour gérer les clients de la clinique. Un client a un nom, un prénom et numéro de téléphone :

$ slc loopback:model Customer
[?] Enter the model name: Customer
[?] Select the data-source to attach Customer to: db (memory)
[?] Expose Customer via the REST API? Yes
[?] Custom plural form (used to build REST URL): Customers
Let's add some Customer properties now.

Enter an empty property name when done.
[?] Property name: Firstname
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Customer property.
Enter an empty property name when done.
[?] Property name: Lastname
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Customer property.
Enter an empty property name when done.
[?] Property name: Phone
   invoke   loopback:property
[?] Property type: number
[?] Required? Yes

Let's add another Customer property.
Enter an empty property name when done.
[?] Property name:

Pour chacune des propriétés de notre modèle, slc nous permet de préciser son nom, son type et si elles sont obligatoires ou non. Différents types sont disponibles :string, number, boolean, object, array, date, buffer, geopoint.

Créons à présent le modèle « Pets » pour gérer les animaux. Un animal a un nom, une catégorie et une date de naissance :

$ slc loopback:model Pet
[?] Enter the model name: Pet
[?] Select the data-source to attach Pet to: db (memory)
[?] Expose Pet via the REST API? Yes
[?] Custom plural form (used to build REST URL): Pets
Let's add some Pet properties now.

Enter an empty property name when done.
[?] Property name: Name
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Pet property.
Enter an empty property name when done.
[?] Property name: Category
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Pet property.
Enter an empty property name when done.
[?] Property name: Birthdate
   invoke   loopback:property
[?] Property type: date
[?] Required? Yes

Let's add another Pet property.
Enter an empty property name when done.
[?] Property name:

Essayons à présent d’utiliser notre API.

Premier essai

Lançons le serveur d’API, toujours grâce à la commande slc :

slc run

Via curl, créons un nouveau client, puis listons tous les clients :

$ curl -H "Content-Type: application/json" -d '{"Lastname":"Franck","Firstname":"Jean-Sébastien","Phone":"0600000000"}' http://localhost:3000/api/Customers
{"Firstname":"Jean-Sébastien","Lastname":"Franck","Phone":600000000,"id":"543957c964090476112c2558"}

$ curl http://localhost:3000/api/Customers

[{"Firstname":"Jean-Sébastien","Lastname":"Franck","Phone":600000000,"id":"543957c964090476112c2558"}]

Ca fonctionne, nous avons créé une API en quelques minutes seulement !

Dans le code

Jetons à présent un coup d’oeil dans le code généré par la commande slc. Deux répertoires principaux ont été créés : common et server.

Le répertoire common contient la description de nos modèles. On y retrouve notamment le fichier customer.json :

{
  "name": "Customer",
  "plural": "Customers",
  "base": "PersistedModel",
  "properties": {
    "Firstname": {
      "type": "string",
      "required": true
    },
    "Lastname": {
      "type": "string",
      "required": true
    },
    "Phone": {
      "type": "number",
      "required": true
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": []
}

Le répertoire server contient, quant-à lui, le code permettant de configurer les différents modules de notre API : explorateur, authentification, routeur, configuration et datasources. Mais nous y reviendrons.

Explorateur des services de l’API

Un explorateur de notre api est disponible sous http://localhost:3000/explorer/ et permet de lister chacun des services exposés. Par exemple voici les services exposés pour gérer les animaux de notre clinique vétérinaire :

Capture+d’écran+2014-10-11+à+12.31.58

 

Pour chacun des services, un exemple d’utilisation et un formulaire nous permettent de nous familiariser avec notre nouvelle API :

Capture+d’écran+2014-10-11+à+12.33.17

Backends et datasources

Le stockage de nos clients et de nos animaux se fait par défaut en mémoire. Allons un cran plus loin et créons une source de données Mongodb pour nos clients et nos animaux. Encore une fois, la commande slc vient à notre aide :

$ slc loopback:datasource VeterinaryDB
[?] Enter the data-source name: VeterinaryDB
[?] Select the connector for VeterinaryDB: MongoDB (supported by StrongLoop)

Ici, plusieurs sources de données sont proposées et supportées par StrongLoop ou par la communauté : In-memory db, Email, MySQL, PostgreSQL, Oracle, Microsoft SQL, MongoDB, SOAP webservices, REST services, Neo4j et Kafka.

Installons le module Mongodb de Strongloop dans notre projet :

$ npm install loopback-connector-mongodb --save

Puis lançons une instance de Mongodb installée en local.

Associons enfin notre source de données à nos modèles Customer et Pet en modifiant le fichier de listing des modèles dans le répertoire server de notre projet :

"Customer": {
  "dataSource": "VeterinaryDB",
  "public": true
},
"Pet": {
  "dataSource": "VeterinaryDB",
  "public": true
}

Le tour est joué. On relance notre serveur et on peut insérer nos clients et nos animaux dans notre backend Mongodb. Facile !

Relations et validation

Un animal appartient à un client, et cette relation n’existe pas dans nos modèles. Différents types de relations peuvent être créés. Ici nous allons créer une relation du type « belongsTo » dans le fichier pet.json :

"relations": {
  "customer": {
    "type": "belongsTo",
    "model": "Customer",
    "foreignKey": "customerId"
  }
}

Relançons le serveur et créons un animal en précisant l’id du client créé précédemment :

$ curl -H "Content-Type: application/json" -d '{"Name":"Heidi","Category":"dog","Birthdate":"2012/05/10","customerId":"543957c964090476112c2558"}' http://localhost:3000/api/Pets

{"Name":"Heidi","Category":"dog","Birthdate":"2012-05-09T22:00:00.000Z","id":"5439581d64090476112c2559","customerId":"543957c964090476112c2558"}

Retournons dans l’explorateur de l’API. On constate qu’un nouveau service est apparu :

Capture+d’écran+2014-10-11+à+18.20.48

On peut effectivement maintenant récupérer le client à partir d’un animal :

$ curl http://localhost:3000/api/Pets/5439581d64090476112c2559/customer

{"Firstname":"Jean-Sébastien","Lastname":"Franck","Phone":600000000,"id":"543957c964090476112c2558"}

A présent, rajoutons une règle de validation sur la category de l’animal. On souhaite que les valeurs possibles soient uniquement « dog », « cat » ou « bird » :

module.exports = function(Pet) {
  Pet.validatesInclusionOf('Category', {in: ['dog', 'cat', 'bird']});
};

Différentes règles de validation peuvent être créées. Il est notamment possible de créer une validation custom en asynchrone, par exemple pour aller faire une requête dans un backend tiers et implémenter des règles métiers plus complexes.

Gestion des utilisateurs de l’API et sécurité

Par défaut tous les services créés sont publics. L’outillage de Strongloop permet cependant de gérer les utilisateurs de l’API, leurs rôles, et ceci pour chacun des services exposés.

Notre nouvel objectif est de restreindre l’accès aux services du modèle Customer pour que ceux ci ne soient que disponibles aux utilisateurs connectés. Encore une fois, je peux utiliser la commande slc :

$ slc loopback:acl
[?] Select the model to apply the ACL entry to: Customer
[?] Select the ACL scope: All methods and properties
[?] Select the access type: All (match all types)
[?] Select the role: Any unauthenticated user
[?] Select the permission to apply: Explicitly deny access

Qui va me modifier le contenu du fichier customer.json :

"acls": [{
  "accessType": "*",
  "principalType": "ROLE",
  "principalId": "$unauthenticated",
  "permission": "DENY"
}]

A présent, si j’essaye de créer un client, le service me renvoie une erreur 401 :

curl -H "Content-Type: application/json" -d '{"Lastname":"Wayne","Firstname":"Bruce","Phone":"0100000000"}' http://localhost:3000/api/Customers
{"error":{"name":"Error","status":401,"message":"Authorization Required","statusCode":401,"stack":"..."}}

Créons donc un nouvel utilisateur, puis connectons le à notre API :

$ curl -H "Content-Type:application/json" -d '{"email": "jfranck@xebia.fr", "password": "monPwd"}' http://localhost:3000/api/users

{"email":"jfranck@xebia.fr","id":1}
 
$ curl -H "Content-Type:application/json" -d '{"email": "jfranck@xebia.fr", "password": "monPwd", "ttl": 3600000}' http://localhost:3000/api/users/login
{"id":"QRdbWbnk9TnLBKy6MXWUoOZ2Q0GYwwHuKGPOqckfWuCIufUNIjpxKYs4E6TAiPlK","ttl":3600000,"created":"2014-10-12T08:30:59.672Z","userId":1}

En étant connecté et en réutilisant l’access token renvoyé par le service de login, je peux désormais créer un client !

curl -H "Content-Type: application/json" -H "Authorization: QRdbWbnk9TnLBKy6MXWUoOZ2Q0GYwwHuKGPOqckfWuCIufUNIjpxKYs4E6TAiPlK" -d '{"Lastname":"Wayne","Firstname":"Bruce","Phone":"0100000000"}' http://localhost:3000/api/Customers
{"Firstname":"Bruce","Lastname":"Wayne","Phone":100000000,"id":"543a3cf6365a1eeb1478c288"}

Monitoring et profiling

La commande slc ne permet pas que de créer une API. Elle permet aussi de brancher n’importe quelle application Node.js sur le profiler Strongops. Après avoir créé un compte sur http://strongops.strongloop.com, il suffit de lancer la commande slc strongops et de préciser les credentials de son compte.

Générons du traffic sur notre api grâce à la commande Apache Bench (ici on exécute 100000 requêtes avec un maximum de 10 requêtes concurrentes) :

ab -n 100000 -c 10 http://localhost:3000/api/Pets

Et sur le dashboard de Strongops, on accède à diverses informations, par exemple : le temps de réponse des requêtes par backend, l’utilisation CPU, le nombre de connexions concurrentes et ci-dessous la consommation mémoire :

Capture+d’écran+2014-10-11+à+15.54.47

Sur ce même dashboard, il est possible de faire du profiling CPU et mémoire, ce qui est très utile pour comprendre les problèmes de performance de votre application Node.js.

Déploiement, clustering et graceful shutdown

Strongloop propose un Process Manager pour gérer les déploiements de nos applicatifs Node.js. Ce Process Manager doit être lancé sur la machine où l’on souhaite déployer notre applicatif. Il est responsable de gérer le déploiement et d’assurer la supervision.

Le Process Manager gère l’applicatif en mode cluster sur une même machine. Le nombre de workers du cluster est configurable, et peut par exemple être initialisé pour avoir autant de workers que de CPU. Lors d’un déploiement, le Process Manager remplace les workers un par un en s’assurant que les connexions TCP aient toutes été traitées avant de couper un worker. Enfin, upstart est utilisé pour relancer les workers en cas de crash.

Pour tester cette fonctionnalité, lançons un Process Manager sur une instance AWS EC2. En pré-requis sur cette instance EC2, Mongodb, Node.js et le module Strongloop doivent être installés. De plus les ports 7777 et 3000 doivent être ouverts.

Commençons par lancer le Process Manager :

ec2-instance$ slc pm -l 7777

Puis, en local, ou dans une usine logicielle, on créé le build et on l’envoie au Process Manager de l’instance ec2 sur le port 7777 :

local$ slc build
Running `git log -1 --pretty=format:"%t" HEAD`
  => a0b945a
Running `git commit-tree -p "refs/heads/deploy" -p "refs/heads/master" -m "Commit tree of 'refs/heads/master' onto 'deploy'" a0b945a`
  => 5a41f36a05d1c7d3b4bcfdc16332750acc8a95fc
Running `git update-ref "refs/heads/deploy" 5a41f36a05d1c7d3b4bcfdc16332750acc8a95fc`
Merged source tree of `refs/heads/master` onto `deploy`
Running `npm install --ignore-scripts`
Running `npm run build`
Running `npm prune --production`
Running `git add --force --all .`
Running `git write-tree`
  => 73e565ebce37c1f8c78443f5531433fa82ca6e02
Running `git commit-tree -p "refs/heads/deploy" -m "Commit build products" 73e565ebce37c1f8c78443f5531433fa82ca6e02`
Running `git update-ref "refs/heads/deploy" 819806760e83ed26b806b6ad289ea88ccaa8769b`
Committed build products onto `deploy`

local$ slc deploy http://ec2-54-171-50-162.eu-west-1.compute.amazonaws.com:7777/veterinary
To http://ec2-54-171-50-162.eu-west-1.compute.amazonaws.com:7777/veterinary/default
 * [new branch]      deploy -> deploy
Deployed `deploy` to `http://ec2-54-171-50-162.eu-west-1.compute.amazonaws.com:7777/veterinary`

Dans les logs du Process Manager sur l’instance EC2, on a :

2014-10-12T09:22:54.468Z pid:2810 worker:1 Browse your REST API at http://localhost:3000/explorer
2014-10-12T09:22:54.469Z pid:2810 worker:1 Web server listening at: http://localhost:3000/
2014-10-12T09:22:54.526Z pid:2812 worker:2 Browse your REST API at http://localhost:3000/explorer
2014-10-12T09:22:54.527Z pid:2812 worker:2 Web server listening at: http://localhost:3000/
2014-10-12T09:22:54.589Z pid:2814 worker:3 Browse your REST API at http://localhost:3000/explorer
2014-10-12T09:22:54.590Z pid:2814 worker:3 Web server listening at: http://localhost:3000/
2014-10-12T09:22:54.626Z pid:2816 worker:4 Browse your REST API at http://localhost:3000/explorer
2014-10-12T09:22:54.626Z pid:2816 worker:4 Web server listening at: http://localhost:3000/
2014-10-12T09:22:55.146Z pid:2810 worker:1 INFO strong-agent[2810] connected to collector
2014-10-12T09:22:55.234Z pid:2812 worker:2 INFO strong-agent[2812] connected to collector
2014-10-12T09:22:55.266Z pid:2814 worker:3 INFO strong-agent[2814] connected to collector
2014-10-12T09:22:55.300Z pid:2816 worker:4 INFO strong-agent[2816] connected to collector

Et dans le navigateur, sur le port 3000 de notre instance EC2, on a bien accès à l’explorateur de l’API. Ca fonctionne ! A noter que cet outillage est également utilisable sur des projets créés indépendamment. Si vous rencontrez des problématiques de déploiement, cette solution est donc tout à fait crédible.

Pour aller plus loin

Dans cet article, nous avons traité les principales fonctionnalités de l’outillage de Strongloop. Je vous encourage vivement à consulter la documentation qui est bien faite afin d’aller plus loin sur les possibilités des modèles, du déploiement, de la sécurité etc. Je vous invite aussi à découvrir le SDK de consultation de l’API qui est fourni pour Javascript, IOS et Android.

Pour finir, vous retrouverez le code de cet article dans github ici.

4 réflexions au sujet de « Node.js : Créer une API REST avec les outils de Strongloop »

  1. Publié par Lemoine Benoit, Il y a 2 années

    Personnellement, je trouve que c’est une mauvaise pratique d’installer des modules en global.
    En effet, ici tu utilises strongloop sur un projet , mais imagine que tu aies un second projet qui utilise une autre version de strongloop.

  2. Publié par David Martin, Il y a 2 années

    Le développement est certes rapide, mais l’interface de l’API donne beaucoup à redire, et ne se conforme pas aux principales contraintes du style d’architecture REST.

    Cela peut présenter un intérêt pour faire du CRUD rapidement, mais ne sera pas un choix robuste pour une API sérieuse.

  3. Publié par Sam Roberts, Il y a 2 années

    @benoit, je suis en accord pour le plus part, mais strongloop n’est pas un dependency de l’application. c’est loopback que est le dependency. strongloop/slc est comme npm, ou le « heroku toolbelt », un project est jamais fixe sur un version particular de strongloop. Mais, ci tu veux, tu est bien capable de installer strongloop locallement dans tons project, est de utilises comme `./node_modules/.bin/slc`.

    @david, je suis curieux pourquoi tu pense notre APIs ne conforme pas as REST, ils on bien utilise par notre customers pour les API que ils crois est serieuse! :-)

    Et je m’excuse pour ma francais mal, je essaie de apprendre le langue de mon espouse, mais c’est difficile.

  4. Publié par Michael, Il y a 2 années

    Bonjour Jean-Sebastien,
    Grace à toi j’ai créé en 10 minutes montre en main.
    Et je veux te dire : MERCI.
    Mdouke

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *