Maison > Article > interface Web > Conception d'une API RESTful à l'aide de NodeJS et Restify
RESTful se compose de deux concepts principaux : Ressources et Représentations. Une ressource peut être n'importe quel objet associé à des données ou identifié par un URI (plusieurs URI peuvent référencer la même ressource) et peut être manipulée à l'aide de méthodes HTTP. La représentation est la manière dont une ressource est affichée. Dans ce didacticiel, nous aborderons quelques informations théoriques sur la conception de l'API RESTful et implémenterons un exemple d'API d'application de blog à l'aide de NodeJS.
Choisir les bonnes ressources pour une API RESTful est une partie importante de la conception. Tout d’abord, vous devez analyser votre domaine d’activité, puis décider de la quantité et du type de ressources à utiliser en fonction des besoins de votre entreprise. Si vous conceviez une API de blog, vous pourriez utiliser Posts, Users et Comments. Voici les noms des ressources et les données qui leur sont associées constituent la ressource elle-même :
{ "title": "How to Design RESTful API", "content": "RESTful API design is a very important case in the software development world.", "author": "huseyinbabal", "tags": [ "technology", "nodejs", "node-restify" ] "category": "NodeJS" }
Après avoir identifié les ressources dont vous avez besoin, vous pouvez procéder aux opérations sur les ressources. L'opération fait ici référence à la méthode HTTP. Par exemple, pour créer un article, vous pouvez faire la requête suivante :
POST /articles HTTP/1.1 Host: localhost:3000 Content-Type: application/json { "title": "RESTful API Design with Restify", "slug": "restful-api-design-with-restify", "content": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", "author": "huseyinbabal" }
De la même manière, vous pouvez consulter les articles existants en faisant la demande suivante :
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
Que diriez-vous de mettre à jour un article existant ? Je t'entends dire :
Je peux faire une autre requête POST à /articles/update/123456789012 avec la charge utile.
Peut-être mieux, mais les URI deviennent de plus en plus complexes. Comme nous l'avons dit précédemment, les opérations peuvent référencer des méthodes HTTP. Cela signifie déclarer l'opération update dans la méthode HTTP au lieu de la mettre dans l'URI. Par exemple :
PUT /articles/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json { "title": "Updated How to Design RESTful API", "content": "Updated RESTful API design is a very important case in the software development world.", "author": "huseyinbabal", "tags": [ "technology", "nodejs", "restify", "one more tag" ] "category": "NodeJS" }
À propos, dans cet exemple, vous verrez les champs de balise et de catégorie. Il n’est pas nécessaire que ce soient des champs obligatoires. Vous pouvez les laisser vides et les définir ultérieurement.
Parfois, vous devez supprimer un article lorsqu'il devient obsolète. Dans ce cas, vous pouvez utiliser une requête DELETEHTTP vers /articles/123456789012.
Les méthodes HTTP sont des concepts standards. Si vous les utilisez comme actions, vous aurez des URI simples, et cette API simple vous aidera à gagner des consommateurs satisfaits.
Et si vous souhaitez insérer un commentaire dans votre article ? Vous pouvez sélectionner des articles et ajouter de nouveaux commentaires aux articles sélectionnés. En utilisant cette instruction, vous pouvez utiliser les requêtes suivantes :
POST /articles/123456789012/comments HTTP/1.1 Host: localhost:3000 Content-Type: application/json { "text": "Wow! this is a good tutorial", "author": "john doe" }
Les ressources sous la forme ci-dessus sont appelées sous-ressources. Comments sont des sous-ressources de articles. La charge utile Comments ci-dessus sera insérée dans la base de données en tant qu'enfant des Articles. Parfois, différents URI font référence à la même ressource. Par exemple, pour voir un commentaire spécifique, vous pouvez utiliser :
GET /articles/123456789012/comments/123 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
ou :
GET /comments/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
De manière générale, les fonctionnalités de l'API changent fréquemment pour offrir de nouvelles fonctionnalités aux consommateurs. Dans ce cas, deux versions d’une même API peuvent exister simultanément. Pour séparer ces deux fonctions, vous pouvez utiliser le contrôle de version. Le contrôle de version se présente sous deux formes
/v1.1/articles/123456789012
.
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Accept-Version: 1.0
En fait, la version change uniquement la représentation de la ressource, pas le concept de la ressource. Par conséquent, vous n’avez pas besoin de modifier la structure de l’URI. Dans la v1.1, un nouveau champ a peut-être été ajouté dans l'article. Cependant, il renvoie toujours un article. Dans la deuxième option, l'URI reste simple et le consommateur n'a pas besoin de modifier son URI dans l'implémentation client.
Il est très important de concevoir des stratégies pour les situations où les consommateurs ne fournissent pas de numéros de version. Vous pouvez générer une erreur lorsqu'aucune version n'est fournie ou renvoyer une réponse avec la première version. Si vous utilisez la dernière version stable comme version par défaut, l'implémentation client du consommateur peut contenir de nombreux bogues.
représente la façon dont l'API affiche les ressources. Lorsque vous appelez un point de terminaison d'API, vous renvoyez une ressource. La ressource peut être dans n'importe quel format tel que XML, JSON, etc. Si vous concevez une nouvelle API, il est préférable d'utiliser JSON. Toutefois, si vous mettez à jour une API existante qui renvoie des réponses XML, vous pouvez fournir une autre version pour les réponses JSON.
Assez d'informations théoriques sur la conception de l'API RESTful. Voyons l'utilisation réelle en concevant et en implémentant une API de blog à l'aide de Restify.
Afin de concevoir une API RESTful, nous devons analyser le domaine métier. Ensuite, nous pouvons définir nos ressources. Dans l'API Blog, nous avons besoin de :
spécifiques
Dans cette API, je n'expliquerai 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 d'authentification basée sur les jetons pour AngularJS et NodeJS.
Nos noms de ressources sont prêts. Les opérations sur les ressources sont de simples CRUD. Vous pouvez vous référer au tableau ci-dessous pour un aperçu de l'API.
Nom de la ressource | Verbe HTTP | Méthode HTTP |
---|---|---|
Article | Créer un article Mettre à jour l'article Supprimer l'article Voir l'article |
POST /articles avec Payload PUT /articles/123 avec Payload DELETE /articles/123 GET /article/123 |
Commentaires | Créer un commentaire Mettre à jour le commentaire Supprimer le commentaire Afficher le commentaire
|
POST /articles/123/comments avec charge utile PUT /comments/123 avec charge utile DELETE /comments/123 GET /comments/123 |
Utilisateur | Créer un utilisateur Mettre à jour l'utilisateur Supprimer l'utilisateur Afficher l'utilisateur |
POST /users avec Payload PUT /users/123 avec Payload DELETE /users/123 GET /users/123 |
在此项目中,我们将使用 NodeJS 和 Restify。资源将保存在 MongoDB 数据库中。首先,我们可以在Restify中将资源定义为模型。
var mongoose = require("mongoose"); var Schema = mongoose.Schema; var ArticleSchema = new Schema({ title: String, slug: String, content: String, author: { type: String, ref: "User" } }); mongoose.model('Article', ArticleSchema);
var mongoose = require("mongoose"); var Schema = mongoose.Schema; var CommentSchema = new Schema({ text: String, article: { type: String, ref: "Article" }, author: { type: String, ref: "User" } }); mongoose.model('Comment', CommentSchema);
不会对用户资源进行任何操作。我们假设我们已经知道能够对文章或评论进行操作的当前用户。
您可能会问这个猫鼬模块来自哪里。它是作为 NodeJS 模块编写的最流行的 MongoDB ORM 框架。该模块包含在项目的另一个配置文件中。
现在我们可以为上述资源定义 HTTP 动词。您可以看到以下内容:
var restify = require('restify') , fs = require('fs') var controllers = {} , controllers_path = process.cwd() + '/app/controllers' fs.readdirSync(controllers_path).forEach(function (file) { if (file.indexOf('.js') != -1) { controllers[file.split('.')[0]] = require(controllers_path + '/' + file) } }) var server = restify.createServer(); server .use(restify.fullResponse()) .use(restify.bodyParser()) // Article Start server.post("/articles", controllers.article.createArticle) server.put("/articles/:id", controllers.article.updateArticle) server.del("/articles/:id", controllers.article.deleteArticle) server.get({path: "/articles/:id", version: "1.0.0"}, controllers.article.viewArticle) server.get({path: "/articles/:id", version: "2.0.0"}, controllers.article.viewArticle_v2) // Article End // Comment Start 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) // Comment End var port = process.env.PORT || 3000; server.listen(port, function (err) { if (err) console.error(err) else console.log('App is ready at : ' + port) }) if (process.env.environment == 'production') process.on('uncaughtException', function (err) { console.error(JSON.parse(JSON.stringify(err, ['stack', 'message', 'inner'], 2))) })
在此代码片段中,首先迭代包含控制器方法的所有控制器文件,并初始化所有控制器,以便执行对 URI 的特定请求。之后,为基本的CRUD操作定义了具体操作的URI。 Article 上的其中一项操作也有版本控制。
例如,如果您在 Accept-Version 标头中将版本声明为 2
,则将执行 viewArticle_v2
。 viewArticle
和 viewArticle_v2
都执行相同的工作,显示资源,但它们以不同的格式显示文章资源,正如您在 中看到的那样title
字段如下。最后,服务器在特定端口上启动,并应用一些错误报告检查。我们可以继续使用控制器方法对资源进行 HTTP 操作。
var mongoose = require('mongoose'), Article = mongoose.model("Article"), ObjectId = mongoose.Types.ObjectId exports.createArticle = function(req, res, next) { var articleModel = new Article(req.body); articleModel.save(function(err, article) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { res.json({ type: true, data: article }) } }) } exports.viewArticle = function(req, res, next) { Article.findById(new ObjectId(req.params.id), function(err, article) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { if (article) { res.json({ type: true, data: article }) } else { res.json({ type: false, data: "Article: " + req.params.id + " not found" }) } } }) } exports.viewArticle_v2 = function(req, res, next) { Article.findById(new ObjectId(req.params.id), function(err, article) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { if (article) { article.title = article.title + " v2" res.json({ type: true, data: article }) } else { res.json({ type: false, data: "Article: " + req.params.id + " not found" }) } } }) } exports.updateArticle = function(req, res, next) { var updatedArticleModel = new Article(req.body); Article.findByIdAndUpdate(new ObjectId(req.params.id), updatedArticleModel, function(err, article) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { if (article) { res.json({ type: true, data: article }) } else { res.json({ type: false, data: "Article: " + req.params.id + " not found" }) } } }) } exports.deleteArticle = function(req, res, next) { Article.findByIdAndRemove(new Object(req.params.id), function(err, article) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { res.json({ type: true, data: "Article: " + req.params.id + " deleted successfully" }) } }) }
您可以在下面找到 Mongoose 端基本 CRUD 操作的说明:
articleModel
的简单保存操作。可以通过将请求正文作为构造函数传递给模型来创建新模型,例如 vararticleModel = new Article(req.body)
。 findOne
带有 ID 参数足以返回文章详细信息。save
命令将更新后的模型保存到数据库中。findByIdAndRemove
是通过提供文章 ID 来删除文章的最佳方法。上面提到的 Mongoose 命令只是通过 Article 对象进行静态方法,该对象也是 Mongoose 模式的引用。
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": new ObjectId(req.params.id)}, {"comments.$": 1}, function(err, comment) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { if (comment) { res.json({ type: true, data: new Comment(comment.comments[0]) }) } else { res.json({ type: false, data: "Comment: " + req.params.id + " not found" }) } } }) } exports.updateComment = function(req, res, next) { var updatedCommentModel = new Comment(req.body); console.log(updatedCommentModel) Article.update( {"comments._id": new ObjectId(req.params.id)}, {"$set": {"comments.$.text": updatedCommentModel.text, "comments.$.author": updatedCommentModel.author}}, function(err) { if (err) { res.status(500); res.json({ type: false, data: "Error occured: " + err }) } else { res.json({ type: true, data: "Comment: " + req.params.id + " updated" }) } }) } exports.deleteComment = function(req, res, next) { 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: "Error occured: " + err }) } else { if (article) { res.json({ type: true, data: article }) } else { res.json({ type: false, data: "Comment: " + req.params.id + " not found" }) } } }) }
当您向某个资源 URI 发出请求时,控制器中声明的相关函数将被执行。控制器文件中的每个函数都可以使用 req 和 res 对象。这里的评论资源是文章的子资源。 所有的查询操作都是通过Article模型进行的,以便找到子文档并进行必要的更新。但是,每当您尝试查看 Comment 资源时,即使 MongoDB 中没有集合,您也会看到一个 Comment 资源。
/articles/123
(好),/articles?id=123
(差)。最后,如果您按照这些基本规则设计 RESTful API,您将始终拥有一个灵活、可维护、易于理解的系统。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!