https://github.com/azat-co/practicalnode/blob/master/chapter5/chapter5.mdjavascript
學習mongodb的官方網站:css
https://university.mongodb.com/ (免費課程,-> study guide,-> exam)html
https://docs.mongodb.com/manual/tutorial/getting-started/前端
Mongodb node.js driver3.x版本的 guide:java
http://mongodb.github.io/node-mongodb-native/3.1/upgrade-migration/main/node
⚠️:本書上的案例使用的是2.2版本的代碼,如open命令,在3.0驅動版已經刪除。jquery
Mongoose :https://mongoosejs.com/ (17000✨)ios
寫MongoDb Validation, casting和business logic boilerplate是很煩人的drag/boring。git
所以使用mongoose代替。github
我真的喜歡使用MongoDB with Node。由於這個數據庫有JavaScript interface, 並使用JSON-like data structure。
MongoDB是一種NoSQL database。
NoSQL databases (DBs), also called non-relational databases非關係型數據庫。
more horizontally scalable,在橫向上有更多的擴展性
better suited for distributed systems than traditional SQL,比傳統的SQL更適合分佈式的系統。
NoSQL DBs內置方法讓數據複製和自定義查詢語法。這被叫作反規範化denormalization.
NoSQL databases deal routinely with larger data sizes than traditional ones.
一般非關係數據庫處理比傳統關係數據庫更大的數據。
NoSQL DBs是沒有schema的。
沒有table,僅僅是一個有IDS的簡單store。
大量數據類型是不儲存在數據庫(沒有更多的ALTER table 查詢); 它們被移動到app,或者object-relational mapping (ORM) levels--咱們的案例,移動到Node.js代碼。
做者說:
這些NoSQL最棒的優勢!我能夠快速地prototype prototyping和iterate (more git pushes!) 一旦我或多或少的完成,我能執行 implement schema和validation in Node. This workflow allows me to not waste time early in the project lifecycle
while still having the security at a more mature stage. 這種工做流程,讓我不會在工程的早期浪費時間,同時在更成熟的階段仍有安全。?
文檔儲存的非關係型數據庫。
與之對比的是key-value類型的數據庫如Redis, 以及wide-column store NoSQL databases。
NoSQL DBs彙總:http://nosql-database.org/
MongoDB是最成熟mature,dependable的NoSQL數據庫。
另外,MongoDB有一個JavaScript交互interface!這很是棒!
由於如今無需在前端(browser JavaScript)和後端(Node.js)之間來回切換switch context,和數據庫。
這是我最喜歡的功能!
開發MongoDB的公司是一個工業的領導者,提供了學習MongoDB的在線網站 (https://university.mongodb.com).
https://docs.mongodb.com/manual/tutorial/getting-started/
準備開始MongoBD和Node.js , 本章將講以下章節:
macOS使用HomeBrew安裝。✅
$ brew install mongodb
或者從官網下載文件並配置它。http://www.mongodb.org/downloads
其餘安裝方式見本文後續說明和鏈接。
可選:
若是想在你的系統的任何位置使用MongoDB命令, 須要把mongoDB path增長到$PATH變量。
對應macOS,你須要open-system path file, 它在/etc/paths:
$ sudo vi /etc/paths
而後,在/etc/paths文件內增長下面的代碼:
/usr/local/mongodb/bin
建立一個data文件夾;默認 MongoDB使用根目錄的/data/db。
//建立文件夾 $ sudo mkdir -p /data/db
//改變group和owner $ sudo chown `id -u` /data/db
這個數據文件夾是你的本地數據庫實例的存放位置~,它存放全部databases, documents,和on-all data.
若是你想儲存數據在其餘地方,能夠指定path, 當你登錄你的database實例時,使用--dbpath選項給mongod命令.
各類安裝方法官網:
使用mongod命令來啓動Mongo server
若是你手動安裝,並無鏈接位置到PATH, 去到你解包MongoDB的這個folder。那裏有個bin文件夾。
輸入:
$ ./bin/mongod
若是你像大多數開發者同樣,喜歡在你的電腦任意位置輸入mongod, 我猜想你把MongoDB bin文件夾放入了你的PATH環境變量中。因此若是你爲MongoDB location增長$PATH, 就直接輸入:
//任意位置 $ mongod
⚠️注意,在增長一個新的path給$PATH變量須要重啓terminal window。
當teminal上出現
waiting for connections on port 27017
意味着MongoDB數據庫server正在運行!Congratulations!
默認它監聽 http://localhost:27017。
This is the host and port for the scripts and applications to access MongoDB.
In our Node.js code, we use 27017 for for the database and port 3000 for the server.
和Node.js REPL相似,MongoDB也有一個console/shell,做爲database server實例的客戶端。
這意味着,保持一個terminal window跑server,再開啓一個tab/window用於console。
開啓console的命令:
$ ./bin/mongo //或mongo
當你成功地鏈接到數據庫實例,以後你應該看到這些:
MongoDB shell version: 4.0.5
connecting to: test
看到光標>, 如今你在一個不一樣的環境內而不是zsh或bash。
你不能再執行shell命令了,因此不要使用node server.js或者mkdir。
可是你可使用JavaScript,Node.js和一些特定的MongoDB代碼。
例如,執行下面的命令,來保存一個document{a: 1}, 而後查詢這個集合,來看看這個新建立的document:
> db.test.save({a: 1})
WriteResult({ "nInserted" : 1 })
> db.test.find()
{ "_id" : ObjectId("5c3c68cb8126284dea64ff02"), "a" : 1 }
命令find(), save()就是字面的意思。
你須要在它們前面加上db.COLLECTION_NAME, 用你本身的名字替代COLLECTION_NAME
⚠️在masOS,使用control + C關閉process,
下章討論最重要的MongoDB console commands:
MongoDB控制檯語法是JavaScript。這很是棒!
最後一件咱們想要的是學習一個新的複製的語言,如SQL。
db.test.find() //db是類名 //test是collection name //find是方法名
經常使用的MongoDB console(shell) commands:
admin 0.000GB config 0.000GB local 0.000GB test 0.000GB
> db.messages.save({a: 1})
> var a = db.messages.findOne()
//a的值是{ "_id" : ObjectId("5c3c8112cdb3fb6e147fb614"), "a" : 1 } > printjson(a) > a.text = "hi" > printjson(a) > db.messages.save(a)
若是有_id, 這個document會被更新,不管什麼新屬性被傳入到save()。
> db.messages.save(a) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > db.messages.find() { "_id" : ObjectId("5c3c8112cdb3fb6e147fb614"), "a" : 1, "text" : "hi" }
若是沒有_id,將插入一個新document並建立一個新的document ID🆔(ObjectId)in _id。
> db.messages.save({b: 2}) WriteResult({ "nInserted" : 1 }) > db.messages.find() { "_id" : ObjectId("5c3c8112cdb3fb6e147fb614"), "a" : 1, "text" : "hi" } { "_id" : ObjectId("5c3c81e6cdb3fb6e147fb615"), "b" : 2 }
可見save(),相似upsert(update or insert)
這裏只列出了最少的API。詳細見:
"Overview—The MongoDB Interactive Shell" (http://www.mongodb.org/display/DOCS/Overview+-+The+MongoDB+Interactive+Shell).
一個基於web數據庫的管理員界面,能夠編輯,搜索,移除MongoUI documents,無需手輸入命令了。
它會在默認的瀏覽器打開app,並鏈接到你的本地DB實例。
更好的桌面工具是Compass,下載地址:https://www.mongodb.com/products/compass
把一個JSON文件引進到一個數據庫。就是把種子數據,存入到一個數據庫。也可使用(CSV,TSV文件)
mongoimport --db dbName --collection collectionName --file fileName.json --jsonArray
進入控制檯後,顯示默認的這些數據庫:
> show dbs admin 0.000GB config 0.000GB local 0.000GB test 0.000GB
當前數據庫是test, 顯示這個數據庫的collections:
> show collections
messages
test
顯示messages中的documents:
> db.messages.find() { "_id" : ObjectId("5c3c8112cdb3fb6e147fb614"), "a" : 1, "text" : "hi" } { "_id" : ObjectId("5c3c81e6cdb3fb6e147fb615"), "b" : 2 }
爲了讓Node和MongoDB一塊兒工做,須要驅動driver。
爲了演示Mongoskin的優點,我會展現如何使用 Node.js native driver for MongoDB,它比用Mongoskin多一些work。下面我建立一個基本的script,存儲database.
Collection.insertMany(docs, options, callback) (點擊查看Node.js MongoDB Driver API)
//insertMany方法會返回promise對象,用於then的鏈接。 db.collection('name').insertMany([ { item: "journal", qty: 25, status: "A", size: { h: 14, w: 21, uom: "cm" }, tags: [ "blank", "red" ] }, { item: "postcard", qty: 45, status: "A", size: { h: 10, w: 15.25, uom: "cm" }, tags: [ "blue" ] } ]) .then(function(result) { //處理結果 })
首先,
npm init -y
而後,保存一個指定的版本爲dependency。
$ npm install mongodb@2.2.33 -SE
(⚠️:下面的代碼是使用node-native-driver的2.2版本,已經被拋棄,下面主要是看一下設計結構,和一些方法的使用,新的3.0版本見下面👇。)
所以,咱們先創建一個小的案例,它測試是否咱們可以從一個Node.js腳本鏈接到一個本地的MongoDB實例, 並運行一系列的聲明相似上個章節:
文件名是:code/ch5/mongo-native-insert.js
首先引入mongodb。而後咱們使用host,port鏈接到數據庫。這是創建鏈接到數據庫的方法之一,db變量把一個reference在一個具體的host和port, 給這個數據庫。
const mongo = require('mongodb') const dbHost = '127.0.0.1' cibst dbPort = 27017 const {Db, Server} = mongo const db = new Db('local', new Server(dbHost, dbPort), {safe: true})
使用db.open來創建鏈接:(version3.0被拋棄,改用MongoClient類的實例建立鏈接)
db.open((error, dbConnection) => { //寫database相關的代碼 //console.log(util.inspect(db)) console.log(db._state) db.close() })
執行完後,db.close() (version3.0,也須要關閉數據庫, MongoClient類提供實例方法close)
爲在MongoDB建立一個document,須要使用insert()方法。insert()方法在異步環境下使用,能夠傳遞callback做爲參數,不然insert()會返回一個promise對象用於異步的then()。
回調函數有error做爲第一個參數。這叫作error-first pattern。
第二個參數是回調函數,它新建立一個document.
在控制檯,咱們無需有多個客戶執行查詢,因此在控制檯方法是同步的,不是異步的。
可是,使用Node, 由於當咱們等待數據庫響應的時侯,咱們想要去處理多個客戶,因此用回調函數。
下面的完整的代碼,是第五步:建立一個document。
⚠️:整個的insert代碼是在open()的回調函數內的。由於insert()是異步的。
const mongo = require('mongodb') const dbHost = '127.0.0.1' const dbPort = 27017 const {Db, Server} = mongo const db = new Db('local', new Server(dbHost, dbPort), {safe: true}) //open函數是異步的 db.open((error, dbConnection) => { if (error) { console.error(error) return process.exit(1) } console.log('db state:', db._state) const item = { name: 'Azat' } dbConnection.collection('messages').insert(item, (error, document) => { if (error) { console.error(error) return process.exit(1) } console.info('created/inserted:', document) db.close() process.exit(0) }) })
process.exit([code])
這個方法告訴Node.js關閉進程,同步地提供一個狀態碼:
調用這個方法會強制的關閉進程,即便還有異步的操做⌛️。
通常無需明確調用process.exit方法。 Node.js會在沒有additional work pending in the event loop後退出.
除了mongo-native-insert.js腳本,咱們還能夠創建更多的方法,如 findOne()。
例如mogo-native.js腳本查詢任意對象並修改它:
const mongo = require('mongodb') const dbHost = '127.0.0.1' const dbPort = 27017 const {Db, Server} = mongo const db = new Db('local', new Server(dbHost, dbPort), {safe: true})
而後,打開一個鏈接,並加上錯誤的測試:
db.open((error, dbConnection) => { if (error) { console.error(error) process.exit(1) } console.log('db state: ', db._state)
從message collection那裏獲得一個item
使用findOne(query, options, callback)方法
第一個參數是query條件, 它的類型是object,
第二個參數是Collection~resultCallback函數,它內部處理返回的document。
(若是沒有callback傳入,返回Promise)
dbConnection.collection('messages').findOne({}, (error, item) => { if (error) { console.error(error) process.exit(1) }
Mongo Shell Method: db.collection.
findOne
(query, projection)
返回一個document,要知足collection/view的指定的查詢標準,並返回符合標準的第一個document,沒有則返回null。
參數 projection,用於限制返回的fields。
{ field1: <boolean>, field2: <boolean> ... }
如上面的區別:
methods在console和在Node.js惟一區別是在node,開發者必須使用回調函數(異步)。
console.info('findONe:', item)
item.text = 'hi' var id = item._id.toString() // we can store ID in a string console.info('before saving: ', item) dbConnection .collection('messages') .save(item, (error, document) => { if (error) { console.error(error) return process.exit(1) } console.info('save: ', document)
解釋:
save(doc, options, callback)方法,相似update or insert。
dbConnection.collection('messages') .find({_id: new mongo.ObjectID(id)}) .toArray((error, documents) => { if (error) { console.error(error) return process.exit(1) } console.info('find: ', documents) db.close() process.exit(0) } ) }) }) })
find(query)方法 -> {Cursor}
query參數類型是object.
{Cursor} 一個Cursor實例(內部的類型,不能直接地實例化。)
建立一個cursor實例,用於迭代從MongoDB查詢的結果。
toArray(callback) -> {Promise}
返回documents的array格式。Promise if no callback passed
解釋:
.find({_id: new mongo.ObjectID(id)})
爲了再次覈查保存的對象,咱們使用了document ID和find()方法,這個方法返回一個cursor,而後咱們使用toArray()提取standard JavaScript array。
最後,執行, 要先運行mongod service:
$ node mongo-native
⚠️???執行node mongo-native-insert.js命令。提示❌:
TypeError: db.open is not a function
這是版本的問題,須要使用的Node.js驅動的mongoDB版本
$ npm install mongodb@2.2.33 -SE
若是使用穩定的驅動driver版本:
npm i mongodb@stable //3.0.1 //執行node xxx.js會報告❌ db.open((error, dbConnection) => { ^ TypeError: db.open is not a function at Object.<anonymous> (/Users/chent
緣由
見2.2api:http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#open
和3.0api: http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html#open
2.2的DB#open方法,在3.0被刪除了。
另外官網的代碼從2.2開始已經使用的是MongoClient創建鏈接
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿重要!
http://mongodb.github.io/node-mongodb-native/3.1/quick-start/quick-start/
下面是創建鏈接並插入一個document的演示代碼:
version3-insert.js
const MongoClient = require('mongodb').MongoClient; const dbHost = '127.0.0.1' const dbPort = 27017 const assert = require('assert'); // Database Name const dbName = 'myproject'; // Connection URL const url = `mongodb://${dbHost}:${dbPort}`; // Create a new MongoClient const client = new MongoClient(url); // Use connect method to connect to the Server client.connect(function(err) { assert.equal(null, err) //若是有err,就會報告❌ console.log("Connected successfully to server"); //MongoClient#db方法,建立一個Db實例 const db = client.db(dbName); // 使用insetMany()方法,若是myproject不存在,則同時建立myproject數據庫和documents collection // 並插入一個document。 insertMany()方法,至關於create or update方法的判斷的整合。 insertDocuments(db, function() { //回調函數用於執行close,關閉db和它的鏈接。 client.close() }) }); const insertDocuments = function(db, myCallback) { // 獲得或者建立一個document: (這裏命名爲documents) const collection = db.collection('documents'); // Insert some documents,
// insertMany()的回調函數,能夠進行err的判斷(assert,expect),發出提示信息,並調用傳入的回調函數myCallback collection.insertMany([ {a : 12}, {b : 2}, {b : 3} ], function(err, result) { console.log("Inserted 3 documents into the collection, \n", result); myCallback(); }); }
$ node version3-insert.js //獲得結果: Connected successfully to server Inserted 3 documents into the collection
除此以外還有find, findOne, 等方法。
文章末尾有新的更新的內容,本章推薦使用mongoose。若是不看的話,前面的白學了,由於不實用!
Mongoskin 提供了更好的API。 一個wrapper,包裹node-mongodb-native。(1600🌟)過期。
除本書外,流行的驅動:
一個MongoDB對象 模型化工具,用於在異步的環境下工做!
https://github.com/Automattic/mongoose
文檔https://mongoosejs.com/docs/index.html
數據驗證很是重要,大多數MongoDb庫都須要開發者建立他們本身的驗證validation, 可是mongoose是例外!
Mongoose有內置的數據驗證。因此mongoose被大型的apps所推薦使用✅!
在Express.js上,能夠下載驗證模塊如:node-validator,或者express-validator。
elegant mongodb object modeling for node.js。
const mongose = rquire('mongose') mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}); //聲明一個類 const Cat = mongoose.model('Cat', { name: String }) //實例化類 const kitty = new Cat({name: 'Zildjian'}); //save方法保存實例,並返回promise。 kitty.save().then(() => { console.log('meow') })
Mongoose提供了直接的,基於schema的解決方案,來構造你的app data。
它包括內置的類型構件, 驗證, 查詢,業務邏輯勾子和更多的功能,開箱即用out of the box!
//先安裝好MongoDb和Node.js $ npm install mongoose
// getting-started.js var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/test');
在腳本文件中引入mongoose,而後打開一個鏈接到test數據庫,這是在本地運行的MongoDB實例(本地開啓mongod服務器).
如今,咱們有了一個等待鏈接,它會鏈接到正在本地運行的test數據庫。
如今須要判斷鏈接是成功仍是失敗,並給出❌提示:
var db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function() { // we're connected! });
db.once()一旦鏈接開始,就調用回調函數。爲了簡化,讓咱們假設全部的代碼在這個回調函數內。
在Mongoose中,一切都從Schema中取用。
var kittySchema = new mongoose.Schema({ name: String })
如今有了一個schema和一個屬性name,類型是string。
下一步是把schema編譯進一個 Model.
var Kitten = mongoose.model("Kitten", kittySchema)
一個Model就是一個類,咱們用它來構建documents。
在這個案例,每一個document都是一隻kitten, 它的屬性和行爲會在schema內聲明!
如今建立一隻kitten:(建立一個實例)
var silence = new Kitten({name: 'Silence'}) console.log(silence.name) //'Silence'
添加行爲函數:如speak:
kittySchema.methods.speak = function() { var greeting = this.name ? "Meow name is " + this.name : "I don't have a name" } var Kitten = mongoose.model("Kitten", kittySchema)
添加到schema的methods屬性中的函數,被編譯到Model prototype, 並會被exposed 到每一個document實例。
var fluffy = new Kitten({ name: 'fluffy'}) fluffy.speak(); // "Meow name is fluffy"
上面的操做是在視圖層到control層,最後要存入MongoDB數據庫的!使用save方法。
save(callback)
fluffy.save(function(err, fluffy) { if (err) return console.error(err) fluffy.speak(); })
如何顯示全部的kittens?
使用find(callback)方法
Kitten.find(function(err, kittens) { if (err) return console.error(err); console.log(kittens); })
若是想要搜索指定的document,使用:
//db.collection('Kitten').find({}, callback) Kitten.find({name: /^fluff/}, callback)
這和原生的Node.js mongoDb driver的方法相似。collection就是mongoose中模型構建的「類」。
而且,均可以使用regexp表達式。
本案例使用第2章創建的hello-world app
步驟:
1.增長種子文件
2.寫Mocha tests
3.增長persistence
在程序根目錄建立db文件夾,而後在terminal輸入:
mongoimport --db blog --collection users --file ./db/users.json --jsonArray
mongoimport --db blog --collection articles --file ./db/articles.json --jsonArray
⚠️參數--jsonArray: 能夠引入的文件內是array格式的數據,默認這種格式不能被引進,除非加上這個選項。
或者把👆的代碼存入一個seed.js腳本,腳本放在程序根目錄文件夾後,執行:
sudo bash seed.sh
建立db/users.json
[{ "email": "hi@azat.co", "admin": true, "password": "1" }]
建立 db/articles.json
[ { "title": "Node is a movement", "slug": "node-movement", "published": true, "text": "In one random deployment, it is often assumed that the number of scattered sensors are more than that required by the critical sensor density. Otherwise, complete area coverage may not be guaranteed in this deployment, and some coverage holes may exist. Besides using more sensors to improve coverage, mobile sensor nodes can be used to improve network coverage..." }, { "title": "Express.js Experience", "slug": "express-experience", "text": "Work in progress", "published": false }, { "title": "Node.js FUNdamentals: A Concise Overview of The Main Concepts", "slug": "node-fundamentals", "published": true, "text": "Node.js is a highly efficient and scalable nonblocking I/O platform that was built on top of a Google Chrome V8 engine and its ECMAScript. This means that most front-end JavaScript (another implementation of ECMAScript) objects, functions, and methods are available in Node.js. Please refer to JavaScript FUNdamentals if you need a refresher on JS-specific basics." } ]
須要安裝mocha, chai。
建立一個測試文件:tests/index.js
const boot = require('../app').boot const shutdown = require('../app').shutdown const port = require('../app').port const axios = require('axios') const { expect } = require('chai') const seedArticles = require('../db/articles.json')
使用axios發收請求響應。使用chai.expect方法,並引入數據。
首先,補上views的html代碼,而後再開始測試。
const boot = require('../app').boot const shutdown = require('../app').shutdown const port = require('../app').port const axios = require('axios') const expect = require('chai').expect
const boot = require('../app').boot const shutdown = require('../app').shutdown const port = require('../app').port const axios = require('axios') const expect = require('chai').expect const seedArticles = require('../db/articles.json') describe('server', () => { before(() => { boot() }) describe('homepage', () => { it('should respond to GET', (done) => { axios .get(`http://localhost:${port}`) .then(function(response) { expect(response.status).to.equal(200) }) .catch((err) => { console.log(err) }) .then(done) //promise的寫法,告訴mocha這個it的test徹底結束。 }) it('should contain posts', (done) => { axios .get(`http://localhost:${port}`) .then((response) => { seedArticles.forEach((item, index, list) => { // ⚠️,respnse.text目前是undefined!,沒有test這個屬性,有data屬性。 if (item.published) { expect(response.data).to.include(`<h2><a href="/articles/${item.slug}">${item.title}`) } else { expect(response.data).not.to.include(`<h2><a href="/articles/${item.slug}">${item.title}`) } }) }) .catch((err) => { console.log("Throw err: ", err) }) .then(done) }) }) after(() => { shutdown() }) })
在第一個describe內嵌套一個describle:
//和上面的describe不能一塊兒運行,不然鏈接不到url。 緣由未找到!!! describe('article page', () => { it('should display text or 401', (done) => { let n = seedArticles.length seedArticles.forEach((item, index, list) => { // console.log(`http://localhost:${port}/articles/${seedArticles[index].slug}`) axios .get(`http://localhost:${port}/articles/${seedArticles[index].slug}`) .then((response) => { console.log("success!!", seedArticles[index].slug) if (item.published) { expect(response.data).to.include(seedArticles[index].text) } else { expect(response).to.exist expect(response.data).to.be(401) } }) .catch((error) => { console.log("error!!!", seedArticles[index].slug) console.log(error.message) }) }) done() //這裏⚠️,axios異步是在一個循環內部執行屢次,所以done放在這個it代碼最後一行
}) })
done()方法是mocha檢測異步函數測試結束的回調函數!具體見文檔搜索done()
⚠️原文response.text是❌的,應該改爲response.data
在完成下一章重寫app.js後,執行mocha test
提示❌:最後檢查app.js發現
require('./routes')返回一個空對象{}, 這是語法糖的緣由。解決方法:
在routes/index.js文件,引入article.js和user.js
exports.article = require('./article') exports.user = require('./user') exports.index = (req, res, next) => { req.collections.articles .find({published: true}, {sort: {_id: -1}}) .toArray((error, articles) => { if (error) return next(error) res.render('index', {articles: articles}) }) }
cursor.sort(sort)
// 參數sort是一個document對象,格式是{field: value}
// 升降分類: 值爲1是升序, -1爲降序。
// 複雜的排序方式見MongoDB文檔說明。
如此require('./routes')會返回一個對象:
{
article: {
show: [Function],
list: [Function],
add: [Function],
edit: [Function],
del: [Function],
postArticle: [Function],
admin: [Function]
},
user: {
list: [Function],
login: [Function],
logout: [Function],
authenticate: [Function]
},
index: [Function]
}
再次執行mocha test,仍然❌
Error: Route.get() requires callback functions but got a [object Undefined]
執行:
node inspect app.js //app.js內能夠加上debugger
發現:第76行❌
76 app.get('/post', routes.article.post)
後面都報告錯誤,註釋掉76-86行,則正常。
!!這是由於我沒有安裝pug視圖引擎,也沒有寫view相關頁面代碼的緣由。
在完成後續步驟後,再執行分別對2個describe塊執行測試,成功。
問題:執行完測試,不能自動回到terminal, 須要ctrl + C返回terminal.
修改app.js:
const express = require('express') const http = require('http') const path = require('path') const ejs = require('ejs') //使用html模版 const routes = require('./routes') //引入routes文件下的腳本 const mongoose = require('mongoose')//引入mongoose const dbUrl = process.env.MONGOHQ_URL || 'mongodb://127.0.0.1:27017/blog' mongoose.connect(dbUrl) const db = mongoose.connection; //獲得默認的鏈接的數據庫數據。 db.on('error', console.error.bind(console, 'connection error:')); // db.once('open', function() { // // we're connected! // }); //從數據庫取出數據,存入一個變量collections const collections = { articles: db.collection('articles'), users: db.collection('users') }
添加如下聲明,用於Express.js中間件模塊:
// 添加中間件: const logger = require('morgan') //用於logging const errorHandler = require('errorhandler') //錯誤處理 const bodyParser = require('body-parser') //解析進入的HTTP請求 bodies const methodOverride = require('method-override') //to support clients that do not have all HTTP methods
// 建立Express實例, let app = express() app.locals.appTitle = 'blog-express'
這一步🌿:經過req對象,在每一個Express.js route內,引入MongoDB collections:
// decorator pattern: //經過req對象, expose mongoose/MongoDB collections 在每一個Express.js route內 app.use((req, res, next) => { if (!collections.articles || !collections.users) { return next(new Error("No collections.")) } req.collections = collections return next() })
上面的代碼叫作decorator pattern
一種Node的設計模式,能夠參考在線課程:Node Patterns: From Callbacks to Observer. (收費!)
這種設計思路是讓req.collections在後續的中間件和路徑內可使用。
⚠️使用next()在中間件內部,不然請求會停滯。
app.use([path,] callback [, callback...])
Mounts the specified middleware function(s) at the specified path:
當請求的路徑匹配path時,中間件函數被執行
回調函數,能夠是:
例子,按順序執行中間件函數:
// this middleware will not allow the request to go beyond it app.use(function(req, res, next) { res.send('Hello World'); }); // requests will never reach this route app.get('/', function (req, res) { res.send('Welcome'); });
繼續,下一步:
在第2章已經寫好這些代碼:
定義Express設置:創建端口,模版的路徑位置,使用什麼模版引擎。
// 定義Express設置:創建端口,模版的路徑位置,使用什麼模版引擎。 app.set('appName', "Blog") app.set('port', process.env.PORT || 3000) app.set('views', path.join(__dirname, 'views')) app.engine('html', ejs.__express) app.set('view engine', 'html')
如今,是對你已經熟悉的經常使用功能: 請求登錄的中間件,轉化JSON input, 使用Stylus for CSS,和serving of static content。
使用app.user()插入這些中間件到Express app。
app.use(logger('dev')) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({extended: true})) app.use(methodOverride()) app.use(require('stylus').middleware(path.join(__dirname, 'public'))) app.use(express.static(path.join(__dirname, 'public')))
使用標準的Express.js error handler, 以前使用require()引入的。
errorhandler是一個(300✨)的用於開發階段的錯誤處理中間件。
if (app.get('env') === 'development') { app.use(errorHandler('dev')) }
下一步,app.js會處理服務器路徑。路徑鏈接到views:
app.get('/', routes.index) app.get('/login', routes.user.login) app.post('/login', routes.user.authenticate) app.get('/logout', routes.user.logout) app.get('/admin', routes.article.admin) app.get('/post', routes.article.post) app.post('/post', routes.article.postArticle) app.get('/articles/:slug', routes.article.show)
EEST api routes大多數時候用在admin page:
// REST API routes // 用在adimn page。 That's where our fancy AJAX browser JavaScript will need them. // 他們使用GET,POST, PUT, DELETE方法,不會渲染網易模版,只輸出JSON數據。 app.get('/api/articles', routes.article.list) app.post('/api/articles', routes.article.add) app.put('/api/articles/:id', routes.article.edit) app.delete('/api/articles/:id', routes.article.del)
加上一個404route, 用於用戶鍵入了錯誤的URL的提示:》
app.all("*", (req, res) => { res.status(404).send() })
建立一個http.Server的實例
// 建立一個http.Server的實例 // 第3章的代碼: // 根據是否在terminal執行node app.js來決定: // 1.執行server.listen(), // 2.出口module到緩存。 const server = http.createServer(app) const boot = () => { server.listen(app.get('port'), () => { console.info(`Express server listening on port ${app.get('port')}`) console.log(`Express server listening on port ${app.get('port')}`) }) } const shutdown = () => { server.close() } if ( require.main === module ) { boot() } else { console.log('Running app as a module') exports.boot = boot exports.shutdown = shutdown exports.port = app.get('port') }
完成app.js✅
下一步添加index.js
, article.js
, and user.js文件到routes文件夾內。
The method for the GET /users
route, which should return a list of existing users (which we'll implement later)
export.list = (req, res, next) => { res.send('respond with a resource') }
這個爲GET /login page route的方法,會渲染login form:(login.html或.pug)
exports.login = (req, res, next) => { res.render('login') }
這個方法最終會destroy the session並返回到home page:
exports.logout = (req, res, next) => { res.redirect('/') }
這個爲POST /authenticate route的方法,會處理驗證並返回到admin page:
exports.authenticate = (req, res, next) => { res.redirect('/admin') }
上面是 user.js的徹底代碼,一個輸出4個方法。
如今最主要的database行爲發生在article.js routes。
GET article page (異步函數的2種寫法),查詢一個article的,進入一個article的詳情頁面:
傳統:推薦✅,由於文檔裏回調函數的參數已經提供好了。
exports.show = (req, res, next) => { if (!req.params.slug) return next(new Error('No article slug.')) req.collections.articles.findOne({slug: req.params.slug}, (error, article) => { if (error) return next(error) if (!article.published) return res.status(401).send() res.render('article', article) }) }
使用promise,⚠️,若是使用promise,回調參數只有article
exports.show = (req, res, next) => { if (!req.params.slug) { return next(new Error('No article slug.')) } req.collections.articles.findOne({slug: req.params.slug}) .then((article) => {if (!article.published) return res.status(401).send() res.render('article', article) }) }
下面4個函數,用於API
GET /api/articles API route(在admin page使用), 在網址輸入框輸入:
http://localhost:3000/api/articles會獲得JSON格式的數據。
exports.list = (req, res, next) => { req.collections .articles .find({}) .toArray((error, articles) => { if (error) return next(error) res.send({articles: articles}) }) }
POST /api/articles
API routes (used in the admin page),
exports.add = (req, res, next) => { if (!req.body.article) return next(new Error('No artilce payload')) let article = req.body.article article.published = false req.collections.articles.insert(article, (error, articleResponse) => { if (error) { return next(error) } res.send(articleResponse) }) }
req.body獲得提交的key/value數據(具體見文檔,或者文章底部的解釋。)
PUT /api/articles/:id API(admin頁面):
(文章使用updateById方法,我改爲updateOne方法)
exports.edit = (req, res, next) => { if (!req.params.id) return next(new Error('No article id')) // 不知道改的對不對?? req.collections.articles.updateOne( {"_id": req.params.id}, {$set: req.body.article}, (error, result) => { if (error) return next(error) res.send({affectedCount: result.modifiedCount}) } ) }
DELETE /api/articles/:id API route
exports.del = (req, res, next) => { if (!req.params.id) return next(new Error('No article ID.')) req.collections.articles.deleteOne( {"_id": req.params.id}, (error, result) => { if (error) return next(error) res.send({affectedCount: result.deletedCount}) } ) }
後續的函數:
其實routes的腳本,至關於Rails中的controller的功能,和數據庫交互。還包括一些model的功能,如一些驗證。
// get article POST page
// http://localhost:3000/post, 進入文章建立頁面,建立一篇新文章。 exports.post = (req, res, next) => { if (!req.body.title) { return res.render('post') } } exports.postArticle = (req, res, next) => { // 表格必須都填入信息,不然渲染回post,並附帶一個錯誤信息: if (!req.body.title || !req.body.slug || !req.body.text) { return res.render('post', {error: 'Fill title, slug and text.'}) } const article = { title: req.body.title, slug: req.body.slug, text: req.body.text, published: false } req.collections.articles.insert(article, (error, articleResponse) => { if (error) return next(error) res.render('post', {message: 'Article was added. Publish it on Admin page'}) }) } // Get /admin 頁面,取數據 exports.admin = (req, res, next) => { req.collections .articles.find({}, {sort: {_id: -1}}) .toArray((error, articles) => { if (error) return next(error) res.render('admin', {articles: articles}) }) }
view的template: 下載代碼,⚠️模版使用pug,須要下載並在app.js內培訓app.set('view engine', 'pug')
下載public文件夾下的文件:其中包括bootstrap, 和jquery
教程使用的bootstrap已通過期。去官網下載一個相近的版本3.4.0,用編譯好的。解壓後複製到public/css。
jquery直接複製上面的鏈接。
補充 style.css, blog.js。
以後進行測試,
而後,打開本地數據庫,mongod, 運行在localhost和使用默認的端口27017。
而後,啓動app.js腳本: node app.js, 便可訪問
備註:
❌:
admin頁面,ation的delete和update行爲。不能寫入數據庫!
// PUT /api/articles/:id API route exports.edit = (req, res, next) => { if (!req.params.id) return next(new Error('No article id')) console.log("edit!!!") req.collections.articles.updateOne( {_id: req.params.id}, {$set: req.body.article}, (error, result) => { console.log("callback, edit!!!") if (error) return next(error) res.send() } ) } // DELETE /api/articles/:id API route exports.del = (req, res, next) => { if (!req.params.id) return next(new Error('No article ID.')) req.collections.articles.deleteOne( {_id: req.params.id}, (error, result) => { if (error) return next(error) res.send({affectedCount: result.deletedCount}) } ) }
花費了2個多小時問題未找到。暫時擱置!
req.collections.articles的值是一個複雜的對象,來自app.js腳本中:
const mongoose = require('mongoose') const dbUrl = process.env.MONGOHQ_URL || 'mongodb://127.0.0.1:27017/blog' mongoose.connect(dbUrl) const db = mongoose.connection; const collections = { articles: db.collection('articles'), users: db.collection('users') } let app = express() app.use((req, res, next) => { if (!collections.articles || !collections.users) { return next(new Error("No collections.")) } req.collections = collections return next() })
req.collections.articles可使用find方法。
猜想: 它鏈接到了mongoDB/mongoose。
可是我嘗試下面的腳本❌,
db.collection("articles").find({})返回一個undefined。很奇怪
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/blog', {useNewUrlParser: true}); var db = mongoose.connection; console.log(db.collection("articles").find())
奇怪?db.collections無效?返回一個{}空hash。方法定義 :A hash of the collections associated with this connection
爲了方便使用res的寫法,能夠根據回調函數本身定義的參數名字寫。
res對象是Node原生對象的升級版,它支持全部的內建fields和方法。
發送http響應。
參數body,能夠是Buffer object, string, object, array。
//例子
res.send('<p>some html</p>')
res.status(404).send({error: 'something blew up'})
res.render(view,[locals], [callback])
渲染一個view, 併發送這個渲染的HTML string給客戶端。
參數:
view: 一個字符串,渲染視圖的路徑,能夠是絕對或相對路徑。
locals:一個對象,傳入view的數據。
callback: function(err, html){} 。若是提供了回調函數,就不會自動執行響應,須要使用res.send()
res.redirect([status,] path)
res.redirect('/foo/bar'); res.redirect('http://example.com'); res.redirect(301, 'http://example.com'); res.redirect('../login');
req.params
This property is an object containing properties mapped to the named route 「parameters」.
一個對象:包含請求的URl的參數/值對兒。
Route parameters
Route path: /users/:userId/books/:bookId Request URL: http://localhost:3000/users/34/books/8989 req.params: { "userId": "34", "bookId": "8989" }
req.body
在request的body內包括了提交的key/value data.
默認是undefined。當你使用body-parsing中間件時,如body-parser, multer
$ npm install body-parser //API var bodyParser = require('body-parser')
// 在程序的app.js腳本內,express的實例app
//app.use(bodyParser.json())for parsing application/json
// app.use(bodyParser.urlencoded({extended: true}))for parsing application/x-www-form-urlencoded
db.collection_name.drop()
刪除這個collection
https://mongoosejs.com/docs/api.html#connection_Connection-dropCollection
Connection.prototype.dropCollection()
//start.js var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}); var db = mongoose.connection; //假設test數據庫內有messages collection,下面的命令會完全刪除這個collection db.dropCollection("messages")
相似命令
console.log(db.collection("inventory")) 返回collection實例。
Not typically needed by applications. Just talk to your collection through your model.
對程序來講沒什麼用途,只是經過你的model告訴你的collection。