Статьи

Создайте свой первый маршрутизатор в Node с помощью Express

Эта статья была первоначально опубликована в блоге разработчиков 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 маршрут состоит из methodpathhandler

Методы, пути и обработчики, о мой!

methodGETPOST Вы также можете указать, что вы хотите, чтобы 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/42params.postId'42'

Метод и путь необходимы для того, чтобы знать, когда что-то делать, но обработчик — это функция обратного вызова, которая фактически вызывается в этих случаях. Обработчику передается requestresponsenext(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'

Ваше приложение может затем взять этот маршрутизатор и разместить его в GETPUT Сам маршрутизатор не должен заботиться о том, каким будет его глобальный путь, и может даже использоваться в нескольких маршрутах (например, /messagesapp.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}`))
В hbspath/index.hbs

Чтобы убедиться, что в Express есть шаблоны для рендеринга, создайте новую папку с именем contentp Когда вы viewslayout.hbslayout.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.postremove Если форма содержит 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Сохранить» .

Настройки приложения Okta

Страница, на которую вы 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 Вы также добавляете промежуточное программное обеспечение специально для todoRouterapp.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:

Если у вас есть какие-либо вопросы по поводу этого поста, пожалуйста, добавьте комментарий ниже. Чтобы получить более интересный контент, следите за @oktadev в Twitter, например, за нами в Facebook , или подпишитесь на наш канал YouTube .