从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(二)

咱们在平时所用到的一些网站、App,它们会将咱们的数据进行保存,当咱们关闭这些网站或者 App 后,下次打开还能看到咱们以前的一些文字、视频记录。在迷你全栈电商应用实战系列的第二篇教程中,咱们将经过基于 Node.js 平台的 Express 框架实现后端 API 数据接口,而且将数据存储在 MongoDB 中。这样咱们的网站就可以记录用户添加的商品,而且不管之后何时打开,都能获取咱们以前的记录。html

提示

阅读这篇文章须要你对 Express 框架有必定的了解。若是不熟悉的话,能够阅读这篇教程快速上手哦。前端

欢迎阅读《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列:vue

  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(一)
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(二)(也就是这篇)
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(三)(敬请期待
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(四)(敬请期待
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)(敬请期待
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(六)(敬请期待
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(七)(敬请期待
  • 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(八)(敬请期待
若是您以为咱们写得还不错,记得 点赞 + 关注 + 评论 三连,鼓励咱们写出更好的教程💪

快速开始

代码

你能够在 Github 查看这一部分教程最终的源码:源码地址git

初始化项目

首先,让咱们使用 express-generator 脚手架来初始化咱们的 Express 项目。在终端运行以下命令来安装:github

npm install -g express-generator

打开终端,输入以下命令测试是否安装成功:vue-router

express --version # 4.15.5

而后输入以下命令初始化咱们的 Express 项目:mongodb

express vue-online-shop-backend

当项目初始化成功以后,接下来经过以下命令开启项目:数据库

cd vue-online-shop-backend
npm install
npm start

接着打开浏览器,输入 http://localhost:3000/ 查看咱们初始好的项目效果。express

初探脚手架代码

经过  express-generator 初始化的项目代码中,咱们在整个教程中只须要了解下面四个文件:npm

  • app.js:Express 应用主文件
  • bin/www:用来开启服务器的脚本
  • routes/index.js:路由主文件
  • views/index.ejs:主页的模板文件,这里因为咱们只打算实现 API 数据接口,因此不用关心

与以前的 Express 教程不一样的是,脚手架代码并无把全部的路由都放在 app.js 中,而是根据不一样的子应用(users、index)进行了拆分,这也与该系列第一篇教程中 vue-router 的嵌套路由不谋而合。

咱们大体地看一下 app.jsroutes/index.js 的内容,快速过一遍 Express 的基础知识。首先来看一下 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');

var index = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 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')));

app.use('/', index);
app.use('/users', users);

// 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;

开头是导入相关依赖,而后经过调用 express() 初始化 express 实例,接着咱们设置了模板引擎为 ejs,以及模板引擎的存放目录,而后就是一系列中间件的加载使用,最后导出 express 实例,丢给 bin/www 脚本进行调用并启动服务器。

提示

这里咱们能够看到,咱们导入的两个路由 indexusers,也和其余中间件同样被处理,因此在 Express 中 “一切皆中间件”。

让咱们再来看一看咱们的路由部分 routes/index.js,路由是咱们 API 服务器的核心,咱们对数据进行增删改查都须要访问特定的路由接口,咱们在整个教程中几乎都是围绕路由的操做。

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

上面的代码,首先导入 express,而后使用其属性方法生成了一个  router 实例,接着定义了 get 这一 HTTP 方法来处理以 GET 方法访问咱们服务器地址为 / 时如何进行处理,最后导出咱们的 index 路由。

咱们的 API 服务器实际上就是经过 HTTP 的各类方法(POST、DELETE、PUT、GET 等)访问咱们定义的路由,进而对数据库进行相应的增删改查操做以获取咱们指望的数据。

小结

经过简单的讲解 express-generator 脚手架为咱们生成的上面四个文件,咱们学到了以下知识:

  • 在 Express 中,一切皆中间件(Middlewares),咱们经过组合中间件来处理复制的后端逻辑。
  • 咱们的 API 服务器实际上就是经过定义一系列路由,当以不一样的 HTTP 方法访问这些路由接口时,对数据进行对应的增删改查操做。
  • 虽然 Express 也能够经过模板引擎展现用户界面,可是因为咱们的迷你电商应用的前端已经用 Vue 来实现了,因此不须要模板引擎。

接入 MongoDB 数据库

解决数据持久化存储最流行的方案无疑是数据库,而 MongoDB 凭借其优异的性能、可扩展性和灵活的数据模式,从众多数据库产品中脱颖而出。而且,MongoDB 的核心功能是基于 BSON(Binary JSON)实现的,甚至提供了 JavaScript Shell,所以在 Node 社区更是深受欢迎。MongoDB 能够从其官网上下载。下载并安装好以后,新打开一个终端(命令控制台),运行如下命令打开数据库(Windows 用户能够搜索 mongo.exe 并打开):

$ mongod
2019-12-22T18:10:25.285+0800 I CONTROL  [initandlisten] MongoDB starting : pid=14475 port=27017 dbpath=/data/db 64-bit host=mRc
2019-12-22T18:10:25.285+0800 I CONTROL  [initandlisten] db version v3.6.0
2019-12-22T18:10:25.285+0800 I CONTROL  [initandlisten] git version: a57d8e71e6998a2d0afde7edc11bd23e5661c915
...

开启 mongod 后会输出不少日志信息,而且能够经过 localhost:27017 进行访问。而后回到咱们以前开启的终端,安装 Mongoose 这个 npm 包:

$ npm install mongoose

Mongoose 是 MongoDB 最流行的 ODM(Object Document Mapping,对象文档映射),使用起来要比底层的 MongoDB Node 驱动更方便。

接着咱们在咱们的 app.js 文件中导入 mongoose ,而且经过 mongoose 提供的接口链接咱们的 MongoDB 数据库:

// ...
const mongoose = require('mongoose');

// ...

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 链接数据库
mongoose.connect(`mongodb://localhost:27017/test`);


// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
// ...

接着咱们经过 npm start 运行服务器,咱们就在 Express 中链接上了咱们的 MongoDB 数据库,虽然如今还看不到任何效果,咱们立刻会编写路由来操做数据库来测试链接的有效性。

容许资源跨域访问

接着咱们要作一点额外的操做,尽管它看起来和咱们的项目没什么关联性,可是确是一个必要的一环,那就是开启资源跨域访问 CORS (Cross-Origin Resources Sharing)。CORS 是用来限制此域名下的资源访问解决方案,当它关闭时,另一个域名访问此域名的资源时会被拒绝。若是想详细了解什么是 CORS,这里推荐一篇阮一峰的文章,里面很细致的讲解了 CORS 的原理。

咱们打开 app.js 文件,添加以下代码:

// ...

// Database connection here
mongoose.connect(`mongodb://localhost:27017/test`);

// CORS config here
app.all('/*', function(req, res, next) {
  // CORS headers
  res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  // Set custom headers for CORS
  res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
  if (req.method == 'OPTIONS') {
    res.status(200).end();
  } else {
    next();
  }
});

一般意义上,上面的代码存在不少问题,咱们通常意义上会使用 NPMcors 来解决,固然咱们这里使用了比较简单粗暴的方式。

设计数据库的 Schemas 和 Models

咱们要在服务器中经过 mongoose 与 MongoDB 数据库进行交互,须要定义 SchemaModel。经过定义它们来告诉 mongoose 你须要的数据结构和对应的数据类型是什么。

咱们来建立 model/index.js 文件编写咱们的 Schema

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const model = mongoose.model.bind(mongoose);
const ObjectId = mongoose.Schema.Types.ObjectId;

const productSchema = Schema({
  id: ObjectId,
  name: String,
  image: String,
  price: Number,
  description: String,
  manufacturer: { type: ObjectId, ref: 'Manufacturer' }
});

const manufacturerSchema = Schema({
  id: ObjectId,
  name: String,
});

const Product = model('Product', productSchema);
const Manufacturer = model('Manufacturer', manufacturerSchema);

module.exports = { Product, Manufacturer };

Schema 接收一个 JavaScript 对象来描述咱们须要的数据结构和对应的数据类型,除了咱们熟知的像 StringNumber 等数据类型外,ObjectId 是一个特殊的数据类型,咱们用它来定义咱们的单个 MongoDB 文档的主键,用于标志存储数据的惟一性。

咱们还能够看到,在咱们的 productSchema 中,manufacturer 数据结构咱们定义了一个 ref 属性,这是 MongoDB 为咱们提供的相似关系数据库的外键功能,容许咱们建立一对多的数据文档,因此 productSchemamanufacturer 属性对应着的数据类型为一条 Manufacturer 记录。

接着咱们经过 model 来建立对于的数据模型,而后导出咱们建立好的数据模型。这里的 model 就是经典的 MVC 设计模式中的 Model

完成 API 路由

路由是 Express 的关键组成部分,也是客户端与服务器进行交互的入口,在 Express 路由中接受两个参数:Request 和 Response,一个用来获取客户端的请求,一个用来发送给客户端服务器的响应。

打开 app.js 文件,加入以下代码:

// ...

var index = require('./routes/index');
var users = require('./routes/users');
const api = require('./routes/api');

var app = express();

// ...

app.use('/', index);
app.use('/users', users);
app.use('/api/v1', api);

// ...

能够看到,咱们导入了 api 路由,并定义了访问路径 /api/v1。全部访问 /api/v1 及其子路径如 /api/v1/xxx 都会激活 api 处理函数,在经典的 MVC 设计模式中,api 也被成为 Controllers

接着咱们编写 api Controllers,在这里面定义操做商品和制造商的路由接口,这里咱们将采用经典的 RESTful API 来编写咱们的路由接口:

const express = require('express');
const router = express.Router();
const productController = require('../../controllers/product');
const manufacturerController = require('../../controllers/manufacturer');

router.get('/manufacturers', manufacturerController.all);
router.get('/manufacturers/:id', manufacturerController.byId);
router.post('/manufacturers', manufacturerController.create);
router.put('/manufacturers/:id', manufacturerController.update);
router.delete('/manufacturers/:id', manufacturerController.remove);

router.get('/products', productController.all);
router.get('/products/:id', productController.byId);
router.post('/products', productController.create);
router.put('/products/:id', productController.update);
router.delete('/products/:id', productController.remove);

module.exports = router;

能够看到,咱们将 index Controller 里面导入了咱们的 productControllermanufacturerController 。而后定义了一系列路由。

这里操做 manufacturer 的前五个路由的功能以下:

  • GET /manufacturers 获取因此的制造商(manufacturers)
  • GET /manufacturers/:id 获取单个制造商,这里 :id 表明动态路由,用于匹配任意字符串:/manufacturers/<any-string>
  • POST /manufacturers 用户建立单个制造商
  • PUT /manufacturers/:id 用于修改单个制造商
  • DELETE /manufacturers/:id 用于删除单个制造商

对应的 product 的五个路由功能以下:

  • GET /products 获取因此的产商品(products)
  • GET /products/:id 获取单个商品,这里 :id 表明动态路由,用于匹配任意字符串:/products/<any-string>
  • POST /products 用户建立单个商品
  • PUT /products/:id 用于修改单个商品
  • DELETE /products/:id 用于删除单个商品

最后咱们导出咱们的路由。

接下来咱们来看一看具体的 manufacturer Controller。

const Model = require('../model');
const { Manufacturer } = Model;

const manufacturerController = {
  all(req, res) {
    Manufacturer.find({})
      .exec((err, manfacturers) => res.json(manfacturers))
  },
  byId(req, res) {
    const idParams = req.params.id;

    Manufacturer
      .findOne({ _id: idParams })
      .exec((err, manufacturer) => res.json(manufacturer));
  },
  create(req, res) {
    const requestBody = req.body;
    const newManufacturer = new Manufacturer(requestBody);

    newManufacturer.save((err, saved) => {
      Manufacturer
        .findOne({ _id: newManufacturer._id })
        .exec((err, manfacturer) => res.json(manfacturer))
    })
  },
  update(req, res) {
    const idParams = req.params.id;
    let manufacturer = req.body;

    Manufacturer.updateOne({ _id: idParams }, { ...manufacturer }, (err, updated) => {
      res.json(updated);
    })
  },
  remove(req, res) {
    const idParams = req.params.id;

    Manufacturer.findOne({ _id: idParams }).remove( (err, removed) => res.json(idParams) )
  }
}

module.exports = manufacturerController;

能够看到咱们定义了一个 manufacturerController 对象,用来组织一系列对 manufacturer 进行增删改查的操做。

咱们在开头导入了咱们以前定义的 ManufacturerModel,这是 Mongoose 为咱们提供的操做数据库的接口,咱们经过定义在 Model 上的一系列如 find、findOne、updateOne、deleteOne 执行咱们对数据的增删改为操做。

最后是咱们的 product Controller ,它内部的操做和咱们上面讲到的 manufacturer Controller 基本一致。

const Model = require('../model');
const { Product } = Model;

const productController = {
  all(req, res) {
    Product.find({})
      .populate('manufacturer')
      .exec((err, products) => res.json(products))
  },
  byId(req, res) {
    const idParams = req.params.id;

    Product
      .findOne({ _id: idParams })
      .populate('manufacturer')
      .exec((err, product) => res.json(product));
  },
  create(req, res) {
    const requestBody = req.body;
    const newProduct = new Product(requestBody);

    newProduct.save((err, saved) => {
      Product
        .findOne({ _id: newProduct._id })
        .populate('manufacturer')
        .exec((err, product) => res.json(product))
    })
  },
  update(req, res) {
    const idParams = req.params.id;
    const product = req.body;

    console.log('idParams', idParams);
    console.log('product', product);

    Product.updateOne({ _id: idParams }, { ...product }, (err, updated) => {
      res.json(updated);
    })
  },
  remove(req, res) {
    const idParams = req.params.id;

    Product.findOne({ _id: idParams }).remove( (err, removed) => res.json(idParams) )
  }
}

module.exports = productController;

编写完上面的代码并保存,打开终端输入 npm start 来开启咱们的服务器。

由于咱们的服务器在开启时要链接 MongoDB 数据库,因此要确保本地的 MongoDB 数据库已经开启,咱们能够经过以下命令来开启:

$ mongod

好了,如今咱们的 API 服务器就搭建完成了,如今咱们经过 API 测试工具 POSTman 来测试一下咱们 API 是否成功。

测试 GET /api/v1/manufacturers

测试 POST /api/v1/manufacturers:咱们添加手机制造商 "一加"

测试 PUT /api/v1/manufacturers/:id:这里咱们把 "一加" 改为 "One Plus"

测试 DELETE /api/v1/manufacturers/:id:咱们把刚刚添加的 "一加" 删掉

最后测试添加商品 productPOST /api/v1/products:这里咱们在定义 product 的数据属性时,加入了 Manufacturer 做为外键,因此建立的时候对应的 manufacturer 属性要为某个 ManufacturerObjectId,好比咱们这里添加小米的新产品 Mix Alpha

能够看到咱们添加了 manufacturer5da72eaac6ccea32f823c247 的小米制造商的新手机 "Mix Alpha"

小结

自此,咱们的 API 服务器就搭建完成了,在这篇教程里面咱们学到了以下知识:

  • 了解 Express 的路由以及如何用 mongoose 链接 MongoDB 数据库
  • 编写路由、Model 和 Controllers
  • 使用 POSTman 来测试咱们编写的 API

相信经过本篇教程的学习,你对使用 Node 和 Express 编写 API 后端服务器有了一个基本的了解,如今咱们了解了 Vue 基础知识,了解了如何搭建后端服务器,接下来咱们将考虑如何使用 Vue 构建大型应用,下一篇教程咱们再见!

想要学习更多精彩的实战技术教程?来 图雀社区逛逛吧。

相关文章
相关标签/搜索