Designing a RESTful API using NodeJS and Restify

RESTful API contains two main concepts: resource and representation. A resource can be any object associated with data, or identified by a URI (multiple URIs can reference the same resource), and can be manipulated using HTTP methods. Representation is the way a resource is displayed. In this tutorial, we'll cover some theoretical information about RESTful API design and implement a sample blog application API using NodeJS.
resource
Choosing the right resources for a RESTful API is an important part of the design. First, you need to analyze your business domain and then decide on the amount and type of resources to use that are relevant to your business needs. If you were designing a blogging API, you might use Posts, Users, and Comments. These are the resource names, and the data associated with them is the resource itself:
{
"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"
}
Resource Verb
After determining the required resources, you can continue with resource operations. The operation here refers to the HTTP method. For example, to create an article, you could make the following request:
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"
}
In the same way, you can view existing articles by making the following request:
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
How about updating an existing article? I hear you saying:
I can make another POST request to /articles/update/123456789012 with the payload.
Maybe better, but URIs are getting more complex. As we said earlier, operations can reference HTTP methods. This means, declaring the update operation in the HTTP method instead of putting it in the URI. For example:
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"
}
By the way, in this example you will see tag and category fields. These do not need to be required fields. You can leave them blank and set them in the future.
Sometimes you need to delete an article when it becomes outdated. In this case, you can use a DELETEHTTP request to /articles/123456789012.
HTTP methods are standard concepts. If you use them as actions, you'll have simple URIs, and this simple API will help you gain happy consumers.
What if you want to insert a comment into your article? You can select articles and add new comments to the selected articles. By using this statement, you can use the following requests:
POST /articles/123456789012/comments HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"text": "Wow! this is a good tutorial",
"author": "john doe"
}
The resources in the above form are called sub-resources. Comments is a sub-resource of the article. The Comments payload above will be inserted into the database as a child of Articles. Sometimes, different URIs refer to the same resource. For example, to view a specific comment, you would use:
GET /articles/123456789012/comments/123 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
or:
GET /comments/123456789012 HTTP/1.1 Host: localhost:3000 Content-Type: application/json
version control
Generally speaking, API functionality changes frequently to provide consumers with new functionality. In this case, two versions of the same API can exist simultaneously. To separate these two functions, you can use version control. Version control comes in two forms
-
Version in URI: You can provide a version number in the URI. For example,
/v1.1/articles/123456789012. - Version in header: Provide the version number in the header and never change the URI. For example:
GET /articles/123456789012 HTTP/1.1 Host: localhost:3000 Accept-Version: 1.0
In fact, the version changes only the representation of the resource, not the concept of the resource. Therefore, you don't need to change the URI structure. In v1.1, a new field may have been added in Article. However, it still returns an article. In the second option, the URI remains simple and the consumer does not need to change its URI in the client implementation.
It is very important to design strategies for situations where consumers do not provide version numbers. You can raise an error when no version is provided, or return a response with the first version. If you use the latest stable version as the default version, the consumer's client implementation may have many bugs.
express
represents the way the API displays resources. When you call an API endpoint, you return a resource. The resource can be in any format such as XML, JSON, etc. If you're designing a new API, it's best to use JSON. However, if you are updating an existing API that returns XML responses, you can provide another version for JSON responses.
This is enough theoretical information about RESTful API design. Let’s see real-life usage by designing and implementing a blogging API using Restify.
Blog REST API
design
In order to design a RESTful API, we need to analyze the business domain. Then we can define our resources. In the Blog API we need:
- Create, update, delete, viewArticles
-
Create comments, update, delete, view, comments for specific posts
-
Create, update, delete, viewUser
In this API, I won't cover how to authenticate a user to create an article or comment. For the authentication part, you can refer to Token Based Authentication Tutorial for AngularJS & NodeJS.
Our resource name is ready. Resource operations are simple CRUD. You can refer to the table below for an overview of the API.
| Resource Name | HTTP verb | HTTP method |
|---|---|---|
| article | Create article Update article Delete article View article |
POST /articles with Payload PUT /articles/123 with Payload DELETE /articles/123 GET /article/123 |
| Comment | Create comment Update comment Delete comment View comment
|
POST with payload /articles/123/comments PUT with payload /comments/123 Delete /comments/123 GET /comments/123 |
| user | Create user Update user Delete user View user |
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 操作。
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 发出请求时,控制器中声明的相关函数将被执行。控制器文件中的每个函数都可以使用 req 和 res 对象。这里的评论资源是文章的子资源。 所有的查询操作都是通过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,您将始终拥有一个灵活、可维护、易于理解的系统。
The above is the detailed content of Designing a RESTful API using NodeJS and Restify. For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
Undresser.AI Undress
AI-powered app for creating realistic nude photos
AI Clothes Remover
Online AI tool for removing clothes from photos.
Clothoff.io
AI clothes remover
Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!
Hot Article
Hot Tools
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
Advanced Conditional Types in TypeScript
Aug 04, 2025 am 06:32 AM
TypeScript's advanced condition types implement logical judgment between types through TextendsU?X:Y syntax. Its core capabilities are reflected in the distributed condition types, infer type inference and the construction of complex type tools. 1. The conditional type is distributed in the bare type parameters and can automatically split the joint type, such as ToArray to obtain string[]|number[]. 2. Use distribution to build filtering and extraction tools: Exclude excludes types through TextendsU?never:T, Extract extracts commonalities through TextendsU?T:Never, and NonNullable filters null/undefined. 3
Generate Solved Double Chocolate Puzzles: A Guide to Data Structures and Algorithms
Aug 05, 2025 am 08:30 AM
This article explores in-depth how to automatically generate solveable puzzles for the Double-Choco puzzle game. We will introduce an efficient data structure - a cell object based on a 2D grid that contains boundary information, color, and state. On this basis, we will elaborate on a recursive block recognition algorithm (similar to depth-first search) and how to integrate it into the iterative puzzle generation process to ensure that the generated puzzles meet the rules of the game and are solveable. The article will provide sample code and discuss key considerations and optimization strategies in the generation process.
How can you remove a CSS class from a DOM element using JavaScript?
Aug 05, 2025 pm 12:51 PM
The most common and recommended method for removing CSS classes from DOM elements using JavaScript is through the remove() method of the classList property. 1. Use element.classList.remove('className') to safely delete a single or multiple classes, and no error will be reported even if the class does not exist; 2. The alternative method is to directly operate the className property and remove the class by string replacement, but it is easy to cause problems due to inaccurate regular matching or improper space processing, so it is not recommended; 3. You can first judge whether the class exists and then delete it through element.classList.contains(), but it is usually not necessary; 4.classList
Vercel SPA routing and resource loading: Solve deep URL access issues
Aug 13, 2025 am 10:18 AM
This article aims to solve the problem of deep URL refresh or direct access causing page resource loading failure when deploying single page applications (SPAs) on Vercel. The core is to understand the difference between Vercel's routing rewriting mechanism and browser parsing relative paths. By configuring vercel.json to redirect all paths to index.html, and correct the reference method of static resources in HTML, change the relative path to absolute path, ensuring that the application can correctly load all resources under any URL.
Vercel Single Page Application (SPA) Deployment Guide: Solving Deep URL Asset Loading Issues
Aug 13, 2025 pm 01:03 PM
This tutorial aims to solve the problem of loading assets (CSS, JS, images, etc.) when accessing multi-level URLs (such as /projects/home) when deploying single page applications (SPAs) on Vercel. The core lies in understanding the difference between Vercel's routing rewriting mechanism and relative/absolute paths in HTML. By correctly configuring vercel.json, ensure that all non-file requests are redirected to index.html and correcting asset references in HTML as absolute paths, thereby achieving stable operation of SPA at any depth URL.
The Module Pattern in JavaScript: A Practical Guide
Aug 05, 2025 am 09:37 AM
ThemodulepatterninjavascriptsolvestheProbllobalscopepollutionandandandandandandandandandlackofencapsulation byusingClosuresandiifestocreatePrivat EvariaBlesandExPosonTrolledPublicapi; 1) IthidesInternal DataStusersandvalidatenamewithinacloslosloslosloslosloslus
Qwik: A Resumable Framework for Instant-Loading Web Apps
Aug 15, 2025 am 08:25 AM
Qwikachievesinstantloadingbydefaultthroughresumability,nothydration:1)TheserverrendersHTMLwithserializedstateandpre-mappedeventlisteners;2)Norehydrationisneeded,enablingimmediateinteractivity;3)JavaScriptloadson-demand,onlywhenuserinteractionoccurs;4
js add element to start of array
Aug 14, 2025 am 11:51 AM
In JavaScript, the most common method to add elements to the beginning of an array is to use the unshift() method; 1. Using unshift() will directly modify the original array, you can add one or more elements to return the new length of the added array; 2. If you do not want to modify the original array, it is recommended to use the extension operator (such as [newElement,...arr]) to create a new array; 3. You can also use the concat() method to combine the new element array with the original number, return the new array without changing the original array; in summary, use unshift() when modifying the original array, and recommend the extension operator when keeping the original array unchanged.


