• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

使用Node.js构建Web应用程序

介绍

除了构建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;
  1. 您需要在构建此应用程序时将使用的两条路线。您将很快创建路由文件。所需的路由作为值分配给两个不同的变量,这些变量在为您的路由设置中间件时使用。

  2. 您将 Mongoose 设置为使用 global.Promise该变量MongoDB分配给MONGODB_URI您的环境或本地 mongo 服务器的路径。此变量作为参数传递以连接到正在运行的 MongoDB 服务器。

  3. 您使用express-session这个中间件很重要,因为您将在应用程序的某些部分显示 Flash 消息。

  4. 您设置了用于验证的中间件。该中间件将用于验证表单输入,确保应用程序的用户不会提交空表单。验证使用安装的包,  express-validator.

  5. 您设置了在显示 Flash 消息时会派上用场的中间件。该中间件使用connect-flash.

  6. 应用程序的路由设置为使用您需要的路由文件。指向/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;
  1. 该路由器的工作是简单地显示用于添加新路由的页面。每当向/genres/add路径发出请求时,都会调用此路由器。

  2. 该路由器处理表单的提交。提交表单时,我们会检查以确保用户输入了名称。如果未输入名称,则重新呈现页面。如果检查顺利,则保存类型并将用户重定向到 /genres页面。

  3. 该模块作为路由器导出。

现在您可以继续创建用于添加新流派的页面。

#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请求。


文章目录
  • 介绍
  • 入门
  • 设置入口文件
  • 书籍和类型模型
  • 流派索引路线和视图
  • 添加新的流派路线并查看
  • 书籍路线和视图
  • 添加新预订路线并查看
  • 书展路线和视图
  • 结论