express路由子路由器
本文最初发布在Okta开发人员博客上 。 感谢您支持使SitePoint成为可能的合作伙伴。
如果最近几年您使用Node进行过任何Web开发,那么您可能已经使用过Express。 即使您没有直接使用它,许多意在使Web开发变得更加简单的框架仍然基于Express。
Express的主要功能之一就是能够创建路线。 URL的无限组合可以在同一台Express服务器上运行,而路由是确定哪些URL运行哪段代码的方式。 您可以具有参数和通配符,从而不必显式声明每个端点。
在本教程中,我将引导您创建服务器,并教您所有有关Express中路由的知识。
Express中的路线是什么?
路由确定在给定任何URL的情况下应传递哪些数据。 让我们以最基本的文件服务器为例。 假设您的文件结构为:
files/
├── images/
│ ├── cat.png
│ ├── dog.jpg
│ └── pig.bmp
└── text/
├── README.md
└── todo.txt
然后,您可以运行一个简单的HTTP服务器,该服务器将自动提供这些文件并为目录创建索引。 没有files/index.html ,但是服务器仍在生成网页并根据该文件夹中的文件提供内容。 如果你去/images/cow.gif你会得到一个404错误-即使没有文件存在,它仍然煮好的东西 。
npm install -g http-server
cd files
http-server

在Express中,路由由method , path和handler 。
方法,路径和处理程序,我的天哪!
该method可以是任何HTTP动词,例如GET (用于获取内容-这是大多数网页使用的内容)或POST (用于将内容发送到服务器-这在HTML表单中很常见)。 如果选择,您还可以指定希望Express为所有方法处理相同的路径。
path是描述相对URL的字符串或正则表达式。 如果您使用的是应用程序的根目录,则说明绝对URL。 可以通过几种方式定义路径。
- 简单字符串 :字符串
'/'表示您要在路由器的根目录使用此路由。 字符串'/asdf'将覆盖路径/asdf - 通配符 :字符串还可以包含一些通配符,其作用与正则表达式相似,但有一些限制:
-
?:A?说前一个字符是可选的。 路径'/Joh?n'将覆盖/Jon和/John -
+:A+表示前一个字符可以重复任意多次,但必须至少重复一次。'/ni+ce'路径将覆盖/nice以及/niiiiiiiiiiiiiiiiice -
*:A*表示前一个字符是可选的,可以根据需要多次重复。 路径'/wow!*'将匹配/wow,/wow!,甚至/wow!!!!!!!!!!!! -
():您还可以将通配符应用于一组字符。'/(ha)+'将匹配/ha,/haha和/hahahahaha,但不匹配/hah
-
- 正则表达式 :如果您想超越基本通配符,则可以使用正则表达式。 使用
/^\/(pen-)?((pine)?apple-)+pen$/您可以匹配/apple-pen,/pineapple-pen或/pen-pineapple-apple-pen。 - 参数 :另一个非常有用的功能是您可以在路径中包含参数。 这使您可以轻松地提供具有动态部分的RESTful URL。 路径
'/posts/:postId'不仅会匹配/posts/42,而且请求还将包含一个params.postId变量,其值为'42'。
方法和路径对于知道何时执行某项操作至关重要,但是处理程序是在这些情况下实际上会被调用的回调函数。 向处理程序传递一个request ,一个response和一个next回调,这些参数通常写为(req, res, next) 。
- Request(
req) :请求包含有关用户要求的各种信息。 在这里,您可以访问路径,参数,标题和许多其他内容。 对于请求中的所有内容,您可以查阅API参考 - 响应(
res) :响应是您将信息发送回用户的方式。 发送数据的最简单方法是使用.send方法(例如res.send('Hello, world!')),但是还有许多其他方法。 同样,您可以在API参考中找到所有方法 - Next Callback(
next) :next函数允许您对同一路由使用多个处理程序。 您可以使用一个处理程序来处理信息,完成后可以调用next()表示可以继续使用下一个处理程序。 如果您传递一个字符串,它将抛出一个错误,您可以在其他地方捕获该错误,或者向用户显示(例如next('You must be authenticated to access this route'))。
什么是Express中的路由器?
现在,您对路由有了更多的了解,它与路由器有何不同? 您可以将路由器视为路由的集合。 这是组织应用程序不同部分的有用方法。
使用路由器时,即使您打算从某个子路径使用该路由器,也可以根据根路径进行思考。 例如,假设您有一个用于管理消息的API。 你可以有一个路径的路由器'/'来GET所有消息或POST一个新的消息。 您可以使用另一个路径'/:id'来GET或PUT (编辑)特定消息。
然后,您的应用程序可以使用该路由器,并使用app.use('/messages', messageRouter)将其托管在/messages 。 路由器本身不必关心它的全局路径,甚至可以在多个路由中使用(例如/messages , /texts和/email )。
使用Express在Node中创建带有路由器的简单应用
讨论已经足够了……让我们看一些实际的代码。 首先,创建一个包含所有代码的文件夹。 然后设置一个package.json文件夹来帮助管理依赖项。 您可以使用npm init来执行此操作。 您还需要安装Express 。
mkdir my-first-router
cd my-first-router
npm init -y
npm install express@4.16.4 hbs@4.0.1
使用以下代码创建一个index.js文件:
index.js
const express = require('express')
const path = require('path')
const app = express()
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')
app.get('/', (req, res) => {
res.render('index', {
title: 'Hello, world!',
content: 'How are you?'
})
})
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`App listening on port ${port}`))
这告诉Express将把手 ( hbs )用作视图引擎。 它使用Node的内置path来告诉它包含视图的目录。 告诉/路径使用index.hbs呈现页面,这会将content放在段落( p )标记中。
为确保Express具有要渲染的模板,请创建一个名为views的新文件夹,然后在其中创建一个名为layout.hbs的新文件。 当您告诉Express渲染视图时,它将首先渲染layout.hbs并将视图的内容放在{{{body}}}标签内。 这使您可以为应用设置框架。 这是一些使用Bootstrap的基本HTML,可为您提供漂亮的样式,而无需编写任何CSS。 这还将使title传递到/路由中的上下文中。
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
views / layout.hbs
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<main>
{{{body}}}
</main>
</body>
</html>
您还需要创建一个index.hbs视图,该视图现在才是非常基本的:
views / index.hbs
<p>{{content}}</p>
为了使开发更加容易,您可以使用以下命令安装nodemon :
npm install --save-dev nodemon@1.18.4
然后修改您的package.json文件,以便"scripts"条目包含带有nodemon .的启动脚本nodemon . 。 这样,您就可以简单地运行npm start并且无论何时进行更改,服务器都会自动重新启动:
"scripts": {
"start": "nodemon ."
}
现在在终端中,如果您键入npm start ,则将启动服务器。 然后,您可以转到http://localhost:3000查看该应用程序正在运行。

