咱們在平時所用到的一些網站、App,它們會將咱們的數據進行保存,當咱們關閉這些網站或者 App 後,下次打開還能看到咱們以前的一些文字、視頻記錄。在迷你全棧電商應用實戰系列的第二篇教程中,咱們將經過基於 Node.js 平臺的 Express 框架實現後端 API 數據接口,而且將數據存儲在 MongoDB 中。這樣咱們的網站就可以記錄用戶添加的商品,而且不管之後何時打開,都能獲取咱們以前的記錄。html
提示閱讀這篇文章須要你對 Express 框架有必定的瞭解。若是不熟悉的話,能夠閱讀這篇教程快速上手哦。前端
歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:vue
若是您以爲咱們寫得還不錯,記得 點贊 + 關注 + 評論 三連,鼓勵咱們寫出更好的教程💪
你能夠在 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.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 構建大型應用,下一篇教程咱們再見!
想要學習更多精彩的實戰技術教程?來 圖雀社區逛逛吧。