Conception d'API RESTful avec NodeJS & Restify

Ce que vous allez créer

L'API RESTful comprend deux concepts principaux: Ressource, et Représentation. La ressource peut être n'importe quel objet associé à des données ou identifié avec un URI (plusieurs URI peuvent faire référence à la même ressource) et peut être exploitée à l'aide de méthodes HTTP. La représentation est la façon dont vous affichez la ressource. Dans ce didacticiel, nous aborderons certaines informations théoriques sur la conception d'API RESTful et implémenterons un exemple d'API d'application de blogging à l'aide de NodeJS.

Ressource

Le choix des ressources appropriées pour une API RESTful est une partie importante de la conception. Tout d'abord, vous devez analyser votre domaine d'activité, puis décider du nombre et du type de ressources qui seront utilisées et qui répondent à vos besoins. Si vous concevez une API de blogging, vous utiliserez probablement Article, Utilisateur, et Commentaire. Ce sont les noms de ressources et les données associées sont la ressource elle-même:

"title": "Comment concevoir une API RESTful", "content": "La conception d'une API RESTful est un cas très important dans le monde du développement logiciel.", "author": "huseyinbabal", "tags": ["technology" , "nodejs", "node-restify"] "category": "NodeJS"

Verbes de ressources

Vous pouvez procéder à une opération de ressource après avoir déterminé les ressources requises. L'opération ici fait référence aux méthodes HTTP. Par exemple, pour créer un article, vous pouvez faire la demande suivante:

POST / articles HTTP / 1.1 Hôte: localhost: 3000 Content-Type: application / json "title": "Conception d'API RESTful avec Restify", "slug": "restful-api-design-with-restify", "contenu" : "Pellentesque habitant morbi tristique senectus et netus et malesuada célèbres ac turpis egestas.", "Auteur": "huseyinbabal"

De la même manière, vous pouvez visualiser un article existant en émettant la requête suivante:

GET / articles / 123456789012 HTTP / 1.1 Hôte: localhost: 3000 Type de contenu: application / json

Qu'en est-il de la mise à jour d'un article existant? Je peux entendre que vous dites:

Je peux faire une autre demande POST à ​​/ articles / update / 123456789012 avec la charge utile.

Peut-être préférable, mais l'URI devient de plus en plus complexe. Comme nous l'avons dit précédemment, les opérations peuvent faire référence à des méthodes HTTP. Cela signifie, indiquez le mettre à jour opération dans la méthode HTTP au lieu de mettre cela dans l'URI. Par exemple:

PUT / articles / 123456789012 HTTP / 1.1 Hôte: localhost: 3000 Type de contenu: application / json "title": "Mise à jour Comment concevoir une API RESTful", "content": "La mise à jour de la conception d'une API RESTful est un cas très important monde du développement logiciel. "," author ":" huseyinbabal "," tags ": [" technologie "," nodejs "," restify "," un tag supplémentaire "]" category ":" NodeJS "

À propos, dans cet exemple, vous voyez des balises et des champs de catégorie. Ceux-ci n'ont pas besoin d'être des champs obligatoires. Vous pouvez les laisser vides et les définir à l'avenir. 

Parfois, vous devez supprimer un article lorsqu'il est obsolète. Dans ce cas, vous pouvez utiliser un EFFACER Demande HTTP à / articles / 123456789012.

Les méthodes HTTP sont des concepts standard. Si vous les utilisez en tant qu'opération, vous aurez des URI simples, et ce type d'API simple vous aidera à gagner des consommateurs satisfaits..

Et si vous voulez insérer un commentaire dans un article? Vous pouvez sélectionner l'article et ajouter un nouveau commentaire à l'article sélectionné. En utilisant cette instruction, vous pouvez utiliser la requête suivante:

POST / articles / 123456789012 / comments HTTP / 1.1 Hôte: localhost: 3000 Type de contenu: application / json "text": "Wow! C'est un bon tutoriel", "author": "john doe"

La forme de ressource ci-dessus est appelée en tant que sous-ressource. Commentaire est une sous-ressource de Article. le Commentaire la charge ci-dessus sera insérée dans la base de données en tant qu’enfant de Article. Parfois, un URI différent fait référence à la même ressource. Par exemple, pour afficher un commentaire spécifique, vous pouvez utiliser:

GET / articles / 123456789012 / comments / 123 HTTP / 1.1 Hôte: localhost: 3000 Type de contenu: application / json 

ou:

GET / comments / 123456789012 HTTP / 1.1 Hôte: localhost: 3000 Type de contenu: application / json

Gestion des versions