在Express中创建路由器
好吧,这很无聊。 如何使其做些有用的事情? 让我们创建一个简单的待办事项清单。 首先创建一个路由器来管理项目列表。 新建一个名为todo.js文件:
todo.js
const express = require('express')
const router = express.Router()
let todo = []
router.post('/', (req, res, next) => {
todo = [...req.body.todo || []]
if (req.body.remove) todo.splice(req.body.remove, 1)
if (req.body.new) todo.push({})
next()
})
router.use('/', (req, res) => {
res.render('todo', { title: 'To-do list', todo })
})
module.exports = router
在这里,您有两个路由处理程序。 第一个侦听POST请求(由router.post表示)。 它将用从表单收到的所有内容的副本替换待办事项列表。 如果表单包含remove属性(包含索引),它将使用splice删除该索引处的元素。 如果表单包含new属性,则将新项推入数组。 修改待办事项列表后,它将调用next()继续进行下一个路由处理程序。
始终使用第二个路由处理程序(由router.use表示)。 它的唯一目的是呈现待办事项列表。 通过这样分离路由,您可以轻松地始终执行一件事,而仅在某些情况下(在这种情况下,针对POST请求)可以轻松地执行另一件事。
要告诉应用程序使用此路由器,您必须在index.js添加几行:
index.js
@@ -1,11 +1,15 @@
const express = require('express')
const path = require('path')
+const todoRouter = require('./todo')
const app = express()
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')
+app.use(express.urlencoded({ extended: true }))
+app.use('/todo', todoRouter)
+
app.get('/', (req, res) => {
res.render('index', {
title: 'Hello, world!',
现在为todo模板。 它更大一点,所以我最后保存了它。 如果您熟悉HTML,它应该很不错。 把手添加了一些使您可以访问变量的功能。 在这种情况下,如果没有任何项目,您将使用{{#if}}块来呈现特殊的东西,并且使用{{#each}}块来以最小的标记来呈现每个列表项。
此处使用的唯一JavaScript是在您更改某些内容时自动提交表单。 如果禁用了JavaScript,则由于标记了“自动保存”的隐藏按钮,因此在按键盘上的“ Enter”键时仍然可以使用。
views / todo.hbs
<form method="post">
<div class="row">
<div class="col">
<button hidden>Autosave</button>
<button class="btn btn-success" name="new" value="true">New</button>
</div>
</div>
<div class="row mt-3">
<div class="col">
{{#if todo.length}}
<ul class="list-group">
{{#each todo}}
<li class="list-group-item d-flex align-items-center">
<input
type="checkbox"
onchange="this.form.submit()"
name="todo[{{@index}}][checked]"
{{#if this.checked}}checked{{/if}}
/>
<input
name="todo[{{@index}}][text]"
onchange="this.form.submit()"
class="form-control mx-2"
value="{{this.text}}"
/>
<button class="btn btn-danger" name="remove" value="{{@index}}">Remove</button>
</li>
{{/each}}
</ul>
{{else}}
<h5>Your To-Do List is empty</h5>
{{/if}}
</div>
</div>
<style>
input[type=checkbox]:checked + input {
text-decoration: line-through;
opacity: 0.75;
}
</style>
</form>
现在转到http://localhost:3000/todo并将一些项目输入到您的待办事项列表中。

在节点中添加用户身份验证
现在,您有了功能齐全的工作清单。 您可能已经注意到,只有当您希望每个使用它的人共享同一列表时,此方法才起作用。 如果添加身份验证,则可以为每个用户提供单独的待办事项列表。
添加用户不必费劲。 实际上,只需使用Okta即可完成。 什么是Okta? ,您可能会问。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。
如果您还没有一个,请注册一个永久性的开发者帐户 。
您将需要保存一些信息以在应用程序中使用。 创建一个名为.env的新文件。 在其中输入您的组织网址。
HOST_URL=http://localhost:3000
OKTA_ORG_URL=https://{yourOktaOrgUrl}
您还需要一个随机字符串作为会话的“应用程序秘密”。 您可以使用以下命令生成它:
echo -e "\nAPP_SECRET=`npx -q uuid`" >> .env
接下来,登录到开发人员控制台,导航至“ 应用程序” ,然后单击“ 添加应用程序” 。 选择“ Web” ,然后单击“ 下一步” 。 为您的应用程序命名,例如“我的第一个路由器”。 将基本URI更改为http://localhost:3000/ ,并将登录重定向URI更改为http://localhost:3000/authorization-code/callback ,然后单击完成。
点击编辑并添加http://localhost:3000/的注销重定向URL ,然后点击保存 。

创建应用程序后进入的页面包含需要保存到.env文件的更多信息。 复制客户ID和客户机密。
OKTA_CLIENT_ID={yourClientId}
OKTA_CLIENT_SECRET={yourClientSecret}
现在回到代码。 您需要添加Okta的OIDC中间件来控制身份验证。 它还依赖于使用会话。 您将需要使用dotenv从.env文件中读取变量。 要安装所需的依赖项,请运行以下命令:
npm install @okta/oidc-middleware@1.0.1 dotenv@6.1.0 express-session@1.15.6
现在修改您的index.js文件。 在这里,您将添加会话和OIDC中间件,以及logout路径,以便用户可以注销该应用程序。 您还正在向todoRouter添加专门的中间件( app.use('/todo', oidc.ensureAuthenticated(), todoRouter) )。 通过添加oidc.ensureAuthenticated() ,可以让Okta确保只有在用户登录后才能访问该路由。如果用户未登录并尝试访问该路由,则将其转到一个安全的网站登录,然后重定向到您的网站。
index.js
@@ -1,14 +1,46 @@
+require('dotenv').config()
+
const express = require('express')
const path = require('path')
+const session = require('express-session')
+const { ExpressOIDC } = require('@okta/oidc-middleware')
+
const todoRouter = require('./todo')
+const oidc = new ExpressOIDC({
+ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`,
+ client_id: process.env.OKTA_CLIENT_ID,
+ client_secret: process.env.OKTA_CLIENT_SECRET,
+ redirect_uri: `${process.env.HOST_URL}/authorization-code/callback`,
+ scope: 'openid profile'
+})
+
const app = express()
+app.use(session({
+ secret: process.env.APP_SECRET,
+ resave: true,
+ saveUninitialized: false
+}))
+app.use(oidc.router)
+
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')
app.use(express.urlencoded({ extended: true }))
-app.use('/todo', todoRouter)
+app.use('/todo', oidc.ensureAuthenticated(), todoRouter)
+
+app.get('/logout', (req, res) => {
+ if (req.userContext) {
+ const idToken = req.userContext.tokens.id_token
+ const to = encodeURI(process.env.HOST_URL)
+ const params = `id_token_hint=${idToken}&post_logout_redirect_uri=${to}`
+ req.logout()
+ res.redirect(`${process.env.OKTA_ORG_URL}/oauth2/default/v1/logout?${params}`)
+ } else {
+ res.redirect('/')
+ }
+})
app.get('/', (req, res) => {
res.render('index', {
为了使用户注销时的操作变得容易一些,请从主页添加指向待办事项列表的链接。
views / index.hbs
<p>{{content}}</p>
<a href="/todo">Go to To-Do List</a>
您还可以在您的layout.hbs添加欢迎消息和注销按钮。
views / layout.hbs
@@ -12,6 +12,12 @@
</head>
<body class="container">
<h1>{{title}}</h1>
+ {{#if userinfo}}
+ <h4>
+ Welcome back, {{userinfo.given_name}}!
+ <small><a href="/logout">Click here to log out</a></small>
+ </h4>
+ {{/if}}
<main>
{{{body}}}
</main>
为此,您需要在呈现视图时将userinfo添加到上下文中。
todo.js
--- a/todo.js
+++ b/todo.js
@@ -13,7 +13,7 @@ router.post('/', (req, res, next) => {
})
router.use('/', (req, res) => {
- res.render('todo', { title: 'To-do list', todo })
+ res.render('todo', { title: 'To-do list', todo, userinfo: req.userContext.userinfo })
})
module.exports = router
index.js
@@ -43,7 +43,10 @@ app.get('/logout', (req, res) => {
})
app.get('/', (req, res) => {
+ const { userinfo } = req.userContext || {}
+
res.render('index', {
+ userinfo,
title: 'Hello, world!',
content: 'How are you?'
})
好的,所以现在您需要用户登录才能编辑待办事项列表,但这仍然是一个共享列表。 为了将其分为每个用户一个单独的列表,请对todo.js进行另一个小的更改。
todo.js
@@ -2,17 +2,21 @@ const express = require('express')
const router = express.Router()
-let todo = []
+const todosByUser = {}
router.post('/', (req, res, next) => {
- todo = [...req.body.todo || []]
+ const todo = [...req.body.todo || []]
if (req.body.remove) todo.splice(req.body.remove, 1)
if (req.body.new) todo.push({})
+ todosByUser[req.userContext.userinfo.sub] = todo
+
next()
})
router.use('/', (req, res) => {
+ const todo = todosByUser[req.userContext.userinfo.sub] || []
+
res.render('todo', { title: 'To-do list', todo, userinfo: req.userContext.userinfo })
})

了解有关节点,快速和安全Web开发的更多信息
现在您有了功能齐全的待办事项清单,我鼓励您对其进行扩展。 尝试将数据存储在数据库中,甚至让Okta为您存储数据! 查看是否可以创建更多路由器以添加到Web服务器。
如果您想查看最终的代码示例,可以在GitHub上找到它。
如果您想了解有关Node和Express的更多信息,请查看Okta开发人员博客上的其他一些文章:
- 通过示例构建和理解Express中间件
- 通过用户身份验证构建并了解一个简单的Node.js网站
- 使用Node和OAuth 2.0构建简单的REST API
- 使用Passport.js和OpenID Connect构建安全节点身份验证
- 使用OAuth 2.0客户端凭据保护Node API
如果您对此帖子有任何疑问,请在下面添加评论。 有关更多精彩内容, 请在Twitter上关注@oktadev , 在Facebook上关注我们,或订阅我们的YouTube频道 。
翻译自: https://www.sitepoint.com/build-your-first-router-in-node-with-express/
express路由子路由器
本文介绍了Express路由的基础知识,包括路由的定义、方法、路径和处理程序。通过实例展示了如何在Node.js中创建带有路由器的简单应用,包括创建路由器、添加用户身份验证,以及如何利用Express的路由功能组织Web应用的不同部分。

448

被折叠的 条评论
为什么被折叠?



