avatar

目錄
使用 Node.js 與 RESTful API 架構來串接 mongoDB 並部署到 Heroku

使用 Node.js 與 RESTful API 架構來串接 mongoDB 並部署到 Heroku

網站組成通常是前端加後端、資料庫,簡單來說,前端主要是畫面加上操作介面,包含了一些前端語言及框架,後端則處理使用者請求控制回應,例如登入,後台管理…等等,後端語言常見的有 PHP、Java、ASP.NET、Node.js、Python…等等。資料庫則有 MySQL、MSSQL、MongoDB…。

因前端不斷的演進,畫面處理需快速變化,傳送資料方式也大都改用 Web API 的方式來提供服務。所以後端(server)就負責建立 API,透過串接 API 處理資料。

這邊就要來使用 Node.js 搭配 express 架構 RESTful API,連接 MongoDB,並部署到 server。

MongoDB

是開源的 NoSQL 資料庫,不需先制定每張資料表的結構、畫出 ER Model,因此不管是用來做個人專案,還是用來處理較大量資料都可以。

MongoDB 是用 Key-value 的方式來儲存資料:

Code
1
2
3
4
{
name: "john",
job: "farmer"
}

另外 MongoDB 不是 JSON 格式,而是 BSON。它的 key 和 value 是有區分大小寫。但 MongoDB 大都是用 JSON 格式來儲存資料。

ps.BSON(Binary JSON),是 JSON 的擴充,因此可使用 Binary data 等。

MongoDB 跟傳統資料庫大致上對應的關係:

DB MongoDB MySQL
資料名稱 Document Row
資料存放區名稱 Collection Table
資料庫名稱 Database Database

RESTful API

REST(Resource Representational State Transfer 具象狀態變化傳輸),API(Application Programming Interface 應用程式介面)。

RESTful API 可以理解成具有 Rest 架構的 Web API,是一種設計風格。意思大致上是,由發送的 HTTP 請求中所包含的資訊,就可以容易解讀出這請求會收到什麼類型的資料。用淺顯易懂的方式來解釋,就是只看 API 的格式就可以看得懂。

由於 API 設計方式跟 HTTP 請求及資料庫資料操作有關,因此就簡略說明一下:

常見的 HTTP 請求方式:
GET:取得資料
POST:新增一筆新的資料(如果存在會新增一筆新的)
PUT:更新一筆資料,如果存在這筆資就會覆蓋過去
PATCH:部分更新資料
DELETE:刪除資料

資料庫基本操作 CRUD:
Create(新增)
Read(讀取)
Update(更新)
Delete(刪除)

常見的 HTTP method 正好會對應到資料庫基本操作。

假設有一組待辦事項的 API,或許會用以下方式來設計:

獲得資料GET /getData
新增資料POST /createData
刪除資料DELETE /deleteData/1

以 REST 風格來開發 RESTful API:

獲得資料GET /data
新增資料POST /data
刪除資料DELETE /data/1

兩者差異是在於 RESTful API 充分地使用了 HTTP Protocol Method,達到:

1.直觀簡潔的資源 URI
2.並且善用 HTTP Verb
3.達到對資源的操作
4.並使用 Web 所接受的資料類型: JSON, XML, YAML 等,最常見的是 JSON。

前置工作

1.建立 heroku 上的 mongoDB 資料庫

2.heroku 建立新 App

3.安裝 mlab 套件

要填寫信用卡資料(才能用 Add-ons)

首先到我們要安裝插件的應用程式頁面,點選「Configure Add-ons」

在搜尋列打上「mlab」,並安裝

4.建立使用者

5.記得連線 URL

mlab 建立後,可在畫面上找到。

格式:
"mongodb://dbuser:dbpass@host1:port1,host2:port2/dbname"

6.

接下來就是程式處理方面。

建立 nodejs server

先建立專案資料夾

npm init

npm install express --save

建立 index.js

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引用 express
const express = require('express');
const server = express();

// 預設 port
const port = process.env.PORT || 3000

// 建立 get method 顯示 index.html 內容
server.get('/', (req, res) => {
// __dirname 回傳被執行 js 檔所在資料夾的絕對路徑
res.sendFile(__dirname + '/index.html')
})
// 監聽 port
server.listen(port, () => console.log(`Listening on ${port}`))

建立 index.html

Code
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>

package.json 加上