En général, les fonctionnalités de l'API changent fréquemment afin de fournir de nouvelles fonctionnalités aux consommateurs. Dans ce cas, deux versions de la même API peuvent exister simultanément. Pour séparer ces deux fonctionnalités, vous pouvez utiliser le contrôle de version. Il existe deux formes de versioning

  1. Version en URI: Vous pouvez fournir le numéro de version dans l'URI. Par exemple, /v1.1/articles/123456789012.
  2. Version en en-tête: Indiquez le numéro de version dans l'en-tête et ne modifiez jamais l'URI.Par exemple:
GET / articles / 123456789012 HTTP / 1.1 Hôte: localhost: 3000 Accepter-Version: 1.0

En réalité, la version ne change que la représentation de la ressource, pas le concept de la ressource. Donc, vous n'avez pas besoin de changer la structure d'URI. Dans la v1.1, un nouveau champ a peut-être été ajouté à Article. Cependant, il retourne toujours un article. Dans la deuxième option, l'URI reste simple et les consommateurs n'ont pas besoin de changer d'URI dans les implémentations côté client.. 

Il est important de concevoir une stratégie pour les situations où le consommateur ne fournit pas de numéro de version. Vous pouvez générer une erreur lorsque la version n'est pas fournie ou vous pouvez renvoyer une réponse à l'aide de la première version. Si vous utilisez la dernière version stable par défaut, les utilisateurs peuvent obtenir de nombreuses erreurs pour leurs implémentations côté client..

Représentation

La représentation est la façon dont une API affiche la ressource. Lorsque vous appelez un point de terminaison d'API, une ressource vous sera renvoyée. Cette ressource peut être dans n'importe quel format tel que XML, JSON, etc. JSON est préférable si vous concevez une nouvelle API. Toutefois, si vous mettez à jour une API existante qui renvoyait une réponse XML, vous pouvez fournir une autre version pour une réponse JSON.. 

C'est assez d'informations théoriques sur la conception d'API RESTful. Jetons un coup d'œil à l'utilisation en situation réelle en concevant et en mettant en œuvre une API de blogging à l'aide de Restify.

API REST de blogging

Conception

Pour concevoir une API RESTful, nous devons analyser le domaine métier. Ensuite, nous pouvons définir nos ressources. Dans une API de blogging, nous avons besoin de:

  • Créer, mettre à jour, supprimer, afficher Article
  • Créer un commentaire pour un particulier Article, Mettre à jour, supprimer, voir, Commentaire
  • Créer, mettre à jour, supprimer, afficher Utilisateur

Dans cette API, je ne décrirai pas comment authentifier un utilisateur pour créer un article ou un commentaire. Pour la partie authentification, vous pouvez vous référer au tutoriel sur l’authentification basée sur les jetons avec AngularJS & NodeJS.. 

Nos noms de ressources sont prêts. Les opérations sur les ressources sont simplement CRUD. Vous pouvez vous reporter au tableau suivant pour une présentation générale de l'API..

Nom de la ressource Verbes HTTP Méthodes HTTP
Article créer un article
mettre à jour l'article
supprimer l'article
voir l'article
POST / articles avec charge utile
PUT / articles / 123 avec charge utile
SUPPRIMER / articles / 123
GET / article / 123
Commentaire créer un commentaire
mettre à jour le commentaire
supprimer le commentaire
voir le commentaire
POST / articles / 123 / comments with Payload
PUT / comments / 123 avec charge utile
SUPPRIMER / commentaires / 123
GET / comments / 123
Utilisateur Créer un utilisateur
mettre à jour l'utilisateur
Supprimer l'utilisateur
voir l'utilisateur
POST / utilisateurs avec charge utile
PUT / utilisateurs / 123 avec charge utile
SUPPRIMER / utilisateurs / 123
GET / utilisateurs / 123

Configuration du projet

Dans ce projet, nous utiliserons NodeJS avec Restifier. Les ressources seront sauvegardées dans le MongoDB base de données. Tout d’abord, nous pouvons définir des ressources en tant que modèles dans Restify..

Article

var mangouste = require ("mangouste"); var Schema = mongoose.Schema; var ArticleSchema = new Schema (titre: String, slug: String, contenu: String, auteur: type: String, ref: "Utilisateur"); mongoose.model ('Article', ArticleSchema);

Commentaire

var mangouste = require ("mangouste"); var Schema = mongoose.Schema; var CommentSchema = new Schema (text: String, article: type: String, ref: "Article", auteur: type: String, ref: "User"); mongoose.model ('Comment', CommentSchema);

Utilisateur

