咱们在平时所用到的一些网站、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 构建大型应用,下一篇教程咱们再见!
想要学习更多精彩的实战技术教程?来 图雀社区逛逛吧。