Вступление
Помимо создания API, Node.js отлично подходит для создания стандартных веб-приложений. Он имеет мощные инструменты, чтобы удовлетворить вкус веб-разработчиков. В этом руководстве вы создадите веб-приложение, которое может служить локальной библиотекой.
При сборке вы узнаете о некоторых типах промежуточного программного обеспечения, вы увидите, как обрабатывать отправку форм в Node.js, а также сможете ссылаться на две модели.
Давайте начнем.
Начиная
Начните с установки экспресс-генератора на вашем компьютере.
| 
 1 
 | 
 npm install express-generator -g 
 | 
Запустите команду экспресс-генератора, чтобы сгенерировать ваше приложение.
| 
 1 
 | 
 express tutsplus-library —view=pug 
 | 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
 create : tutsplus-library 
  create : tutsplus-library/package.json 
  create : tutsplus-library/app.js 
  create : tutsplus-library/public 
  create : tutsplus-library/routes 
  create : tutsplus-library/routes/index.js 
  create : tutsplus-library/routes/users.js 
  create : tutsplus-library/views 
  create : tutsplus-library/views/index.pug 
  create : tutsplus-library/views/layout.pug 
  create : tutsplus-library/views/error.pug 
  create : tutsplus-library/bin 
  create : tutsplus-library/bin/www 
  create : tutsplus-library/public/javascripts 
  create : tutsplus-library/public/images 
  create : tutsplus-library/public/stylesheets 
  create : tutsplus-library/public/stylesheets/style.css 
  install dependencies: 
    $ cd tutsplus-library && npm install 
  run the app: 
    $ DEBUG=tutsplus-library:* npm start 
 | 
Теперь перенеситесь в ваш рабочий файл, откройте package.json и сделайте зависимости похожими на те, что приведены ниже.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
 | 
 #package.json 
{ 
  «name»: «tutsplus-library», 
  «version»: «0.0.0», 
  «private»: true, 
  «scripts»: { 
    «start»: «node ./bin/www» 
  }, 
  «dependencies»: { 
    «body-parser»: «~1.17.1», 
    «connect-flash»: «^0.1.1», 
    «cookie-parser»: «~1.4.3», 
    «debug»: «~2.6.3», 
    «express»: «~4.15.2», 
    «express-messages»: «^1.0.1», 
    «express-session»: «^1.15.5», 
    «express-validator»: «^4.2.1», 
    «mongoose»: «^4.11.12», 
    «morgan»: «~1.8.1», 
    «pug»: «~2.0.0-beta11», 
    «serve-favicon»: «~2.4.2» 
  } 
} 
 | 
Запустите команду для установки пакетов.
| 
 1 
 | 
 npm install 
 | 
Настройте входной файл
 app.js был создан при app.js команды генератора;  Тем не менее, вам нужно настроить дополнительную конфигурацию.  Отредактируйте файл, чтобы он выглядел так, как показано ниже. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
 | 
 #app.js 
var express = require(‘express’); 
var path = require(‘path’); 
var favicon = require(‘serve-favicon’); 
var logger = require(‘morgan’); 
var cookieParser = require(‘cookie-parser’); 
var bodyParser = require(‘body-parser’); 
const session = require(‘express-session’) 
const expressValidator = require(‘express-validator’) 
const flash = require(‘connect-flash’) 
const mongoose = require(‘mongoose’) 
// 1 
const genres = require(‘./routes/genres’); 
const books = require(‘./routes/books’); 
var app = express(); 
// 2 
mongoose.Promise = global.Promise 
const mongoDB = process.env.MONGODB_URI || 
mongoose.connect(mongoDB) 
// view engine setup 
app.set(‘views’, path.join(__dirname, ‘views’)); 
app.set(‘view engine’, ‘pug’); 
// uncomment after placing your favicon in /public 
//app.use(favicon(path.join(__dirname, ‘public’, ‘favicon.ico’))); 
app.use(logger(‘dev’)); 
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({ extended: false })); 
app.use(cookieParser()); 
app.use(express.static(path.join(__dirname, ‘public’))); 
// 3 
app.use(session({ 
  secret: ‘secret’, 
  saveUninitialized: true, 
  resave: true 
})) 
// 4 
app.use(expressValidator({ 
  errorFormatter: function(param, msg, value) { 
    var namespace = param.split(‘.’) 
    , root = namespace.shift() 
    , formParam = root 
    while(namespace.length) { 
      formParam += ‘[‘ + namespace.shift() + ‘]’ 
    } 
    return { 
      param : formParam, 
      msg : msg, 
      value : value 
    } 
  } 
})) 
// 5 
app.use(flash()) 
app.use(function (req, res, next) { 
  res.locals.messages = require(‘express-messages’) 
  next() 
}) 
// 6 
app.use(‘/genres’, genres); 
app.use(‘/books’, books); 
// catch 404 and forward to error handler 
app.use(function(req, res, next) { 
  var err = new Error(‘Not Found’); 
  err.status = 404; 
  next(err); 
}); 
// error handler 
app.use(function(err, req, res, next) { 
  // set locals, only providing error in development 
  res.locals.message = err.message; 
  res.locals.error = req.app.get(‘env’) === ‘development’ ? 
  // render the error page 
  res.status(err.status || 500); 
  res.render(‘error’); 
}); 
module.exports = app; 
 | 
