介绍
除了构建API,Node.js还非常适合构建标准的Web应用程序。 它具有功能强大的工具,可满足Web开发人员的需求。 在本教程中,您将构建一个可以用作本地库的Web应用程序。
在构建过程中,您将了解某些类型的中间件,您将看到如何在Node.js中处理表单提交,并且还可以引用两个模型。
让我们开始吧。
入门
首先在计算机上安装Express Generator。
npm install express-generator -g
运行express generator命令来生成您的应用程序。
express tutsplus-library --view=pug
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,并使依赖项与下面的内容类似。
#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"
}
}
运行命令以安装软件包。
npm install
设置条目文件
app.js
是在您运行generator命令时创建的; 但是,您需要设置额外的配置。 编辑文件,使其看起来像我下面的文件。
#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 || 'mongodb://127.0.0.1/tutsplus-library'
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' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
- 您需要在构建此应用程序时要使用的两条路线。 您将很快创建路由文件。 将所需的路由作为值分配给两个不同的变量,这些变量在为路由设置中间件时使用。
- 您将Mongoose设置为使用
global.Promise
。 为变量MongoDB
分配了您环境的MONGODB_URI
或本地mongo服务器的路径。 该变量作为参数传递给连接到正在运行的MongoDB服务器。 - 您可以使用
express-session
设置会话中间件。 该中间件非常重要,因为您将在应用程序的某些部分中显示Flash消息。 - 您设置了中间件进行验证。 该中间件将用于验证表单输入,以确保应用程序的用户不会提交空白表单。 验证使用已安装的软件包
express-validator
。 - 您设置了中间件,该中间件在显示即时消息时会派上用场。 该中间件利用了
connect-flash
。 - 设置应用程序的路由以使用所需的路由文件。 指向/ genres和/ books的请求将分别使用流派和book路由文件。 目前,您还没有创建路线文件,但是很快就可以创建。
书籍和体裁模型
书籍模型将使用猫鼬模式来定义书籍的结构。 创建一个名为models的目录,以及一个名为Book.js的新文件。 这是它的样子。
#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
,在该位置将保存每个引用流派的ID。 ref
指定您要参考的模型。 请注意,流派保存为数组,这意味着一本书可以具有多个流派。
让我们继续创建流派模型。
#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的文件。
首先要求您将要使用的所有模块。
#routes/genres.js
var express = require('express');
var router = express.Router();
const mongoose = require('mongoose')
const Genre = require('../models/Genre')
接下来,放入处理您类型的索引文件的路由。
router.get('/', (req, res, next) => {
const genres = Genre.find({}).exec()
.then((genres) => {
res.render('genres', { genres: genres })
}, (err) => {
throw err
})
});
每当对/ genres发出请求时,都会调用此路由。 在这里,您可以在流派模型上调用find方法来获取所有已创建的流派。 然后将这些流派呈现在称为genre的模板上。 让我们继续进行创建,但是首先,将您的layout.pug更新为如下所示:
#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的视图文件。 在此文件中,您将循环浏览创建的流派并将每个流派输出到无序列表中。
这是文件的外观。
#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}
添加新流派路线并查看
返回您的routes / genres.js ,添加将处理创建新类型的路由。
#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路径发出请求时,都会调用此路由器。
- 该路由器处理表单的提交。 提交表单后,我们检查以确保用户输入了名称。 如果未输入名称,则页面将重新呈现。 如果检查顺利,将保存流派,并将用户重定向到/ genres页面。
- 该模块将作为路由器导出。
现在,您可以继续创建页面以添加新类型。
#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
书籍路线和检视
为book创建一个新的路由文件,并将其命名为books.js 。 正如您之前使用该类型所做的那样,首先需要必需的模块。
#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')
接下来,设置路由器以显示存储在库中的所有书籍。 以您建立流派的方式自行尝试; 您可以随时返回进行更正。
我想您已经尝试过了-它应该是这样。
router.get('/', (req, res, next) => {
const books = Book.find({}).exec().then((books) => {
res.render('books', { books: books })
}, (err) => {
throw err
})
});
调用此路由器时,将请求查找保存在数据库中的所有书籍。 如果一切顺利,这些书将显示在/ books页面上,否则将引发错误。
您需要创建一个新文件来显示所有书籍,这是它的外观。
#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
您只需浏览返回的书,并使用无序列表输出每本书的名称和描述。 书名指向书的各个页面。
添加新书路线并查看
您设置的下一个路由器将处理新书的添加。 这里将使用两个路由器:一个将仅呈现页面,而另一个将处理表单的提交。
这就是路由器的外观。
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页面。 当对/ add路径发出请求时,将调用此路由器。 由于添加的书籍应该具有类型,因此您要显示已保存到数据库中的类型。
const genres = Genre.find({}).exec()
.then((genres) => {
上面的代码在数据库中找到所有类型,并以变量类型返回它们。 这样,您就可以循环浏览各种类型并将其显示为复选框。
第二个路由器处理表单的提交。 首先,检查请求的主体以确保某些字段不为空。 这是您在app.js中设置的express-validator
中间件很方便的地方。 如果有错误,将重新呈现页面。 如果没有,则将保存新的Book实例,并将用户重定向到/ books页面。
让我们继续为此创建视图。
创建一个名为addBooks.pug的新视图文件。 请注意,视图的名称与给res.render的第一个参数匹配。 这是因为您正在渲染模板。 重定向期间,您只需传递要重定向到的路径,就像使用res.redirect('/books')
。
确定这一点后,视图应该是这样。
#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
这里要注意的重要事项是表单操作和方法。 单击提交按钮后,您正在向/ books / add发出POST请求。 另一件事-再次遍历返回的流派集合并显示它们中的每一个。
图书展示路线和视图
让我们放下路由来处理对每个书页的请求。 当您在那里时,也必须导出模块。
#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;
这里没有魔术发生。
首先,对此路由器发出的请求必须有一个ID:书的ID。 该id是使用req.params.id
从请求的参数中获得的。 由于ID是唯一的,因此用于标识应从数据库中获取的特定书籍。 找到该书后,该书的体裁值将填充所有已保存到该书本实例的体裁。 如果一切顺利,将呈现书本视图,否则将引发错误。
让我们为一本书创建视图。 这是它的外观。
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}
|,
您可以通过运行以下命令来启动节点服务器:
DEBUG=tutsplus-library:* npm start
结论
现在您知道了如何在Node.js中构建标准的Web应用程序,而不仅仅是一个简单的待办应用程序。 您能够处理表单提交,引用两个模型并设置一些中间件。
您可以通过扩展应用程序来进一步操作-尝试添加删除书籍的功能。 首先在显示页面上添加一个按钮,然后转到路由文件并为此添加一个路由器。 请注意,这将是POST请求。
您还可以考虑将更多功能添加到应用程序中。 我希望你喜欢它。
翻译自: https://code.tutsplus.com/tutorials/build-web-application-using-nodejs--cms-29652