Il n'y aura aucune opération pour la ressource User. Nous supposerons que nous connaissons déjà l'utilisateur actuel qui sera capable d'opérer des articles ou des commentaires.

Vous pouvez demander d'où vient ce module mangouste. C'est le framework ORM le plus populaire pour MongoDB, écrit en tant que module NodeJS. Ce module est inclus dans le projet dans un autre fichier de configuration.. 

Nous pouvons maintenant définir nos verbes HTTP pour les ressources ci-dessus. Vous pouvez voir ce qui suit:

var restify = require ('restify'), fs = require ('fs') var controllers = , controllers_path = process.cwd () + '/ app / controllers' fs.readdirSync (controllers_path) .forEach (fonction (fichier ) if (file.indexOf ('. js')! = -1) controllers [file.split ('.') [0]] = require (chemin_contrôleur + '/' + fichier)) var serveur = restify.createServer (); server .use (restify.fullResponse ()) .use (restify.bodyParser ()) // Article Démarrer server.post ("/ articles", controllers.article.createArticle) server.put ("/ articles /: id", controllers.article.updateArticle) server.del ("/ articles /: id", controllers.article.deleteArticle) server.get (chemin: "/ articles /: id", version: "1.0.0", contrôleurs. article.viewArticle) server.get (chemin: "/ articles /: id", version: "2.0.0", controllers.article.viewArticle_v2) // Article End // Commentaire Démarrer server.post ("/ comments" , controllers.comment.createComment) server.put ("/ comments /: id", controllers.comment.viewComment) server.del ("/ comments /: id", controllers.comment.deleteComment) server.get ("/ comments /: id ", controllers.comment.viewComment) // Commentaire End var port = process.env.PORT || 3000; server.listen (port, fonction (err) if (err) console.error (err) sinon console.log ('L'application est prête à:' + port)) if (process.env.environment == 'production' ) process.on ("uncaughtException", fonction (err) console.error (JSON.parse (JSON.stringify (err, ["pile", "message", "intérieur"], 2))))

Dans cet extrait de code, les fichiers de contrôleur contenant des méthodes de contrôleur sont tout d'abord itérés et tous les contrôleurs sont initialisés afin d'exécuter une requête spécifique à l'URI. Ensuite, les URI pour des opérations spécifiques sont définis pour les opérations CRUD de base. Il existe également une gestion des versions pour l’une des opérations sur Article.. 

Par exemple, si vous déclarez version comme 2 dans l'en-tête Accept-Version, voirArticle_v2 sera exécuté. voirArticle et voirArticle_v2 les deux font le même travail, montrant la ressource, mais ils affichent la ressource Article dans un format différent, comme vous pouvez le voir dans le Titre Champ ci-dessous. Enfin, le serveur est démarré sur un port spécifique et certaines vérifications de rapport d'erreur sont appliquées. Nous pouvons poursuivre avec les méthodes du contrôleur pour les opérations HTTP sur les ressources.

article.js

