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

介绍

除了构建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;
  1. 您需要在构建此应用程序时要使用的两条路线。 您将很快创建路由文件。 将所需的路由作为值分配给两个不同的变量,这些变量在为路由设置中间件时使用。
  2. 您将Mongoose设置为使用global.Promise 。 为变量MongoDB分配了您环境的MONGODB_URI或本地mongo服务器的路径。 该变量作为参数传递给连接到正在运行的MongoDB服务器。
  3. 您可以使用express-session设置会话中间件。 该中间件非常重要,因为您将在应用程序的某些部分中显示Flash消息。
  4. 您设置了中间件进行验证。 该中间件将用于验证表单输入,以确保应用程序的用户不会提交空白表单。 验证使用已安装的软件包express-validator
  5. 您设置了中间件,该中间件在显示即时消息时会派上用场。 该中间件利用了connect-flash
  6. 设置应用程序的路由以使用所需的路由文件。 指向/ 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;
  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

书籍路线和检视

为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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值