咱們在平時所用到的一些網站、App,它們會將咱們的數據進行保存,當咱們關閉這些網站或者 App 後,下次打開還能看到咱們以前的一些文字、視頻記錄。在迷你全棧電商應用實戰系列的第二篇教程中,咱們將經過基於 Node.js 平臺的 Express 框架實現後端 API 數據接口,而且將數據存儲在 MongoDB 中。這樣咱們的網站就可以記錄用戶添加的商品,而且不管之後何時打開,都能獲取咱們以前的記錄。html
提示前端
閱讀這篇文章須要你對 Express 框架有必定的瞭解。若是不熟悉的話,能夠閱讀這篇教程快速上手哦。vue
迷你全棧電商應用實戰系列的所有教程以下:git
若是您以爲咱們的教程寫得還不錯,請記得給咱們點個贊哦!鼓勵咱們更快更好地寫完剩下的教程!你也能夠在評論區留言,告訴咱們想要實現什麼功能,咱們必定會仔細考慮的哦!github
你能夠在 Github 查看這一部分教程最終的源碼:源碼地址。vue-router
首先,讓咱們使用 express-generator 腳手架來初始化咱們的 Express 項目。在終端運行以下命令來安裝:mongodb
npm install -g express-generator
複製代碼
打開終端,輸入以下命令測試是否安裝成功:數據庫
express --version # 4.15.5
複製代碼
而後輸入以下命令初始化咱們的 Express 項目:express
express vue-online-shop-backend
複製代碼
當項目初始化成功以後,接下來經過以下命令開啓項目:npm
cd vue-online-shop-backend
npm install
npm start
複製代碼
接着打開瀏覽器,輸入 http://localhost:3000/
查看咱們初始好的項目效果。
經過 express-generator 初始化的項目代碼中,咱們在整個教程中只須要了解下面四個文件:
app.js
:Express 應用主文件bin/www
:用來開啓服務器的腳本routes/index.js
:路由主文件views/index.ejs
:主頁的模板文件,這裏因爲咱們只打算實現 API 數據接口,因此不用關心與以前的 Express 教程不一樣的是,腳手架代碼並無把全部的路由都放在 app.js
中,而是根據不一樣的子應用(users、index)進行了拆分,這也與該系列第一篇教程中 vue-router 的嵌套路由不謀而合。
咱們大體地看一下 app.js
和 routes/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
腳本進行調用並啓動服務器。
提示
這裏咱們能夠看到,咱們導入的兩個路由
index
和users
,也和其餘中間件同樣被處理,因此在 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
腳手架爲咱們生成的上面四個文件,咱們學到了以下知識:
解決數據持久化存儲最流行的方案無疑是數據庫,而 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();
}
});
複製代碼
一般意義上,上面的代碼存在不少問題,咱們通常意義上會使用 NPM 包 cors
來解決,固然咱們這裏使用了比較簡單粗暴的方式。
咱們要在服務器中經過 mongoose 與 MongoDB 數據庫進行交互,須要定義 Schema
和 Model
。經過定義它們來告訴 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 對象來描述咱們須要的數據結構和對應的數據類型,除了咱們熟知的像 String
、Number
等數據類型外,ObjectId
是一個特殊的數據類型,咱們用它來定義咱們的單個 MongoDB 文檔的主鍵,用於標誌存儲數據的惟一性。
咱們還能夠看到,在咱們的 productSchema
中,manufacturer
數據結構咱們定義了一個 ref
屬性,這是 MongoDB 爲咱們提供的相似關係數據庫的外鍵功能,容許咱們建立一對多的數據文檔,因此 productSchema
的 manufacturer
屬性對應着的數據類型爲一條 Manufacturer
記錄。
接着咱們經過 model
來建立對於的數據模型,而後導出咱們建立好的數據模型。這裏的 model
就是經典的 MVC 設計模式中的 Model。
路由是 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 裏面導入了咱們的 productController
和 manufacturerController
。而後定義了一系列路由。
這裏操做 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
:咱們把剛剛添加的 "一加"
刪掉
最後測試添加商品 product
,POST /api/v1/products
:這裏咱們在定義 product
的數據屬性時,加入了 Manufacturer
做爲外鍵,因此建立的時候對應的 manufacturer
屬性要爲某個 Manufacturer
的 ObjectId
,好比咱們這裏添加小米的新產品 Mix Alpha
:
能夠看到咱們添加了 manufacturer
爲 5da72eaac6ccea32f823c247
的小米制造商的新手機 "Mix Alpha"
。
自此,咱們的 API 服務器就搭建完成了,在這篇教程裏面咱們學到了以下知識:
相信經過本篇教程的學習,你對使用 Node 和 Express 編寫 API 後端服務器有了一個基本的瞭解,如今咱們瞭解了 Vue 基礎知識,瞭解瞭如何搭建後端服務器,接下來咱們將考慮如何使用 Vue 構建大型應用,下一篇教程咱們再見!
想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。