var mongoose = require ('mongoose'), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.createArticle = fonction (req, res, suivant) var articleModel = nouvel article (req.body ) articleModel.save (function (err, article) if (err) res.status (500); res.json (type: false, data: "Une erreur s'est produite:" + err) else res.json ( type: true, données: article) exports.viewArticle = fonction (req, res, suivant) Article.findById (nouvel ObjectId (req.params.id), fonction (err, article) if ( err) res.status (500); res.json (type: false, data: "Une erreur s'est produite:" + err) else if (article) res.json (type: true, data: article ) else res.json (type: false, données: "Article:" + req.params.id + "non trouvé")) exports.viewArticle_v2 = fonction (req, res, next) Article.findById (nouvel ObjectId (req.params.id), fonction (err, article) if (err) res.status (500); res.json (type: false, données: "une erreur s'est produite:" + err) else if (article) article.title = article.title + "v2" res.json (type: true, données: article) else res.json (type: false, données : "Article:" + req.params.id + "non trouvé")) exports.updateArticle = fonction (req, res, next) var updatedArticleModel = new Article (req.body); Article.findByIdAndUpdate (nouvel ObjectId (req.params.id), updatedArticleModel, function (err, article) if (err) res.status (500); res.json (type: false, données: "Une erreur s'est produite: "+ err) else if (article) res.json (type: true, données: article) else res.json (type: false, données:" Article: "+ req.params. id + "non trouvé")) exports.deleteArticle = fonction (req, res, suivant) Article.findByIdAndRemove (nouvel objet (req.params.id), fonction (err, article) if (err ) res.status (500); res.json (type: false, données: "Une erreur s'est produite:" + err) else res.json (type: true, données: "Article:" + req. params.id + "supprimé avec succès")) 

Vous pouvez trouver une explication des opérations de base du CRUD du côté Mongoose ci-dessous:

  • créerArticle: C'est simple enregistrer opération sur articleModèle envoyé à partir du corps de la demande. Un nouveau modèle peut être créé en transmettant le corps de la demande en tant que constructeur à un modèle tel que var articleModel = nouvel article (req.body)
  • voirArticle: Pour afficher les détails de l'article, un ID d'article est nécessaire dans le paramètre URL.. findOne avec un paramètre ID suffit pour retourner le détail de l'article.
  • updateArticle: La mise à jour d'article est une simple requête de recherche et une manipulation de données sur l'article renvoyé. Enfin, le modèle mis à jour doit être enregistré dans la base de données en émettant un enregistrer commander.
  • deleteArticle: findByIdAndRemove est le meilleur moyen de supprimer un article en fournissant l'identifiant de l'article.

Les commandes Mongoose mentionnées ci-dessus sont simplement statiques, comme méthode à travers un objet Article qui est également une référence du schéma Mongoose..

comment.js

var mongoose = require ('mongoose'), Comment = mongoose.model ("Comment"), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.viewComment = function (req, res)  Article.findOne ("comments._id": nouvel ObjectId (req.params.id), "comments. $": 1, fonction (err, comment) if (err) res.status (500) ; res.json (type: false, data: "Une erreur s'est produite:" + err) else if (comment) res.json (type: true, data: new Comment (comment.comments [0]) ) else res.json (type: false, données: "Commentaire:" + req.params.id + "non trouvé")) exports.updateComment = fonction (req, res, next) var updatedCommentModel = new Comment (req.body); console.log (updatedCommentModel) Article.update ("comments._id": nouvel ObjectId (req.params.id), "$ set": "comments. $. text": updatedCommentModel.text, "commentaires. $ .author ": updatedCommentModel.author, function (err) if (err) res.status (500); res.json (type: false, data:" Une erreur s'est produite: "+ err) else res.json (type: true, données: "Commentaire:" + req.params.id + "mis à jour")) exports.deleteComment = fonction (req, res, suivant) Article.findOneAndUpdate ( "comments._id": new ObjectId (req.params.id), "$ pull": "comments": "_id": new ObjectId (req.params.id), function (err, article) if (err) res.status (500); res.json (type: false, data: "Une erreur s'est produite:" + err) else if (article) res.json (type: true, data: article) else res.json (type: false, data: "Comment:" + req.params.id + "not found"))

Lorsque vous faites une demande à l’un des URI de la ressource, la fonction associée indiquée dans le contrôleur sera exécutée. Chaque fonction dans les fichiers du contrôleur peut utiliser le req et res objets. le commentaire ressource ici est une sous-ressource de Article. Toutes les opérations de requête sont effectuées via le modèle Article afin de rechercher un sous-document et d'effectuer la mise à jour nécessaire. Cependant, chaque fois que vous essayez d'afficher une ressource de commentaire, vous en verrez une même s'il n'y a pas de collection dans MongoDB..  

Autres suggestions de design

  • Choisissez des ressources faciles à comprendre afin de fournir une utilisation facile aux consommateurs.
  • Laissez la logique métier être mise en œuvre par les consommateurs. Par exemple, la ressource Article a un champ appelé limace. Les consommateurs n'ont pas besoin d'envoyer ce détail à l'API REST. Cette stratégie de slug devrait permettre de réduire le couplage entre l’API et les consommateurs côté API REST. Les consommateurs doivent uniquement envoyer les détails du titre, et vous pouvez générer le slug en fonction des besoins de votre entreprise du côté de l'API REST..
  • Implémentez une couche d'autorisation pour vos points de terminaison d'API. Les consommateurs non autorisés peuvent accéder à des données restreintes appartenant à un autre utilisateur. Dans ce tutoriel, nous ne couvrons pas la ressource Utilisateur, mais vous pouvez vous référer à Authentification basée sur les jetons avec AngularJS & NodeJS pour plus d'informations sur les authentifications d'API..
  • URI de l'utilisateur au lieu de la chaîne de requête. / articles / 123  (Bien), / articles? id = 123 (Mal).
  • Ne pas garder l'état; toujours utiliser une entrée / sortie instantanée.
  • Utilisez le nom pour vos ressources. Vous pouvez utiliser des méthodes HTTP pour opérer sur des ressources.

Enfin, si vous concevez une API RESTful en respectant ces règles fondamentales, vous disposerez toujours d'un système flexible, maintenable et facilement compréhensible..