- Вам потребовались два маршрута, которые вы будете использовать при создании этого приложения. Вы создадите файл маршрутов в ближайшее время. Требуемые маршруты присваиваются в качестве значений двум различным переменным, которые используются при настройке промежуточного программного обеспечения для ваших маршрутов.
 -   Вы настроили Mongoose для использования 
global.Promise. ПеременнойMongoDBназначаетсяMONGODB_URIвашей среды или путь к вашему локальному серверу Монго. Эта переменная передается в качестве аргумента для подключения к работающему серверу MongoDB. -   Вы устанавливаете промежуточное программное обеспечение сеанса, используя 
express-session. Это промежуточное ПО важно, так как вы будете отображать флеш-сообщения в некоторых частях вашего приложения. -   Вы настроили промежуточное ПО для проверки.  Это промежуточное программное обеспечение будет использоваться для проверки ввода формы, гарантируя, что пользователи приложения не отправят пустую форму.  В валидации используется установленный пакет, 
express-validator. -   Вы устанавливаете промежуточное ПО, которое пригодится при отображении флеш-сообщений.  Это промежуточное ПО использует 
connect-flash. - Маршруты для приложения настроены для использования требуемого файла маршрутов. Запросы, указывающие на / genres и / books, будут использовать файлы жанров и маршрутов книг соответственно. На данный момент вы не создали файлы маршрутов, но скоро это сделаете.
 
Книжно-жанровая модель
Модель книги будет использовать схему мангуста, чтобы определить, как будут структурированы книги. Создайте каталог с именем models и новый файл с именем Book.js. Вот как это выглядит.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
 | 
 #models/Book.js 
const mongoose = require(‘mongoose’) 
mongoose.Promise = global.Promise 
const Schema = mongoose.Schema 
const bookSchema = Schema({ 
  name: { 
    type: String, 
    trim: true, 
    required: ‘Please enter a book name’ 
  }, 
  description: { 
    type: String, 
    trim: true 
  }, 
  author: { 
    type: String, 
    trim: true, 
  }, 
  genre: [{ 
    type: Schema.Types.ObjectId, 
    ref: ‘Genre’ 
  }] 
}) 
module.exports = mongoose.model(‘Book’, bookSchema) 
 | 
  Здесь у вас есть четыре поля.  Последнее поле используется для хранения жанра, к которому относится каждая книга.  Поле жанра здесь ссылается на модель жанра, которая будет создана далее.  Вот почему тип установлен в Schema.Types.ObjectId , где будут сохраняться идентификаторы каждого ссылочного жанра.  ref указывает модель, на которую вы ссылаетесь.  Обратите внимание, что жанр сохраняется в виде массива, что означает, что книга может иметь более одного жанра. 
Давайте продолжим создавать модель жанра.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
 | 
 #models/genre.js 
const mongoose = require(‘mongoose’) 
mongoose.Promise = global.Promise 
const Schema = mongoose.Schema 
const genreSchema = Schema({ 
  name: { 
    type: String, 
    trim: true, 
    required: ‘Please enter a Genre name’ 
  } 
}) 
module.exports = mongoose.model(‘Genre’, genreSchema) 
 | 
  Для вашего жанра вам нужно только одно поле: name . 
