從零到部署:用 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 構建大型應用,下一篇教程咱們再見!

想要學習更多精彩的實戰技術教程?來 圖雀社區逛逛吧。

相關文章
相關標籤/搜索