ホームページ  >  記事  >  ウェブフロントエンド  >  NodeJS と Restify を使用した RESTful API の設計

NodeJS と Restify を使用した RESTful API の設計

WBOY
WBOYオリジナル
2023-09-03 10:37:011458ブラウズ

使用NodeJS和Restify设计RESTful API

RESTful API には、resourcerepresentation という 2 つの主要な概念が含まれています。リソースは、データに関連付けられたオブジェクト、または URI によって識別されるオブジェクト (複数の URI が同じリソースを参照できます) であり、HTTP メソッドを使用して操作できます。表現とは、リソースを表示する方法です。このチュートリアルでは、RESTful API 設計に関する理論的な情報をいくつか取り上げ、NodeJS を使用してサンプル ブログ アプリケーション API を実装します。

###リソース###

RESTful API に適切なリソースを選択することは、設計の重要な部分です。まず、ビジネス ドメインを分析し、ビジネス ニーズに関連して使用するリソースの量と種類を決定する必要があります。ブログ API を設計している場合は、

Posts

Users、および Comments を使用できます。これらはリソース名であり、それらに関連付けられたデータはリソース自体です: リーリー リソース動詞

必要なリソースを決定したら、リソースの操作を続行できます。ここでの操作は HTTP メソッドを指します。たとえば、記事を作成するには、次のリクエストを行うことができます:

リーリー

同様に、次のリクエストを行うことで既存の記事を表示できます:

リーリー

既存の記事を更新してみてはいかがでしょうか?あなたの声が聞こえます:

ペイロードを使用して、/articles/update/123456789012 に対して別の POST リクエストを行うことができます。

もっと良いかもしれませんが、URI はより複雑になっています。前に述べたように、操作は HTTP メソッドを参照できます。これは、
update

オペレーションを URI に入れるのではなく、HTTP メソッドで宣言することを意味します。例えば:### リーリー ちなみに、この例では、タグ フィールドとカテゴリ フィールドが表示されます。これらは必須フィールドである必要はありません。空白のままにして、将来的に設定することもできます。

記事が古くなった場合、記事を削除する必要がある場合があります。この場合、

DELETE

HTTP リクエストを

/articles/123456789012 に使用できます。

HTTP メソッドは標準的な概念です。これらをアクションとして使用すると、単純な URI が得られ、この単純な API は満足な消費者を獲得するのに役立ちます。

記事にコメントを挿入したい場合はどうすればよいですか?記事を選択し、選択した記事に新しいコメントを追加できます。このステートメントを使用すると、次のリクエストを使用できます:

リーリー

上記の形式のリソースは、

サブリソースと呼ばれます。

コメントは、記事のサブリソースです。 上記の Comments ペイロードは、Articles の子としてデータベースに挿入されます。場合によっては、異なる URI が同じリソースを参照することがあります。たとえば、特定のコメントを表示するには、次のように使用します: リーリー ###または:### リーリー ###バージョン管理### 一般的に、API 機能は消費者に新しい機能を提供するために頻繁に変更されます。この場合、同じ API の 2 つのバージョンが同時に存在する可能性があります。これら 2 つの機能を分離するには、バージョン管理を使用できます。バージョン管理には 2 つの形式があります

URI のバージョン:

URI にバージョン番号を指定できます。たとえば、

/v1.1/articles/123456789012

  1. ヘッダーのバージョン: ヘッダーにバージョン番号を指定します。URI は決して変更しないでください。 ### ###例えば:### リーリー 実際には、バージョンによって変更されるのはリソースの表現のみであり、リソースの概念は変更されません。したがって、URI 構造を変更する必要はありません。 v1.1 では、記事に新しいフィールドが追加されている可能性があります。ただし、それでも記事が返されます。 2 番目のオプションでは、URI は単純なままであり、コンシューマはクライアント実装で URI を変更する必要がありません。
  2. 消費者がバージョン番号を提供しない状況に備えた戦略を設計することが非常に重要です。バージョンが指定されていない場合はエラーを発生させることも、最初のバージョンを含む応答を返すこともできます。最新の安定バージョンをデフォルト バージョンとして使用する場合、コンシューマのクライアント実装には多くのバグが含まれる可能性があります。
  3. ###急行### は、API がリソースを表示する方法を表します。 API エンドポイントを呼び出すと、リソースが返されます。リソースは XML、JSON などの任意の形式にすることができます。新しい API を設計している場合は、JSON を使用するのが最善です。ただし、XML 応答を返す既存の API を更新する場合は、JSON 応答に別のバージョンを提供できます。 RESTful API 設計に関する理論的な情報はこれで十分です。 Restify を使用してブログ API を設計および実装して、実際の使用法を見てみましょう。
  4. ブログREST API
