
RESTful API 包含两个主要概念:资源和表示。资源可以是与数据关联的任何对象,或者用 URI 标识(多个 URI 可以引用同一资源),并且可以使用 HTTP 方法进行操作。表示是显示资源的方式。在本教程中,我们将介绍有关 RESTful API 设计的一些理论信息,并使用 NodeJS 实现示例博客应用程序 API。
为 RESTful API 选择正确的资源是设计的一个重要部分。首先,您需要分析您的业务领域,然后决定使用与您的业务需求相关的资源数量和类型。如果您正在设计博客 API,您可能会使用文章、用户和评论。这些是资源名称,与之关联的数据是资源本身:
{
"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"
}
确定所需资源后,您可以继续进行资源操作。这里的操作指的是HTTP方法。例如,为了创建一篇文章,您可以提出以下请求:
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"
}
以同样的方式,您可以通过发出以下请求来查看现有文章:
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
更新现有文章怎么样?我听到你在说:
我可以使用有效负载向 /articles/update/123456789012 发出另一个 POST 请求。
也许更好,但是 URI 变得越来越复杂。正如我们前面所说,操作可以引用HTTP方法。这意味着,在 HTTP 方法中声明更新操作,而不是将其放入 URI 中。例如:
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"
}
顺便说一下,在此示例中您会看到标签和类别字段。这些不需要是必填字段。您可以将它们留空并在将来设置它们。
有时,您需要在文章过时时将其删除。在这种情况下,您可以对 /articles/123456789012 使用DELETEHTTP 请求。
HTTP 方法是标准概念。如果您将它们用作操作,您将拥有简单的 URI,而这种简单的 API 将帮助您赢得满意的消费者。
如果您想在文章中插入评论怎么办?您可以选择文章并向所选文章添加新评论。通过使用此语句,您可以使用以下请求:
POST /articles/123456789012/comments HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"text": "Wow! this is a good tutorial",
"author": "john doe"
}
以上形式的资源称为子资源。 评论是文章的子资源。 上面的评论有效负载将作为文章的子项插入到数据库中。有时,不同的 URI 引用相同的资源。例如,要查看特定评论,您可以使用:
GET /articles/123456789012/comments/123 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
或者:
GET /comments/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
一般来说,API 功能会经常更改,以便为消费者提供新功能。在这种情况下,同一 API 的两个版本可以同时存在。为了分离这两个功能,您可以使用版本控制。版本控制有两种形式
/v1.1/articles/123456789012。
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Accept-Version: 1.0
实际上,版本改变的只是资源的表示形式,而不是资源的概念。因此,您不需要更改 URI 结构。在 v1.1 中,可能在 Article 中添加了一个新字段。但是,它仍然返回一篇文章。在第二个选项中,URI 仍然很简单,消费者不需要在客户端实现中更改其 URI。
针对消费者不提供版本号的情况设计策略非常重要。当未提供版本时,您可以引发错误,也可以使用第一个版本返回响应。如果您使用最新的稳定版本作为默认版本,消费者的客户端实现可能会出现许多错误。
表示是 API 显示资源的方式。当您调用 API 端点时,您将返回一个资源。该资源可以是任何格式,例如 XML、JSON 等。如果您正在设计新的 API,则最好使用 JSON。但是,如果您要更新用于返回 XML 响应的现有 API,则可以为 JSON 响应提供另一个版本。
有关 RESTful API 设计的理论信息已经足够了。让我们通过使用 Restify 设计和实现博客 API 来看看现实生活中的用法。
为了设计 RESTful API,我们需要分析业务领域。然后我们就可以定义我们的资源。在博客 API 中,我们需要:
在此 API 中,我不会介绍如何验证用户身份以创建文章或评论。对于身份验证部分,您可以参考 AngularJS & NodeJS 的基于令牌的身份验证教程。
我们的资源名称已准备就绪。资源操作就是简单的CRUD。您可以参考下表来了解 API 的总体展示。
| 资源名称 | HTTP 动词 | HTTP 方法 |
|---|---|---|
| 文章 | 创建文章 更新文章 删除文章 查看文章 |
POST /articles with Payload PUT /articles/123 with Payload DELETE /articles/123 GET /article/123 |
| 评论 | 创建评论 更新评论 删除评论 查看评论
|
带负载的 POST /articles/123/comments 带负载的 PUT /comments/123 删除 /comments/123 GET /comments/123 |
| 用户 | 创建用户 更新用户 删除用户 查看用户 |
POST /users with Payload PUT /users/123 with 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,您将始终拥有一个灵活、可维护、易于理解的系统。
以上就是使用NodeJS和Restify设计RESTful API的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号