Эта статья была первоначально опубликована в блоге разработчиков Okta . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.
Если вы занимались веб-разработкой с помощью Node в последние несколько лет, вы, вероятно, использовали Express. Даже если вы не использовали его напрямую, многие фреймворки, призванные сделать веб-разработку еще проще, все еще построены на Express.
Одной из ключевых функций в Express является возможность создавать маршруты. Бесконечная комбинация URL-адресов может попасть на один и тот же сервер Express, и маршруты позволяют определить, какие URL-адреса выполняют какой фрагмент кода. У вас могут быть параметры и шаблоны, чтобы вам не приходилось явно указывать каждую конечную точку.
В этом руководстве я расскажу, как создать сервер, и научу вас всему, что нужно знать о маршрутах в Express.
Что такое маршрут в экспрессе?
Маршруты определяют, какие данные должны быть доставлены по любому URL. Давайте возьмем самый простой файловый сервер в качестве примера. Скажем, у вас есть файловая структура:
files/
├── images/
│ ├── cat.png
│ ├── dog.jpg
│ └── pig.bmp
└── text/
├── README.md
└── todo.txt
Затем вы можете запустить простой HTTP-сервер, который будет автоматически обслуживать эти файлы и создавать индекс для каталогов. Там нет files/index.html
Если вы перейдете на /images/cow.gif
что-то подает .
npm install -g http-server
cd files
http-server
В Express маршрут состоит из method
path
handler
Методы, пути и обработчики, о мой!
method
GET
POST
Вы также можете указать, что вы хотите, чтобы Express обрабатывал один и тот же путь для всех методов, если вы выберете.
path
Если вы работаете с корнем вашего приложения, это описывает абсолютный URL. Путь может быть определен несколькими способами.
- Простые строки : строка
'/'
Строка'/asdf'
/asdf
- Подстановочные знаки : строка также может содержать несколько подстановочных знаков, которые работают подобно регулярному выражению, но немного ограничены:
-
?
: А?
говорит, что предыдущий символ не является обязательным. Путь'/Joh?n'
/Jon
/John
-
+
+
Путь'/ni+ce'
/nice
/niiiiiiiiiiiiiiiiice
-
*
*
Путь'/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)
- Запрос (запрос) : запрос содержит все виды информации о том, что было задано пользователем. Отсюда вы можете получить доступ к пути, параметрам, заголовкам и множеству других вещей. По всем вопросам по запросу вы можете обратиться к справочнику по API
- Ответ (
req
: Ответ — это способ отправки информации обратно пользователю. Самый простой способ вернуть данные — это методres
.send
Опять же, вы можете найти все методы в справочнике по API - Следующий обратный вызов (
res.send('Hello, world!')
:next
Вы можете использовать один обработчик для обработки информации, и когда это будет сделано, он может вызватьnext
Если вы передадите строку, она выдаст ошибку, которую вы можете перехватить в другом месте или отобразить пользователю (например,next()
Что такое роутер в экспрессе?
Теперь, когда вы немного более знакомы с маршрутами, чем это отличается от маршрутизатора? Вы можете думать о маршрутизаторе как о наборе маршрутов. Это может быть полезным способом организации различных разделов вашего приложения.
При использовании маршрутизатора вы можете думать с точки зрения корневого пути, даже если вы собираетесь использовать этот маршрутизатор из некоторого подпути. Например, скажем, у вас есть API для управления сообщениями. Вы могли бы иметь маршрутизатор с путем next('You must be authenticated to access this route')
'/'
GET
Вы можете иметь другой путь POST
'/:id'
Ваше приложение может затем взять этот маршрутизатор и разместить его в GET
PUT
Сам маршрутизатор не должен заботиться о том, каким будет его глобальный путь, и может даже использоваться в нескольких маршрутах (например, /messages
app.use('/messages', messageRouter)
/messages
Создайте простое приложение с маршрутизатором в узле с помощью Express
Уже достаточно разговоров … давайте перейдем к реальному коду. Для начала создайте папку, в которой будет размещен весь ваш код. Затем настройте папку /texts
Вы можете использовать /email
Вам также нужно будет установить Express .
package.json
Создайте файл npm init
index.js
mkdir my-first-router
cd my-first-router
npm init -y
npm install [email protected] [email protected]
Это говорит Express использовать Handlebars ( 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}`))
В hbs
path
/
index.hbs
Чтобы убедиться, что в Express есть шаблоны для рендеринга, создайте новую папку с именем content
p
Когда вы views
layout.hbs
layout.hbs
Это позволяет настроить скелет для приложения. Вот некоторый базовый HTML- код с использованием Bootstrap , который даст вам приятный стиль без необходимости написания CSS. Это также отобразит {{{body}}}
title
просмотров / 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
index.hbs
Чтобы сделать разработку немного проще, вы можете установить <p>{{content}}</p>
nodemon
Затем измените файл npm install --save-dev [email protected]
package.json
"scripts"
, Это позволит вам просто запустить nodemon .
npm start
Теперь в вашем терминале, если вы "scripts": {
Затем вы можете перейти на
"start": "nodemon ."
}
npm start
Создать маршрутизатор в Express
Ну, это немного скучно. Как насчет того, чтобы сделать что-то полезное? Давайте создадим простой список дел. Начните с создания маршрутизатора для управления списком элементов. Создайте новый файл с именем http://localhost:3000
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!',
Это немного больше, так что я сохранил его напоследок. Если вы знакомы с HTML, это не должно быть слишком плохо, чтобы следовать. Handlebars добавляет несколько функций, которые позволяют вам получить доступ к переменным. В этом случае вы используете блок todo
{{#if}}
Единственный JavaScript, используемый здесь, предназначен для автоматической отправки формы, когда вы что-то меняете. Если бы JavaScript был отключен, он все равно работал бы при нажатии клавиши «Ввод» на клавиатуре, благодаря скрытой кнопке «Автосохранение».
просмотров / todo.hbs
{{#each}}
Теперь перейдите по <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>
Добавить аутентификацию пользователя в узле
Теперь у вас есть функциональный список дел. Возможно, вы заметили, что это сработает только в том случае, если вы хотите, чтобы все пользователи использовали один и тот же список. Если вы добавите аутентификацию, у вас будет отдельный список дел для каждого пользователя.
Добавление пользователей не должно быть проблемой. На самом деле это можно сделать просто с помощью Okta. Что такое окта? спросите вы. Okta — это облачный сервис, который позволяет разработчикам создавать, редактировать и безопасно хранить учетные записи пользователей и данные учетных записей пользователей, а также связывать их с одним или несколькими приложениями.
Если у вас его еще нет, зарегистрируйте бесплатную учетную запись разработчика .
Вам нужно будет сохранить некоторую информацию для использования в приложении. Создайте новый файл с именем http://localhost:3000/todo
В нем введите URL своей организации.
.env
Вам также понадобится случайная строка для использования в качестве секрета приложения для сеансов. Вы можете создать это с помощью следующих команд:
HOST_URL=http://localhost:3000
OKTA_ORG_URL=https://{yourOktaOrgUrl}
Затем войдите в консоль разработчика, перейдите в « Приложения» и нажмите « Добавить приложение» . Выберите Web , затем нажмите Next . Дайте вашему приложению имя, например «Мой первый маршрутизатор». Измените базовый URI на echo -e "\nAPP_SECRET=`npx -q uuid`" >> .env
URI перенаправления входа в систему на
http://localhost:3000/
Готово»
Нажмите « Изменить» и добавьте URI перенаправления выхода из системы http://localhost:3000/authorization-code/callback
Сохранить» .
Страница, на которую вы http://localhost:3000/
.env
Скопируйте идентификатор клиента и секрет клиента.
OKTA_CLIENT_ID={yourClientId}
OKTA_CLIENT_SECRET={yourClientSecret}
Теперь вернемся к коду. Вам нужно будет добавить промежуточное программное обеспечение OIDC от Okta для управления аутентификацией. Это также зависит от использования сессий. Вам нужно будет использовать dotenv
.env
Чтобы установить зависимости, которые вам понадобятся, выполните следующую команду:
npm install @okta/[email protected] [email protected] [email protected]
Теперь измените ваш файл index.js
Здесь вы добавите промежуточное программное обеспечение OIDC для сеанса и маршрут logout
Вы также добавляете промежуточное программное обеспечение специально для todoRouter
app.use('/todo', oidc.ensureAuthenticated(), todoRouter)
Добавляя oidc.ensureAuthenticated()
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', {
Чтобы немного упростить ситуацию при выходе пользователя, добавьте ссылку на список дел с домашней страницы.
просмотров / index.hbs
<p>{{content}}</p>
<a href="/todo">Go to To-Do List</a>
Вы также можете добавить приветственное сообщение и кнопку выхода из системы в файл layout.hbs
просмотров / 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 })
})
Узнайте больше о Node, Express и безопасной веб-разработке
Теперь, когда у вас есть полностью функциональный список дел, я призываю вас расширить его. Попробуйте сохранить данные в базе данных, или пусть Okta сохранит их для вас ! Посмотрите, можете ли вы создать еще несколько маршрутизаторов для добавления на веб-сервер.
Если вы хотите увидеть окончательный пример кода, вы можете найти его на GitHub .
Если вы хотите узнать больше о Node и Express, ознакомьтесь с некоторыми из этих других статей в блоге разработчиков Okta:
- Постройте и поймите Express Middleware через примеры
- Создание и понимание простого веб-сайта Node.js с аутентификацией пользователя
- Создайте простой REST API с помощью Node и OAuth 2.0
- Создание безопасной аутентификации узла с помощью Passport.js и OpenID Connect
- Защитите Node API с помощью учетных данных клиента OAuth 2.0
Если у вас есть какие-либо вопросы по поводу этого поста, пожалуйста, добавьте комментарий ниже. Чтобы получить более интересный контент, следите за @oktadev в Twitter, например, за нами в Facebook , или подпишитесь на наш канал YouTube .