介绍
除了构建api之外,node.js 还非常适合构建标准 Web 应用程序。它具有强大的工具来满足 Web 开发人员的口味。在本教程中,您将构建一个可用作本地库的 Web 应用程序。
在构建过程中,您将了解一些类型的中间件,您将了解如何在 Node.js 中处理表单提交,并且您还将能够引用两个模型。
让我们开始吧。
入门
首先在您的机器上安装 express 生成器。
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
在您运行生成器命令时创建;但是,您需要设置额外的配置。编辑文件,使其看起来像我下面的内容。
#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
.您设置了在显示 Flash 消息时会派上用场的中间件。该中间件使用
connect-flash
.应用程序的路由设置为使用您需要的路由文件。指向/genres和/books的请求将分别使用流派和书籍路由文件。此时您还没有创建路由文件,但您很快就会创建。
书籍和类型模型
Book Model 将使用 Mongoose Schema 来定义书籍的结构。创建一个名为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)
这里有四个字段。最后一个字段用于存储每本书所属的类型。这里的流派字段引用了流派模型,该模型将在接下来创建。这就是 type 设置为 的原因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发出请求时,都会调用此路由。在这里,您在 Genre 模型上调用 find 方法来获取所有已创建的流派。然后将这些流派呈现在一个名为流派的模板上。让我们继续创建它,但首先,将您的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
书籍路线和视图
为书籍创建一个新的路由文件,并将其命名为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.jsexpress-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请求。