Code
1
2
3
"scripts": {
"start": "node index.js" // 加此行
},

測試一下,終端機下執行 npm start,在瀏覽器網址輸入 http://localhost:3000/ 。看畫面是否成功出現 welcome 字樣。

連線 mongodb

1.安裝 mongodb

npm install mongodb --save

index.js 加上以下內容:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
// 引用
const MongoClient = require('mongodb').MongoClient;

// mongoDB 連線位置(可看 heroku 的 mlab 頁面有資訊)
const url = 'mongodb://<username>:<password>@<host1>:<port1>/<dbName>';

// 連線 mongoDB
MongoClient.connect(url, function (err, db) {
if (err) throw err;
const dbo = db.db("<dbName>")
console.log('mongoDB in running')
db.close()
});

測試一下,終端機下執行 npm start,終端機畫面上是否顯示 Listening on 3000 字樣。

若產生以下字樣

(node:39803) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.

修改連線內容

Code
1
2
3
4
5
6
7
8
9
10
// 連線 mongoDB
// 加上以下設定
//{ useNewUrlParser: true, useUnifiedTopology: true }
MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, db) {
// 連線資料庫
const dbo = db.db("<dbName>")
console.log('mongoDB in running')
// 關閉連線
db.close()
});

MongoDB 加上一筆資料

修改 index.js

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 連線 mongoDB
MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, db) {
if (err) throw err;
// 連線資料庫
const dbo = db.db("<dbName>")
// console.log('mongoDB in running')
// // 關閉連線
// db.close()
// 建立一筆資料
const myobj = { name: "jw", time: "2020/04/14/15:00", content: "hello world" };
// mongoDB 的操作方式
dbo.collection("comments").insertOne(myobj, function (err, res) {
if (err) throw err;
console.log("1 document inserted");
db.close();
});
});

執行 npm start,畫面上會顯示 1 document inserted。

heroku 的 mlab 頁面上會有新增的資料。

Get API-取得資料

index.js

Code
1
2
3
4
5
6
7
8
9
//GET API  從 http://localhost:3000/comments 取得資料
server.get('/comments', (req, res) => {
// 回傳 comments 的所有資料
db.collection('comments').find().toArray((err, result) => {
if (err) return console.log(err)
// 顯示取得資料在頁面上
res.send({ data: result })
})
})

終端機輸入 npm start,打開瀏覽器 http://localhost:3000/comments, 可以看到畫面顯示資料。

POST API-新增資料

下載 body-parser 套件,來處理資料格式,將其轉型別成 JSON。

npm install body-parser --save

index.js 加上

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const bodyParser = require('body-parser');

app.use(bodyParser.json());

// POST API 路徑為/comments expressjs 取參數的方法之一 req.body
server.post('/comments', (req, res) => {
// 顯示 clinet 端傳送過來的 JSON
console.log(req.body);
db.collection('comments').save(req.body, (err, result) => {
if (err) return console.log(err)
console.log('saved to database')
res.send(req.body);
});
})

// modify server.listen(3000, () => {});
/* 將之前的監聽 server.listen(port, () => console.log(`Listening on ${port}`)) 註解掉,改寫至 MongoClient.connect() 裡 */

MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
if (err) return console.log(err)
db = client.db('<dbName>')
server.listen(port, () => {
console.log('listening on 3000')
})
})

執行 npm start,打開瀏覽器輸入網址 localhost:3000,在 console 輸入:

JavaSript fetch post

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fetch('http://localhost:3000/comments', {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
// 傳給 server 的 JSON
body: JSON.stringify({
"name": "jw",
"time":"2020/04/13-15:10:01",
"content": "Hello"
})
}).then(res=>res.json())
.then(res => console.log(res))

ps. 顯示 (node:3970) DeprecationWarning: collection.save is deprecated. Use insertOne, insertMany, updateOne, or updateMany instead.

修改 save() 為提示中的函式 insertOne()…等等

DELETE API-刪除資料

利用 網址:id 來帶參數,方便維運 API。若要使用 MongoDB 預設的 _id 當作查詢刪除,需要使用 ObjectID 處理來 value,但如果使用其他物件來作查詢,就不需使用 ObjectID。

index.js

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ObjectID = require('mongodb').ObjectID;

