Node操作MongoDB並與express結合實現圖書管理系統
Node操作MongoDB資料庫
Web應用離不開資料庫的操作,我們將陸續瞭解Node操作MongoDB與MySQL這是兩個具有代表性的資料庫,非關係型資料庫(NoSQL)及關係型資料庫(SQL)。這一節,我們主要了解node中使用MongoDB,並與express結合實現一個簡單圖書管理小應用
我們來簡單看看關係型資料庫與非關係型資料庫
非關係型資料庫-NoSQL
在NoSQL之前,資料庫中SQL一支獨秀。隨著web2.0的快速發展,非關係型、分散式資料儲存得到了快速的發展,訪問量巨大,傳統關係型資料庫遇到各種瓶頸(如:高併發讀寫需求,高擴充套件性和可用性,複雜SQL,特別是多表關聯查詢等等)。NoSQL就誕生於此背景下,NoSQL資料庫的出現,彌補了關係資料(比如MySQL)在某些方面的不足,在某些方面能極大的節省開發成本和維護成本
NoSQL的優勢
- 易擴充套件
- 高效能
- 靈活的資料模型
- 高可用
NoSQL的分類
NoSQL可以大體上分為4個種類:Key-value、Document-Oriented、Column-Family Databases以及 Graph-Oriented Databases
型別 | 代表 | 特點 |
---|---|---|
鍵值(Key-Value) | MemcacheDB | 鍵值資料庫就像在傳統語言中使用的雜湊表。你可以通過key來新增、查詢或者刪除資料,鑑於使用主鍵訪問,所以會獲得不錯的效能及擴充套件性。 |
面向文件(Document-Oriented) | MongoDB | 文件儲存一般用類似json的格式儲存,儲存的內容是文件型的。這樣也就有有機會對某些欄位建立索引,實現關係資料庫的某些功能。 |
列儲存(Wide Column Store/Column-Family) | Cassandra | 顧名思義,是按列儲存資料的。最大的特點是方便儲存結構化和半結構化資料,方便做資料壓縮,對針對某一列或者某幾列的查詢有非常大的IO優勢。 |
圖(Graph-Oriented) | Neo4J | 圖形關係的最佳儲存。使用傳統關係資料庫來解決的話效能低下,而且設計使用不方便。 |
關係型資料庫-SQL
SQL指結構化查詢語言,全稱是 Structured Query Language,關係資料庫,是建立在關係模型基礎上的資料庫,藉助於集合代數等數學概念和方法來處理資料庫中的資料,關簡單來說,關係模型指的就是二維表格模型,而一個關係型資料庫就是由二維表及其之間的聯絡所組成的一個數據組織
SQL的優點
- 容易理解:二維表結構是非常貼近邏輯世界的一個概念,關係模型相對網狀、層次等其他模型來說更容易理解
- 使用方便:通用的SQL語言使得操作關係型資料庫非常方便
- 易於維護:豐富的完整性(實體完整性、參照完整性和使用者定義的完整性)大大減低了資料冗餘和資料不一致的概率
前文提到了SQL遇到了瓶頸,並不是說SQL不行(個人認為MySQL是業內相當優秀的資料庫),只是應用場景的不同
RDBMS 資料庫程式
RDBMS 指關係型資料庫管理系統(Relational Database Management System)。RDBMS 是 SQL 的基礎,同樣也是所有現代資料庫系統的基礎,比如 MS SQL Server、IBM DB2、Oracle、MySQL 以及 Microsoft Access。RDBMS 中的資料儲存在被稱為表的資料庫物件中。表是相關的資料項的集合,它由列和行組成
接下來我們就一起來使用Node操作MongoDB(Node操作MySQL將在下一篇介紹),並使用它來寫一個建議的圖書管理小案例
Node操作MongoDB
MongoDB 是一個基於分散式檔案儲存的資料庫。由 C++ 語言編寫。旨在為 WEB 應用提供可擴充套件的高效能資料儲存解決方案。MongoDB 是一個介於關係資料庫和非關係資料庫之間的產品,是非關係資料庫當中功能最豐富,最像關係資料庫的。下面這個表就展示出來MongoDB與SQL資料庫的一個簡單比較
SQL術語/概念 | MongoDB術語/概念 | 解釋/說明 |
---|---|---|
database | database | 資料庫 |
table | collection | 資料庫表/集合 |
row | document | 資料記錄行/文件 |
column | field | 資料欄位/域 |
index | index | 索引 |
table joins | 表連線,MongoDB不支援 | |
primary key | primary key | 主鍵,MongoDB自動將_id欄位設定為主鍵 |
MongoDB和Node.js特別配,因為MongoDB是基於文件的,文件是按BSON(JSON的輕量化二進位制格式)儲存的,增刪改查等管理資料庫的命令和JavaScript語法很像,這裡我們選擇mongoose來進行增刪改查,mongoose構建在MongoDB之上,提供了Schema、Model和Document物件,用起來很方便
1. 安裝Mongoose
$ npm install mongoose
安裝好後 require('mongoose')
就可以使用了
2.使用Mongoose進行CRUD
連線資料庫
const mongoose = require("mongoose") // 使用原生promise,mongoose自帶promise不再支援了 mongoose.Promise = global.Promise const db=mongoose.connect('mongodb://localhost/test') db.connection.on("error", function (error) { console.log("資料庫連線失敗:" + error) }) db.connection.on("open", function () { console.log("資料庫連線成功") })
我們來看看Mongoose的幾個名詞
Schema
: 一種以檔案形式儲存的資料庫模型骨架,不具備資料庫的操作能力Model
: 由Schema
釋出生成的模型,具有抽象屬性和行為的資料庫操作對Entity
: 由Model
建立的實體,他的操作也會影響資料庫
Schema
生成Model
,Model
創造Entity
,Model
和Entity
都可對資料庫操作造成影響,但Model
比Entity
更具操作性
關於mongoose最重要的就是理解Schema
Model
Entity
,它的各種方法直接去查文件使用就好。
Schema
schema是mongoose裡會用到的一種資料模式,可以理解為表結構的定義;每個schema會對映到mongodb中的一個collection,它不具備操作資料庫的能力
const kittySchema = mongoose.Schema({ name: String })
Schema.Type
Schema.Type
是由Mongoose
內定的一些資料型別,基本資料型別都在其中,他也內建了一些Mongoose
特有的Schema.Type
。當然,你也可以自定義Schema.Type
,只有滿足Schema.Type
的型別才能定義在Schema
內
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- Objectid
- Array
Model
定義好了Schema,接下就是生成Model。
model是由schema生成的模型,可以對資料庫的操作
var Kitten = mongoose.model('Kitten', kittySchema)
Entity
用Model建立Entity,Entity
可以對資料庫操作
var silence = new Kitten({ name: 'Silence' }) console.log(silence.name); // 'Silence'
查詢
model.find({},field,callback) // 引數1忽略,或為空物件則返回所有集合文件 model.find({},{'name':1, 'age':0},callback) //過濾查詢,引數2: {'name':1, 'age':0} 查詢文件的返回結果包含name , 不包含age.(_id預設是1) model.find({},null,{limit:20}) // 遊標操作 limit 限制返回結果數量為20個,如不足20個則返回所有 model.findOne({}, callback) // 查詢找到的第一個文件 model.findById('obj._id', callback) // 查詢找到的第一個文件, 只接受 _id 的值查詢
建立
// 在集合中建立一個文件 Model.create(doc(s), [callback]) Entity.save(callback)
刪除
Model.remove([criteria], [callback]) // 根據條件查詢到並刪除 Model.findByIdAndRemove(id, [options], [callback]) // 根據id查詢到並刪除
修改
Model.update(conditions, update, [options], [callback]) // 根據引數找到並更新 Model.findByIdAndUpdate(id, [update], [options], [callback]) // 根據id查詢到並更新
上面簡單寫了幾個常用操作,關於Mongoose的更多使用請移步官網 ,我就不搬了 (推薦閱讀: Mongoose學習參考文件——基礎篇)
圖書管理系統
瞭解了MongoDB以及Mongoose的簡單使用,我們一起來實現一個圖書管理的小案例,其有最基本的增刪改查,同時我們將瞭解到express的基本使用,同時會認識下模板引擎,但這些只是簡略瞭解,這節的重點是Mongoose操作MongoDB
UI採用了漂亮的UIkit3
傳送門: Github
可以去github拉下來,然後npm install
然後node index.js
即可跑起來
1. 準備工作
我們先隨便新建一個資料夾,然後在這個目錄下
$ npm init
初始化專案完成後使用下載express,mongoose,nunjucks(模板引擎), body-parser(bodyParser中介軟體用來解析http請求體)
$ npm install express mongoose nunjucks body-parser --save
接下來我們新建index.js檔案,在裡面將express跑起來
const express = require('express') const nunjucks = require('nunjucks') const path = require('path') const bodyParser = require('body-parser') const app = express() // 靜態檔案目錄 app.use(express.static(path.join(__dirname, 'public'))) // 配置模板引擎 nunjucks.configure(path.join(__dirname, 'views'), { autoescape: true, express: app }) app.set('view engine', 'html') // 配置bodyParser app.use(bodyParser.json()) app.use(bodyParser.urlencoded({extended: false})) // 路由 app.get('/', (req, res)=>{ res.send('HELLO mongo') }) const server = app.listen(3000, function () { console.log('app listening at http:localhost:3000') })
現在我們執行node index.js
便可以跑起來了,當然更推薦使用以前介紹到的supervisor
這裡我們再聊一聊Node web應用的模板引擎,這兒我們用了nunjucks 這是mozilla維護的一個模板引擎,他是 jinja2的javascript版本,用過python的jinja2一定會感覺很親切,除此之外,很有很多有些的模板引擎如ejs,jade。但個人認為jade是反人類的,因此更推薦Nunjucks及ejs。當然了,這取決於大家的喜好,更多模板引擎請自行搜尋瞭解。
我們新建一個views資料夾,放置模板。這兒只需要一個主頁顯示所有圖書index.html,add.html新增圖書,edit.html編輯圖書,base.html作為基礎模板,其他模板檔案可以繼承它
關於nunjucks,我們以他官網的那一段小程式碼來簡單看一下
{% extends "base.html" %} {# 繼承base.html 這是註釋 #} {# 區塊 #} {% block header %} <h1>{{ title }}</h1> {% endblock %} {% block content %} <ul> {# 迴圈 #} {% for name, item in items %} <li>{{ name }}: {{ item }}</li> {% endfor %} </ul> {% endblock %}
更多使用請自行檢視官網,
2. 功能設計以及路由配置
這兒我們就來看看,這個小的圖書管理系統需要的功能。增刪改查 就是新增圖書,刪除圖書,修改圖書,顯示所有圖書
我們就可以根據這幾個功能來配置我們的路由了
const express = require('express') const router = express.Router() // GET 首頁顯示全部書籍 router.get('/', (req, res) => { }) // GET 新增書籍 router.get('/add', (req, res) => { }) // POST 新增書籍 router.post('/add', (req, res) => { }) // GET 刪除 router.get('/:bookId/remove', (req, res) => { }) // GET 編輯 router.get('/:bookId/edit', (req, res) => { }) // POST 編輯 router.post('/:bookId/edit', (req, res) => { })
這兒為了專案結構更清晰,我們不把路由寫在index.js中,而是提取到routes目錄下,我們新建routes目錄,在下面新建book.js ,然後將相關路由全部放到其中並匯出,
const express = require('express') const router = express.Router() ··· ··· ··· module.exports = router // 匯出
然後新建一個index.js檔案
module.exports = function (app) { app.use('/', require('./book')) }
這兒這樣劃分,在這可能看不出太多優勢,但是在大一點的應用中,我們這樣配置可以讓功能劃分很清晰。
最後我們在入口檔案index.js中將路由require
進去,就可以使用了,
···
routes(app) ···
到此,前置工作就差不多了,下面我們就可以進入今天的重頭戲Mongoose
3. 功能實現, Mongoose操作MongoDB
新建lib資料夾,新建mongo.js檔案,連線資料庫,在其中定義 Schema 併發布為model
const mongoose = require('mongoose') const Schema = mongoose.Schema mongoose.Promise = global.Promise // MongoDB會自動建立books資料庫 const db = mongoose.connect('mongodb://localhost:27017/books') db.connection.on("error", function (error) { console.log("資料庫連線失敗:" + error) }) db.connection.on("open", function () { console.log("資料庫連線成功") }) const BookSchema = Schema({ title: { unique: true, // 唯一的不可重複 type: 'String', // Schema.Type String型別 }, summary: 'String', price: 'Number', meta: { createAt: { type: Date, default: Date.now() } } }) exports.Book = mongoose.model('Book', BookSchema)
新建Models資料夾,在其中新建books.js放置對MongoDB 的一些操作,這裡面使用了promise,如果還不會那你就得去補補了
const Book = require('../lib/mongo').Book module.exports = { getBooks(){ return Book .find({}) .sort({_id: -1}) .exec() }, getBook(id){ return Book .findById(id) .exec() }, editBook(id, data){ return Book .findByIdAndUpdate(id, data) .exec() }, addBook(book){ return Book.create(book) }, delBook(id){ return Book .findByIdAndRemove(id) .exec() } }
這裡面的一些方法,我們在前面講Mongoose的時候都瞭解過了,想了解的更多還是推薦去官網看看
最後我們就是根據不同的路由進行不同的處理了
const express = require('express') const router = express.Router() const BookModel = require('../models/books') router.get('/', (req, res) => { BookModel.getBooks() .then((books) => { res.render('index', {books}) }) }) router.get('/add', (req, res) => { res.render('add') }) router.post('/add', (req, res) => { let book = req.body BookModel.addBook(book) .then((result) => { res.redirect('/') }) }) router.get('/:bookId/remove', (req, res) => { BookModel.delBook(req.params.bookId) .then((book) => { res.redirect('/') }) }) router.get('/:bookId/edit', (req, res) => { let book = req.body BookModel.getBook(req.params.bookId) .then((book) => { res.render('edit', { book, bookid: req.params.bookId }) }) }) router.post('/:bookId/edit', (req, res) => { let book = req.body BookModel.editBook(req.params.bookId, book) .then((result)=>{ res.redirect('/') }) }) module.exports = router
OK!!!到此,我們這小專案基本就算完成了。程式碼詳見GitHub 作為一個學習案例,這算完成了,但其中可以優化完善的地方還很多,大家可以自行探索···
拋磚引玉