Жанр Указатель Маршрут и вид
В этом уроке вы будете использовать два пути маршрутов для своего жанра: путь для добавления новых жанров и другой, в котором перечислены ваши жанры. Создайте файл в вашем каталоге маршрутов с именем genres.js .
Начните с того, что вам потребуются все модули, которые вы будете использовать.
| 
 1 
2 
3 
4 
5 
6 
 | 
 #routes/genres.js 
var express = require(‘express’); 
var router = express.Router(); 
const mongoose = require(‘mongoose’) 
const Genre = require(‘../models/Genre’) 
 | 
Затем добавьте маршрут, который обрабатывает индексный файл для ваших жанров.
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
 router.get(‘/’, (req, res, next) => { 
  const genres = Genre.find({}).exec() 
    .then((genres) => { 
      res.render(‘genres’, { genres: genres }) 
  }, (err) => { 
    throw err 
  }) 
}); 
 | 
Этот маршрут вызывается всякий раз, когда делается запрос к / жанрам . Здесь вы вызываете метод find для вашей модели Genre, чтобы получить все созданные жанры. Эти жанры затем отображаются в шаблоне под названием жанры . Давайте продолжим и создадим это, но сначала обновите ваш layout.pug, чтобы он выглядел так:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
 | 
 #views/layout.pug 