// DELETE API
server.delete('/comments/:id', (req, res) => {
// use _id need use ObjectID(value)
const obj = { _id: ObjectID(req.params.id) };
// 顯示刪除 _id
console.log(obj)
if (!obj) {
res.sendStatus(403);
}
db.collection("comments").deleteOne(obj, function (err, obj) {
if (err) throw err;
console.log("1 document deleted");
// 回傳訊息
res.send('delete success');
});
})

ps. 顯示 (node:3826) DeprecationWarning: collection.remove is deprecated. Use deleteOne, deleteMany, or bulkWrite instead.

修改 remove() 為提示中的函式 deleteOne()…等

PUT API-更新(UPDATE)資料

updateOne(obj, newvalues, function(err, obj){}) 來更新資料,前面兩個參數分別帶查詢條件及更新資料。

index.js

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server.put('/comments/:id', (req, res) => {
// 顯示 id 及 修改內容
console.log(req.params.id, req.body);
if (!req.body) {
res.sendStatus(403);
}
const newvalues = { $set: req.body };
const obj = { _id: ObjectID(req.params.id) };
db.collection("comments").updateOne(obj, newvalues, function (err, obj) {
if (err) throw err;
console.log("1 document update");
res.send('update success');
});
})

建立 config.json

可以將連線字串、帳密…環境變數等,放在這再引用,方便修改管理。

config.json

Code
1
2
3
{
"DB_URL" : ""mongodb://dbuser:dbpass@host1:port1,host2:port2/dbname""
}

index.js 加上

const { DB_URL: url } = require('./config.json');

postman 測試

處理完 RESTful API 部分後,可以使用 postman 來測試。

GET

% asset_img postman_get.png This is an image %}

POST(記得依照標頭輸入設定來調整)

PUT(記得依照標頭輸入設定來調整)

DELETE

部署到 Heroku

部署到 server 上,可方便使用 API。

終端機輸入:

  1. heroku config set
Code
1
$ heroku config:set PROD_MONGODB=mongodb://dbuser:dbpass@host1:port1,host2:port2/dbname

2.登入 heroku

heroku login

3.建立 GIT

git init
git add .
git commit -m "nodejs"

4.設定連結的 heroku 遠端主機

heroku git:remote -a (Heroku 上 的 App 名稱)

5.push remote

git push heroku master

heroku open

打開瀏覽器,輸入 heroku 的 app 路徑加上 /comments。

接著處理前端及畫面互動就可以做其他運用了。

CORS

若是其他應用要連線 heroku 取用 API 資料時,有可能會發生 CORS 問題。可用以下方式處理。

1.後端 API 開放跨網域 cors 權限

安裝 cors

npm install cors

index.js 加上

const cors = require('cors')

server.use(cors());

也可以單一 API 使用

Code
1
2
3
4
5
server.get('/', (req, res) => {
res.send('welcome');
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
});

2.使用第三方資源來協助存取使用 cors-anywhere、bypasscors

將 cors-anywhere 所提供的 API 放前面,後面加上要訪問的 API。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
// use cors-anywhere to fetch api data
const cors = 'https://cors-anywhere.herokuapp.com/';
// origin api url
const url = 'https://xxx.xxx/xxx.php?=2223';

axios.get(`${cors}${url}`)
.then((response) => {
const msg = response.data;
document.body.innerHTML = JSON.stringify(msg)
},
(error) => {
}
);

參考:
https://iandays.com/2018/10/11/nodejsapi/
https://blog.johlmike.com/2016/08/05/heroku-cha-jian-mlab-mongodb-zi-liao-ku/
https://blog.johlmike.com/2016/07/12/mongoose-node-js-上連接-mongodb/#more-453
https://ithelp.ithome.com.tw/articles/10186483
https://dotblogs.com.tw/felixblog/2019/12/11/171324
https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/express-restfulapi%E8%B5%B7%E6%89%8B%E6%95%99%E5%AD%B83-77206cd64ebb
https://i5ting.github.io/node-http/
https://progressbar.tw/posts/53
https://medium.com/itsems-frontend/api-是什麼-restful-api-又是什麼-a001a85ab638
https://ithelp.ithome.com.tw/articles/10157431
https://ithelp.ithome.com.tw/articles/10157674
https://noob.tw/mongodb/
https://medium.com/@des75421/cors-跨來源資源共用cors-191d4bfc4735
https://andy6804tw.github.io/2017/12/27/middleware-tutorial/#中介軟體
https://andy6804tw.github.io/2019/09/21/fix-cors-problem/