作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
在为休息 API构建后端时,表达.js通常是节点的首选.js框架. 同时它也支持构建静态HTML和模板, 在本系列中, 我们将专注于使用打印稿进行后端开发. 生成的休息 API将是任何前端框架或外部后端服务都能够查询的API.
你需要:
在终端(或命令提示符)中,我们将为项目创建一个文件夹. 从该文件夹中运行 npm init
. 这会产生 一些基本的节点.我们需要的Js项目文件.
接下来,我们将添加表达.Js框架和一些有用的库:
NPM I 表达调试Winston 表达- Winston 歌珥
这些库是有充分理由的 节点.js开发人员 最喜欢的:
调试
是一个我们将用来避免调用的模块吗 控制台.日志()
在开发我们的应用程序时. 这样,我们就可以在故障排除期间轻松地过滤调试语句. 它们也可以在生产环境中完全关闭,而不必手动删除.温斯顿
负责记录请求到我们的API和返回的响应(和错误). 表达-温斯顿
直接与表达集成.. js,这样所有的标准api相关 温斯顿
日志代码已经完成.歌珥
是一张快件吗.Js中间件允许我们启用 跨域资源共享. 没有这个, 我们的API只能从与后端完全相同的子域提供服务的前端使用.我们的后端在运行时使用这些包. 但我们也需要安装一些 发展 打印稿配置的依赖项. 为此,我们将运行:
——save-dev @types/歌珥 @types/表达 @types/调试 source-map-support tslint typescript
这些依赖项需要在我们的应用自己的代码中启用打印稿, 以及表达使用的类型.Js和其他依赖项. 当我们使用像WebStorm或VSCode这样的IDE时,通过允许我们在编码时自动完成一些函数方法,这可以节省很多时间.
中的最终依赖项 包.json
应该是这样的:
“依赖”:{
“调试”:“^ 4.2.0",
“表达”:“^ 4.17.1",
:“表达-温斯顿 ^ 4.0.5",
“温斯顿”:“^ 3.3.3",
“歌珥”:“^ 2.8.5"
},
" devDependencies ": {
“@types /歌珥”:“^ 2.8.7",
“@types /调试”:“^ 4.1.5",
“@types /表达”:“^ 4.17.2",
:“source-map-support ^ 0.5.16",
:“tslint ^ 6.0.0",
:“打印稿^ 3.7.5"
}
现在我们已经安装了所有必需的依赖项,让我们开始构建自己的代码!
在本教程中,我们将创建三个文件:
./应用程序.ts
./共同/共同.路线.配置.ts
./用户/用户.路线.配置.ts
项目结构的两个文件夹(常见的
和 用户
)是有各自职责的独立模块. 从这个意义上说,我们最终将为每个模块提供以下部分或全部内容:
这个文件夹结构提供了 基本休息 API设计,这是本教程系列其余部分的早期起点,并且足以开始练习.
在 常见的
文件夹,我们创建 常见的.路线.配置.ts
文件看起来像下面这样:
从“表达”中输入表达;
导出类CommonRoutesConfig {
应用:表达.应用程序;
名称:字符串;
构造函数(应用程序:表达.应用程序,名称:字符串){
这.App = App;
这.Name = Name;
}
getName () {
返回这.名称;
}
}
这里我们创建路由的方式是可选的. 但是因为我们在用打印稿, 我们的路由场景是一个练习使用继承的机会 扩展
关键字,我们很快就会看到. 在这个项目中, 所有的路由文件都有相同的行为:它们都有一个名称(我们将使用它进行调试),并且可以访问主表达.js 应用程序
object.
现在,我们可以开始创建用户路由文件了. 在 用户
文件夹,我们来创建 用户.路线.配置.ts
然后开始这样编码:
导入CommonRoutesConfig../共同/共同.路线.配置”;
从“表达”中输入表达;
导出类UsersRoutes扩展CommonRoutesConfig {
构造函数(应用程序:表达.应用程序){
超级(应用,“UsersRoutes”);
}
}
在这里,我们导入 CommonRoutesConfig
类并将其扩展到我们的新类 UsersRoutes
. 通过构造函数,我们将应用程序(main 表达.应用程序
对象)和UsersRoutes到的名称 CommonRoutesConfig
的构造函数.
这个例子非常简单, 但是当扩展到创建多个路由文件时, 这将帮助我们避免重复代码.
假设我们想要在这个文件中添加新特性,比如日志记录. 我们可以添加必要的字段到 CommonRoutesConfig
类,然后是所有扩展的路由 CommonRoutesConfig
会有访问权限吗.
如果我们想拥有一些功能呢 类似的 在这些类之间(比如配置API端点), 但这需要为每个类提供不同的实现? 一种选择是使用打印稿的特性 抽象.
让我们创建一个非常简单的抽象函数 UsersRoutes
类(以及未来的路由类)将继承自 CommonRoutesConfig
. 假设我们想强制所有的路由都有一个函数(这样我们就可以从公共构造函数调用它) 配置ureRoutes ()
. 我们将在这里声明每个路由类资源的端点.
要做到这一点,我们将添加三个快速的东西 常见的.路线.配置.ts
:
摘要
对我们的 class
行,以启用该类的抽象.摘要 配置ureRoutes (): 表达.应用程序;
. 这强制进行任何类扩展 CommonRoutesConfig
提供与该签名匹配的实现(如果不匹配), 打印稿编译器会抛出一个错误.这.配置ureRoutes ();
在构造函数的末尾,因为我们现在可以确定这个函数将存在.结果:
从“表达”中输入表达;
导出抽象类CommonRoutesConfig {
应用:表达.应用程序;
名称:字符串;
构造函数(应用程序:表达.应用程序,名称:字符串){
这.App = App;
这.Name = Name;
这.配置ureRoutes ();
}
getName () {
返回这.名称;
}
摘要 配置ureRoutes (): 表达.应用程序;
}
有了它,任何类扩展 CommonRoutesConfig
必须有一个函数调用 配置ureRoutes ()
返回一个 表达.应用程序
object. 这意味着 用户.路线.配置.ts
需要更新:
导入CommonRoutesConfig../共同/共同.路线.配置”;
从“表达”中输入表达;
导出类UsersRoutes扩展CommonRoutesConfig {
构造函数(应用程序:表达.应用程序){
超级(应用,“UsersRoutes”);
}
配置ureRoutes () {
//(我们将在这里添加实际的路由配置)
返回这.应用程序;
}
}
回顾一下我们的作品:
我们首先进口的是 常见的.路线.配置
文件,然后 表达
模块. 然后我们定义 UserRoutes
类,说我们想要它扩展 CommonRoutesConfig
基类,这意味着我们承诺它将实现 配置ureRoutes ()
.
将信息发送到 CommonRoutesConfig
类,我们正在使用 构造函数
这个班的. 它期望收到 表达.应用程序
对象,我们将在下一步中更深入地描述它. 与 super ()
,我们传递给 CommonRoutesConfig
的构造函数是应用程序和路由的名称,在这个场景中是UsersRoutes. (super ()
,反过来,将调用我们的实现 配置ureRoutes ()
.)
的 配置ureRoutes ()
函数是我们为休息 API的用户创建端点的地方. 在这里,我们将使用 应用程序 和它的 路线 来自表达的功能.js.
使用的想法 应用程序.路线()
功能是避免代码重复, 这很容易,因为我们正在创建一个具有良好定义的资源的休息 API. 本教程的主要资源是 用户. 在这种情况下,我们有两种情况:
用户
在请求路径的末尾. (在本文中,我们不会讨论查询过滤、分页或其他此类查询.)用户/:用户标识
.的方式 .路线()
快件作品.js让我们用一些优雅的链接来处理HTTP动词. 这是因为 .get ()
, .post ()
等.的实例,都返回相同的 IRoute
这是第一个 .路线()
电话是. 最终的配置是这样的:
配置ureRoutes () {
这.应用程序.路线(' /用户)
.(要求:表达.请求,res: 表达.响应) => {
res.状态(200).send(' List of 用户 ');
})
.(要求:表达.请求,res: 表达.响应) => {
res.状态(200).send(' Post to 用户 ');
});
这.应用程序.路线(/用户/:userId)
.(要求:表达.请求,res: 表达.响应,下一个:表达.NextFunction) => {
//这个中间件函数在任何请求/用户/:userId之前运行
//但是它还没有完成任何事情——
//使用next()将控制传递给下面的下一个适用函数
next ();
})
.(要求:表达.请求,res: 表达.响应) => {
res.状态(200).得到请求id ${req.参数个数.userId}’);
})
.(要求:表达.请求,res: 表达.响应) => {
res.状态(200).发送(' 把请求id ${req.参数个数.userId}’);
})
.补丁(要求:表达.请求,res: 表达.响应) => {
res.状态(200).发送('请求补丁id ${req.参数个数.userId}’);
})
.删除(要求:表达.请求,res: 表达.响应) => {
res.状态(200).请求删除id ${req.参数个数.userId}’);
});
返回这.应用程序;
}
上面的代码允许任何休息 API客户端调用我们的 用户
端点带有 帖子
或者一个 得到
请求. 类似地,它允许客户机调用我们的 /用户/:userId
端点带有 得到
, 把
, 补丁
, or 删除
请求.
但对于 /用户/:userId
,还添加了通用中间件 所有()
函数,该函数将在任何 get ()
, put ()
, 补丁()
, or delete ()
功能. 当(在本系列后面)我们创建仅由经过身份验证的用户访问的路由时,此功能将非常有用.
你可能已经注意到了 .所有()
函数-与任何中间件一样-有三种类型的字段: 请求
, 响应
, NextFunction
.
NextFunction
作为回调函数,允许控制通过任何其他中间件函数. 一路走来, 在控制器最终向请求者发送响应之前,所有中间件将共享相同的请求和响应对象.应用程序.ts
现在我们已经配置了一些基本的路由框架, 我们将开始配置应用程序的入口点. 让我们创建 应用程序.ts
文件放到项目文件夹的根目录,并以下面的代码开始:
从“表达”中输入表达;
从' HTTP '导入*作为HTTP;
从' Winston '输入* as Winston;
从表达-温斯顿中导入* as 表达Winston;
从'歌珥'中导入歌珥;
导入CommonRoutesConfig./共同/共同.路线.配置”;
导入{UsersRoutes}./用户/用户.路线.配置”;
从'调试'中导入调试;
在本文中,只有两个导入是新的:
http
是一个节点.js-native模块. 这是启动我们快车的必要条件.js应用程序.分析体
表达自带的中间件是什么.js. 它在控制转到我们自己的请求处理程序之前解析请求(在我们的示例中为JSON).现在我们已经导入了文件,我们将开始声明我们想要使用的变量:
Const 应用:表达.应用程序 = 表达();
Const服务器:HTTP.服务器= http.create服务器(应用);
Const port = 3000;
const 路线: Array = [];
const 调试Log:调试.IDebugger = 调试('应用程序');
的 表达()
函数返回主表达.Js应用程序对象,我们将在整个代码中传递该对象,首先将其添加到 http.服务器
object. (我们需要启动 http.服务器
配置我们的 表达.应用程序
.)
我们将监听端口3000——这是打印稿 会自动推断 是一个 数量
-而不是标准端口80 (HTTP)或443 (HTTPS),因为这些通常用于应用程序的前端.
没有规则规定端口必须是3000 -如果未指定, 任意端口 但是在节点.js和表达.所以我们继续这个传统.
我们仍然可以在自定义端口本地运行, 即使我们希望后端响应标准端口上的请求. 这需要一个 反向代理 在端口80或443上接收特定域或子域的请求. 然后它会将它们重定向到我们的内部端口3000.
的 路线
Array将跟踪我们的路由文件,以便调试,我们将在下面看到.
最后, 调试Log
会变成一个类似的函数吗 控制台.日志
, 但更好的是:它更容易微调,因为它会自动作用域到我们想要调用的文件/模块上下文. (在这个例子中,当我们把它作为字符串传递给 调试()
构造函数.)
现在,我们已经准备好配置所有的表达了.js中间件模块和我们API的路由:
//我们在这里添加中间件,将所有传入请求解析为JSON
应用程序.使用(表达.json ());
//我们在这里添加中间件来允许跨域请求
应用程序.使用(歌珥());
//这里我们正在准备表达Winston日志中间件配置,
//自动记录表达处理的所有HTTP请求.js
const 日志记录器Options: 表达Winston.LoggerOptions = {
运输:[新的温斯顿.传输.控制台()),
格式:温斯顿.格式.结合(
温斯顿.格式.json (),
温斯顿.格式.prettyPrint (),
温斯顿.格式.Colorize ({all: true})
),
};
if (!过程.env.调试){
日志记录器Options.meta = false; // when not 调试ging, 日志 请求s as one-liners
}
//使用上述配置初始化日志记录器
应用程序.使用(表达Winston.记录器(日志记录器Options));
//这里我们将UserRoutes添加到数组中,
//发送表达后.Js的应用程序对象,以便将路由添加到我们的应用程序中!
路线.推动(新UsersRoutes (应用程序));
//这是一个简单的路由,以确保一切正常工作
const runningMessage = '服务器运行在http://localhost:${port} ';
应用程序.Get ('/', (req: 表达).请求,res: 表达.响应) => {
res.状态(200).发送(runningMessage)
});
的 表达Winston.日志记录器
hook into 表达.Js,自动记录细节——通过与 调试
-每个已完成的请求. 我们传递给它的选项将整齐地格式化并为相应的终端输出着色, 在调试模式下使用更详细的日志记录(默认值).
注意,我们必须定义我们的路由 后 我们建立了 表达Winston.日志记录器
.
最后也是最重要的一点:
服务器.listen(port, () => {
路线.forEach((路线: CommonRoutesConfig) => {
为${路线 . net配置的路由.getName()}’);
});
//我们唯一的例外是避免控制台.Log(),因为我们
//总是想知道服务器何时完成启动
控制台.日志(runningMessage);
});
这实际上启动了我们的服务器. 一旦启动,节点.Js将运行我们的回调函数, 在调试模式下,哪个会报告到目前为止我们配置的所有路由的名称, 只是 UsersRoutes
. 在那之后, 我们的回调通知我们后端已准备好接收请求, 即使在生产模式下运行.
包.json
将打印稿转换为JavaScript并运行应用现在我们的骨架已经准备好运行了, 我们首先需要一些样板文件配置来启用打印稿编译. 让我们添加文件 ts配置.json
在项目根目录下:
{
" compilerOptions ": {
“目标”:“es2016”,
“模块”:“常见的js”,
“outDir”:“./ 经销 ",
“严格”:没错,
“esModuleInterop”:没错,
“inlineSourceMap”:真的
}
}
然后我们只需要添加最后的润色 包.json
以以下脚本的形式:
"脚本":{
“开始”:“tsc && 节点——unh和led-rejections =严格 ./ 经销 /应用程序.js",
"调试": "出口 调试 =* . && NPM运行启动”,
"测试": "echo \"Error: no 测试 specified\" && 退出1”
},
的 测试
Script是一个占位符,我们将在本系列的后面部分替换它.
的 tsc 在 开始
script属于打印稿. 它负责将打印稿代码翻译成JavaScript,然后输出到 经销
文件夹. 然后,我们只需运行构建版本 节点 ./ 经销 /应用程序.js
.
我们通过 ——unh和led-rejections =严格
到节点.js(即使是节点 . js).Js v16+)因为在实践中, 使用直接的“崩溃并显示堆栈”方法进行调试比使用 表达Winston.errorLogger
object. 即使在生产环境中也是如此,让节点.Js继续运行,尽管未处理的拒绝可能会使服务器处于意外状态, 允许进一步(和更复杂)的bug发生.
的 调试
脚本调用 开始
脚本,但首先定义了 调试
环境变量. 这使得我们所有的 调试Log ()
语句(以及表达的类似语句).Js本身,它使用相同的 调试
模块)将有用的详细信息输出到终端——这些详细信息(方便地)在使用标准在生产模式下运行服务器时被隐藏 npm开始
.
尝试运行 NPM运行调试
你自己,然后把它和 npm开始
查看控制台输出的变化情况.
提示:您可以将调试输出限制为 应用程序.ts
文件的 调试Log ()
语句的使用 调试 =应用
而不是 调试 = *
. 的 调试
模块通常是相当灵活的,而这个特性 也不例外.
Windows用户可能需要更改 出口
to 集
自 出口
是如何在Mac和Linux上运行的. 如果您的项目需要支持多种开发环境, 跨环境包 这里提供了一个简单的解决方案.
与 NPM运行调试
or npm开始
仍然运行,我们的休息 API将准备好服务端口3000上的请求. 此时,我们可以使用cURL, 邮递员, 失眠等. 测试后端.
因为我们只为用户资源创建了一个骨架, 我们可以简单地发送没有主体的请求,以查看一切都按预期工作. 例如:
curl——请求 得到 'localhost:3000/用户/12345'
我们的后端应该返回答案 请求id 12345的得到
.
至于 帖子
荷兰国际集团(ing):
curl——请求 帖子 'localhost:3000/用户' \
——data-raw”
这和我们构建骨架的所有其他类型的请求看起来非常相似.
在本文中, 我们通过从头配置项目并深入了解表达的基础知识,开始创建休息 API.js框架. 然后,我们通过构建一个模式,迈出了掌握打印稿的第一步 UsersRoutesConfig
扩展 CommonRoutesConfig
我们将在本系列的下一篇文章中重用该模式. 我们通过配置我们的 应用程序.ts
进入我们的新路线,然后 包.json
用脚本来构建和运行我们的应用程序.
但即使是用表达制作的休息 API的基础.js和打印稿是相当复杂的. In 下一部分 本系列的, 我们专注于为用户资源创建适当的控制器,并深入研究一些有用的服务模式, 中间件, 控制器, 和模型.
完整的项目可用 GitHub上,本文结束时的代码参见 toptal-article-01
分支.
绝对! 对于流行的npm包(包括表达)来说,这是非常常见的.要有相应的打印稿类型定义文件. 节点就是这样.Js本身,加上包含的子组件,如调试包.
是的. 节点.js本身可以用来创建生产就绪的休息 api, 还有一些流行的框架,比如表达.Js来减少不可避免的样板文件.
不,对于那些有现代JavaScript背景的人来说,开始学习打印稿并不难. 对于那些有面向对象编程经验的人来说,这甚至更容易. 但掌握打印稿的所有细微差别和最佳实践需要时间,就像掌握任何技能一样.
这取决于项目,但绝对推荐用于节点.js编程. 它是一种更具表现力的语言,用于在后端对现实世界的问题域进行建模. 这使代码更具可读性,并减少了出现bug的可能性.
打印稿在任何有JavaScript的地方使用, 但它特别适合于大型应用程序. 它使用JavaScript作为基础, 增加了静态类型和对面向对象编程(OOP)范例更好的支持. 这反过来又支持更高级的开发和调试体验.
马科斯在IT和开发领域有17年以上的经验. 他的爱好包括休息架构、敏捷开发方法和JS.
12
世界级的文章,每周发一次.
世界级的文章,每周发一次.