###デザイン###

RESTful API を設計するには、ビジネス ドメインを分析する必要があります。次に、リソースを定義します。 Blog API では次のものが必要です:

作成、更新、削除、表示

記事

特定の

投稿

に対するコメントの作成、更新、削除、表示、

コメント

  • 作成、更新、削除、表示
  • ユーザー

この API では、記事やコメントを作成するためにユーザーを認証する方法については説明しません。認証部分については、「AngularJS および NodeJS のトークンベースの認証チュートリアル」を参照してください。

リソース名が準備できました。リソース操作は単純な CRUD です。 API の概要については、以下の表を参照してください。

###記事### ###コメント### コメントの作成ユーザーの更新
リソース名 HTTP動詞 HTTPメソッド
記事の作成 記事の更新

記事の削除

記事を表示

POST /articles (ペイロードあり)

PUT /articles/123 (ペイロードあり)

DELETE /articles/123

GET /article/123

コメントの更新 コメントの削除

コメントの表示

ペイロード /articles/123/comments を使用した POST

ペイロード /comments/123/123 を使用した PUT

削除 /comments/123

GET /comments/123

###ユーザー###

ユーザーの作成

ユーザーの削除 ユーザーの表示

POST /users (ペイロードあり)

PUT /users/123 (ペイロードあり)

DELETE /users/123

GET /users/123

项目设置

在此项目中,我们将使用 NodeJSRestify。资源将保存在 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_v2viewArticleviewArticle_v2 都执行相同的工作,显示资源,但它们以不同的格式显示文章资源,正如您在 中看到的那样title 字段如下。最后,服务器在特定端口上启动,并应用一些错误报告检查。我们可以继续使用控制器方法对资源进行 HTTP 操作。

article.js

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 操作的说明:

  • createArticle:这是对从请求正文发送的 articleModel 的简单保存操作。可以通过将请求正文作为构造函数传递给模型来创建新模型,例如 vararticleModel = new Article(req.body)
  • viewArticle:为了查看文章详细信息,URL 参数中需要提供文章 ID。 findOne 带有 ID 参数足以返回文章详细信息。
  • updateArticle:文章更新是一个简单的查找查询并对返回的文章进行一些数据操作。最后,需要通过发出 save 命令将更新后的模型保存到数据库中。
  • deleteArticle:findByIdAndRemove 是通过提供文章 ID 来删除文章的最佳方法。

上面提到的 Mongoose 命令只是通过 Article 对象进行静态方法,该对象也是 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": 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 发出请求时,控制器中声明的相关函数将被执行。控制器文件中的每个函数都可以使用 reqres 对象。这里的评论资源是文章的子资源。 所有的查询操作都是通过Article模型进行的,以便找到子文档并进行必要的更新。但是,每当您尝试查看 Comment 资源时,即使 MongoDB 中没有集合,您也会看到一个 Comment 资源。

其他设计建议

  • 选择易于理解的资源,以便消费者轻松使用。
  • 让业务逻辑由消费者实现。例如,文章资源有一个名为 slug 的字段。 消费者不需要将此详细信息发送到 REST API。这种 slug 策略应该在 REST API 端进行管理,以减少 API 和消费者之间的耦合。消费者只需要发送标题详细信息,您就可以在REST API端根据您的业务需求生成slug。
  • 为您的 API 端点实施授权层。未经授权的消费者可以访问属于其他用户的受限数据。在本教程中,我们没有介绍用户资源,但您可以参阅使用 AngularJS 和 NodeJS 进行基于令牌的身份验证,以获取有关 API 身份验证的更多信息。
  • 用户 URI 而不是查询字符串。 /articles/123  (好),/articles?id=123(差)。
  • 不保留状态;始终使用即时输入/输出。
  • 使用名词来表示您的资源。您可以使用 HTTP 方法来操作资源。

最后,如果您按照这些基本规则设计 RESTful API,您将始终拥有一个灵活、可维护、易于理解的系统。

以上がNodeJS と Restify を使用した RESTful API の設計の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。