Вступление
Помимо создания 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 .
Вы также можете подумать о дополнительных функциях, которые можно добавить в приложение. Надеюсь, вам понравилось.