doctype html 
html 
  head 
    title= title 
    link(rel=’stylesheet’, href=’/stylesheets/style.css’) 
    link(rel=’stylesheet’, href=’https://bootswatch.com/paper/bootstrap.css’) 
    script(src=’https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js’) 
    script(src=’https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js’) 
  body 
    .container-fluid 
      block header 
        nav.navbar.navbar-inverse 
          .container-fluid 
            .navbar-header 
              button.navbar-toggle.collapsed(type=’button’, data-toggle=’collapse’, data-target=’#bs-example-navbar-collapse-2′) 
                span.sr-only Toggle navigation 
                span.icon-bar 
                span.icon-bar 
                span.icon-bar 
              a.navbar-brand(href=’#’) Local Library 
            #bs-example-navbar-collapse-2.collapse.navbar-collapse 
              ul.nav.navbar-nav.navbar-right 
                li 
                  a(href=’/books’) View Books 
                li 
                  a(href=’/books/add’) Add New Book 
                li 
                  a(href=’/genres’) View Genres 
                li 
                  a(href=’/genres/add’) Add New Genre 
        block content 
 | 
Это даст вашим взглядам хорошую структуру, чтобы помочь навигации. Теперь создайте файл представления под названием genre.pug . В этом файле вы будете просматривать созданные жанры и выводить каждый жанр в неупорядоченном списке.
Вот как должен выглядеть файл.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
 | 
 #views/genres.pug 
extends layout 
block content 
  h1 Genre 
  ul.well.well-lg 
    each genre, i in genres 
      li.well.well-sm 
        p #{genre.name} 
 | 
  Добавить новые жанровые маршруты и посмотреть 
Вернитесь в ваш route / genres.js, чтобы добавить маршруты, которые будут обрабатывать создание новых жанров.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
 #routes/genres.js 
// 1 
router.get(‘/add’, (req, res, next) => { 
  res.render(‘addGenre’) 
}) 
// 2 
router.post(‘/add’, (req, res, next) => { 
  req.checkBody(‘name’, ‘Name is required’).notEmpty() 
  const errors = req.validationErrors() 
  if (errors) { 
    console.log(errors) 
    res.render(‘addgenres’, { genre, errors }) 
  } 
  const genre = (new Genre(req.body)).save() 
    .then((data) => { 
      res.redirect(‘/genres’) 
    }) 
    .catch((errors) => { 
      console.log(‘oops…’) 
      console.log(errors) 
    }) 
}) 
// 3 
module.exports = router; 
 | 
- Работа этого маршрутизатора заключается в простом отображении страницы для добавления новых маршрутов. Этот маршрутизатор вызывается всякий раз, когда поступают запросы к / genres / add path.
 - Этот маршрутизатор обрабатывает отправку формы. Когда форма отправлена, мы проверяем, что имя введено пользователем. Если имя не введено, страница перерисовывается. Если проверки хороши, жанр сохраняется, и пользователь перенаправляется на страницу / genres .
 - Модуль экспортируется как маршрутизатор.
 
Теперь вы можете пойти дальше и создать страницу для добавления нового жанра.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
 #views/addGenre.pug 
extends layout 
block content 
  .row 
    .col-md-12 
      h1 Add Book 
      form(method=»POST», action=»/genres/add») 
        .form-group 
          label.col-lg-2.control.label Name 
          .col-lg-10 
            input.form-control(type=»text», name=’name’) 
        .form-group 
          .col-lg-10.col-lg-offset-2 
            input.button.btn.btn-primary(type=’submit’, value=’Submit’) 
             if errors 
        ul 
          for error in errors 
            li!= error.msg 
 | 
Книжные маршруты и просмотр
Создайте новый файл маршрута для книг и назовите его books.js . Как вы делали ранее с жанром, начните с необходимых модулей.
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
 #routes/books.js 
var express = require(‘express’); 
var router = express.Router(); 
const mongoose = require(‘mongoose’) 
const Book = require(‘../models/Book’) 
const Genre = require(‘../models/Genre’) 
 | 
Затем настройте маршрутизатор для отображения всех книг, сохраненных в библиотеке. Попробуйте сами, так как вы устанавливаете жанр; Вы всегда можете проверить, чтобы внести исправления.
Я полагаю, вы попробовали это — вот как это должно выглядеть.
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
 router.get(‘/’, (req, res, next) => { 
  const books = Book.find({}).exec().then((books) => { 
    res.render(‘books’, { books: books }) 
  }, (err) => { 
    throw err 
  }) 
}); 
 | 
Когда вызывается этот маршрутизатор, делается запрос на поиск всех книг, сохраненных в базе данных. Если все идет хорошо, книги отображаются на странице / books , иначе выдается ошибка.
Вам нужно создать новый файл для отображения всех книг, и вот как это должно выглядеть.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
 #views/books.pug 
extends layout 
block content 
  h1 Books 
  ul.well.well-lg 
    each book, i in books 
      li.well.well-sm 
        a(href=`/books/show/${book.id}`) #{book.name} 
        p= book.description 
 | 
Вы просто просматриваете возвращенные книги и выводите название и описание каждой книги, используя неупорядоченный список. Название книги указывает на отдельную страницу книги.
Добавить новые книжные маршруты и посмотреть
Следующий маршрутизатор, который вы настроите, будет обрабатывать добавление новых книг. Здесь будут использоваться два маршрутизатора: один просто отобразит страницу, а другой будет обрабатывать отправку формы.
Вот так выглядят роутеры.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
 router.get(‘/add’, (req, res, next) => { 
  const genres = Genre.find({}).exec() 
    .then((genres) => { 
      res.render(‘addBooks’, { genres }) 
    }) 
    .catch((err) => { 
      throw err 
    }) 
}) 
router.post(‘/add’, (req, res, next) => { 
  req.checkBody(‘name’, ‘Name is required’).notEmpty() 
  req.checkBody(‘description’, ‘Description is required’).notEmpty() 
  req.checkBody(‘genre’, ‘Genre is required’).notEmpty 
  const errors = req.validationErrors() 
  if (errors) { 
    console.log(errors) 
    res.render(‘addBooks’, { book, errors }) 
  } 
  const book = (new Book(req.body)).save() 
    .then((data) => { 
      res.redirect(‘/books’) 
    }) 
    .catch((errors) => { 
      console.log(‘oops…’) 
    }) 
}) 
 | 
В первом маршрутизаторе вы отображаете страницу / addBooks . Этот маршрутизатор вызывается при запросе / добавлении пути. Поскольку добавленные книги должны иметь жанры, вы хотите отобразить жанры, которые были сохранены в базе данных.
| 
 1 
2 
 | 
 const genres = Genre.find({}).exec() 
     .then((genres) => { 
 | 
Приведенный выше код находит все жанры в вашей базе данных и возвращает их в переменных жанрах. Благодаря этому вы сможете переключаться между жанрами и отображать их в виде флажков.
  Второй маршрутизатор обрабатывает отправку формы.  Сначала вы проверяете тело запроса, чтобы убедиться, что некоторые поля не пусты.  Здесь вам пригодится промежуточное ПО для express-validator , установленное вами в app.js.  Если есть ошибки, страница перерисовывается.  Если их нет, новый экземпляр Book сохраняется и пользователь перенаправляется на страницу / books. 
Давайте продолжим и создадим представления для этого.
  Создайте новый файл представления под названием addBooks.pug .  Обратите внимание, что имя представления соответствует первому параметру, данному для res.render.  Это потому, что вы визуализируете шаблон.  Во время перенаправления вы просто передаете путь, на который хотите перенаправить, как вы это сделали с res.redirect('/books') . 
Установив это, вот как должны выглядеть взгляды.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
 | 
 #views/addBooks.pug 
extends layout 
block content 
  .row 
    .col-md-12 
      h1 Add Book 
      form(method=»POST», action=»/books/add») 
        .form-group 
          label.col-lg-2.control-label Name 
          .col-lg-10 
            input.form-control(type=»text», name=’name’) 
        .form-group 
          label.col-lg-2.control-label Author 
          .col-lg-10 
            input.form-control(type=»text», name=’author’) 
        .form-group 
          label.col-lg-2.control-label Book Description 
          .col-lg-10 
            textarea#textArea.form-control(rows=’3′, name=’description’) 
        .form-group 
          label.col-lg-2.control-label Genre 
          .col-lg-10 
            for genre in genres 
              .checkbox 
                input.checkbox(type=’checkbox’, name=’genre’, id=genre._id, value=genre._id, checked=genre.checked) 
                label(for=genre._id) #{genre.name} 
        .form-group 
          .col-lg-10.col-lg-offset-2 
            input.button.btn.btn-primary(type=’submit’, value=’Submit’) 
             if errors 
        ul 
          for error in errors 
            li!= error.msg 
 | 
Здесь важно отметить форму действия и метод. Когда нажата кнопка отправки, вы отправляете запрос POST в / books / add . Еще одна вещь — вы снова просматриваете коллекцию возвращенных жанров и отображаете каждый из них.
Книга Показать маршрут и вид
Давайте перейдем к маршруту, чтобы обработать запросы, сделанные на каждой странице книги. Пока вы там, важно также экспортировать ваш модуль.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
 | 
 #routes/books.js 
router.get(‘/show/:id’, (req, res, next) => { 
  const book = Book.findById({ _id: req.params.id }) 
  .populate({ 
    path: ‘genre’, 
    model: ‘Genre’, 
    populate: { 
      path: ‘genre’, 
      model: ‘Book’ 
    } 
  }) 
  .exec() 
    .then((book) => { 
      res.render(‘book’, { book }) 
    }) 
    .catch((err) => { 
      throw err 
    }) 
}) 
module.exports = router; 
 | 
Никакой магии здесь не происходит.
  Во-первых, запросы к этому маршрутизатору должны иметь идентификатор: идентификатор книги.  Этот идентификатор получается из параметров запроса с использованием req.params.id .  Это используется для идентификации конкретной книги, которую следует получить из базы данных, поскольку идентификаторы уникальны.  Когда книга найдена, значение жанра книги заполняется всеми жанрами, которые были сохранены в этом экземпляре книги.  Если все идет хорошо, представление книги отображается, в противном случае выдается ошибка. 
Давайте создадим вид для книги. Вот как это должно выглядеть.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
 | 
 block content 
 .well.well-lg 
   h1 #[strong Name:] #{book.name} 
   ul 
     li #[strong Description:] #{book.description} 
     li #[strong Author]: #{book.author} 
     li #[strong Genre:] 
       each genre in book.genre 
         #{genre.name} 
         |, 
 | 
Вы можете запустить свой сервер узлов, выполнив:
| 
 1 
 | 
 DEBUG=tutsplus-library:* npm start 
 | 
Вывод
Теперь вы знаете, как создать стандартное веб-приложение в Node.js, а не просто простое приложение. Вы смогли обработать отправку формы, обратиться к двум моделям и настроить промежуточное программное обеспечение.
Вы можете пойти дальше, расширив приложение — попробуйте добавить возможность удаления книги. Сначала добавьте кнопку на страницу показа, а затем перейдите к файлам маршрутов и добавьте маршрутизатор для этого. Обратите внимание, что это будет запрос POST .
Вы также можете подумать о дополнительных функциях, которые можно добавить в приложение. Надеюсь, вам понравилось.