Статьи

Создание веб-приложений с использованием Node.js

Помимо создания 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;
  1. Вам потребовались два маршрута, которые вы будете использовать при создании этого приложения. Вы создадите файл маршрутов в ближайшее время. Требуемые маршруты присваиваются в качестве значений двум различным переменным, которые используются при настройке промежуточного программного обеспечения для ваших маршрутов.
  2. Вы настроили Mongoose для использования global.Promise . Переменной MongoDB назначается MONGODB_URI вашей среды или путь к вашему локальному серверу Монго. Эта переменная передается в качестве аргумента для подключения к работающему серверу MongoDB.
  3. Вы устанавливаете промежуточное программное обеспечение сеанса, используя express-session . Это промежуточное ПО важно, так как вы будете отображать флеш-сообщения в некоторых частях вашего приложения.
  4. Вы настроили промежуточное ПО для проверки. Это промежуточное программное обеспечение будет использоваться для проверки ввода формы, гарантируя, что пользователи приложения не отправят пустую форму. В валидации используется установленный пакет, express-validator .
  5. Вы устанавливаете промежуточное ПО, которое пригодится при отображении флеш-сообщений. Это промежуточное ПО использует connect-flash .
  6. Маршруты для приложения настроены для использования требуемого файла маршрутов. Запросы, указывающие на / 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;
  1. Работа этого маршрутизатора заключается в простом отображении страницы для добавления новых маршрутов. Этот маршрутизатор вызывается всякий раз, когда поступают запросы к / genres / add path.
  2. Этот маршрутизатор обрабатывает отправку формы. Когда форма отправлена, мы проверяем, что имя введено пользователем. Если имя не введено, страница перерисовывается. Если проверки хороши, жанр сохраняется, и пользователь перенаправляется на страницу / genres .
  3. Модуль экспортируется как маршрутизатор.

Теперь вы можете пойти дальше и создать страницу для добавления нового жанра.

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 .

Вы также можете подумать о дополнительных функциях, которые можно добавить в приложение. Надеюсь, вам понравилось.