>웹 프론트엔드 >JS 튜토리얼 >NodeJS와 Restify를 사용하여 RESTful API 설계

NodeJS와 Restify를 사용하여 RESTful API 설계

WBOY
WBOY원래의
2023-09-03 10:37:011617검색

使用NodeJS和Restify设计RESTful API

RESTful API는 ResourcesRepresentations라는 두 가지 주요 개념으로 구성됩니다. 리소스는 데이터와 연결되거나 URI로 식별되는 모든 개체일 수 있으며(여러 URI가 동일한 리소스를 참조할 수 있음) HTTP 메서드를 사용하여 조작할 수 있습니다. 표현은 리소스가 표시되는 방식입니다. 이 튜토리얼에서는 RESTful API 디자인에 대한 몇 가지 이론적 정보를 다루고 NodeJS를 사용하여 샘플 블로그 애플리케이션 API를 구현합니다.

리소스

RESTful API에 적합한 리소스를 선택하는 것은 디자인의 중요한 부분입니다. 먼저 비즈니스 도메인을 분석한 다음 비즈니스 요구 사항과 관련하여 사용할 리소스의 양과 유형을 결정해야 합니다. 블로그 API를 설계하는 경우 Posts, UsersComments를 사용할 수 있습니다. 다음은 리소스 이름이며 이와 관련된 데이터는 리소스 자체입니다.

으아아아

자원 동사

필요한 자원을 파악한 후 자원 운영을 진행할 수 있습니다. 여기서의 작업은 HTTP 메서드를 나타냅니다. 예를 들어 기사를 작성하려면 다음 요청을 할 수 있습니다.

으아아아

동일하게 다음 요청을 하면 기존 기사를 볼 수 있습니다.

으아아아

기존 기사를 업데이트하는 것은 어떻습니까? 나는 당신이 말하는 것을 들었습니다:

페이로드를 사용하여 /articles/update/123456789012에 또 다른 POST 요청을 할 수 있습니다.

더 좋을 수도 있지만 URI가 점점 더 복잡해지고 있습니다. 앞서 말했듯이 작업은 HTTP 메서드를 참조할 수 있습니다. 즉, update 작업을 URI에 넣는 대신 HTTP 메서드에서 선언하는 것입니다. 예:

으아아아

그런데 이 예에서는 태그 및 카테고리 필드가 표시됩니다. 필수 필드일 필요는 없습니다. 공백으로 두고 나중에 설정할 수 있습니다.

기사가 오래되면 삭제해야 하는 경우도 있습니다. 이 경우 /articles/123456789012에 대한 DELETEHTTP 요청을 사용할 수 있습니다.

HTTP 방법은 표준 개념입니다. 이를 작업으로 사용하면 간단한 URI를 갖게 되며 이 간단한 API는 행복한 소비자를 얻는 데 도움이 됩니다.

글에 댓글을 삽입하고 싶다면 어떻게 해야 하나요? 기사를 선택하고 선택한 기사에 새 댓글을 추가할 수 있습니다. 이 문을 사용하면 다음 요청을 사용할 수 있습니다.

으아아아

위 형태의 리소스를 하위 리소스라고 합니다. 댓글기사의 하위 리소스입니다. 위의 Comments 페이로드는 Articles의 하위 항목으로 데이터베이스에 삽입됩니다. 때로는 서로 다른 URI가 동일한 리소스를 참조하는 경우도 있습니다. 예를 들어 특정 댓글을 보려면 다음을 사용할 수 있습니다.

으아아아

또는:

으아아아

버전 관리

일반적으로 API 기능은 소비자에게 새로운 기능을 제공하기 위해 자주 변경됩니다. 이 경우 동일한 API의 두 가지 버전이 동시에 존재할 수 있습니다. 이 두 기능을 분리하려면 버전 제어를 사용할 수 있습니다. 버전 관리는 두 가지 형태로 제공됩니다

  1. URI 버전: URI에 버전 번호를 제공할 수 있습니다. 예를 들어 /v1.1/articles/123456789012.
  2. 헤더 버전: 헤더에 버전 번호를 제공하고 URI를 변경하지 마세요. 예:
으아아아

사실 버전은 리소스의 개념이 아닌 리소스의 표현만 변경합니다. 따라서 URI 구조를 변경할 필요가 없습니다. v1.1에서는 Article에 새로운 필드가 추가되었을 수 있습니다. 그러나 여전히 기사를 반환합니다. 두 번째 옵션에서는 URI가 단순하게 유지되며 소비자는 클라이언트 구현에서 해당 URI를 변경할 필요가 없습니다.

소비자가 버전 번호를 제공하지 않는 상황에 대비한 전략을 설계하는 것이 매우 중요합니다. 버전이 제공되지 않으면 오류가 발생하거나 첫 번째 버전으로 응답을 반환할 수 있습니다. 최신 안정 버전을 기본 버전으로 사용하는 경우 소비자의 클라이언트 구현에 많은 버그가 있을 수 있습니다.

을 의미합니다.

는 API가 리소스를 표시하는 방식을 나타냅니다. API 엔드포인트를 호출하면 리소스가 반환됩니다. 리소스는 XML, JSON 등 모든 형식이 될 수 있습니다. 새로운 API를 설계하는 경우 JSON을 사용하는 것이 가장 좋습니다. 그러나 XML 응답을 반환하는 기존 API를 업데이트하는 경우 JSON 응답에 다른 버전을 제공할 수 있습니다.

RESTful API 설계에 대한 충분한 이론적 정보. Restify를 사용하여 블로그 API를 설계하고 구현하여 실제 사용법을 살펴보겠습니다.

블로그 REST API

디자인

RESTful API를 설계하려면 비즈니스 도메인을 분석해야 합니다. 그런 다음 리소스를 정의할 수 있습니다. Blog 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 with payload

PUT /comments/123 with payload

DELETE /comments/123

GET /comments/123

사용자 사용자 만들기

사용자 업데이트

사용자 삭제

사용자 보기

POST /users with Payload

PUT /users/123 with Payload

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.