MaDi's Blog

一個紀錄自己在轉職軟體工程師路上的學習小空間

0%

[Node.js + Firebase] 全端網站開發-學習筆記

前陣子有參加一堂教Node.js+Firebase全端的課程,為了怕學完之後全部忘光,特別紀錄了一下自己學習的心得,以利未來需要用Node.js來開發的自己參考參考。

Node.js

Q. 何謂Node.js?

是別人用javascript寫成的套件,讓使用者可以在伺服端也可以使用javascript

Q. 為何選擇Node.js?

  1. 對於前端工程師轉往後端發展是相當友善,因為只需要會javascript一種語言就能夠寫前後端

  2. 因為傳統的後端伺服器在每個使用者連結時都會送他一個新的執行緒,會於IO密集型的需求會有麻煩,而Node.js可以透過異步IO來解決。

  3. 因為javascript是”事件驅動”的程式語言,可以request為基底,跑事件迴圈來讓執行緒幾乎不會卡住。

Q. 何謂NPM?

NPM(Node Package Manager),是一個線上套件庫,專門下載各式各樣的javascript套件來使用

環境搭建

  • Node.js
  • Express框架
  • VSCode(略)

Node.js

Q. 如何安裝?

官網安裝載點Automatically install the necessary tools...不要打勾,其他按next
安裝好之後,在終端機上輸入node -vnpm -v,如果可以看到version就是安裝成功。

Q. 如何下載套件?

1
2
3
cd 資料夾
npm init # 用npm來初始化資料夾,裡面會有json.package檔,只要把此json檔給別人就不用下載xxx套件
npm install xxx--save # 下載xxx套件並存入package.json

Q. 如何運行程式?

node 檔名.js

Express框架

別人寫好的module,直接require就能用,專門拿來寫輕型的網站,操作較簡單易懂

Q. 如何下載Express套件?

1
2
3
4
5
6
cd Desktop                 
mkdir demo #建立demo資料夾
cd demo

npm init # 用npm來初始化demo資料夾,裡面會有json.package檔,只要把此json檔給別人就不用下載express
npm install express --save # 下載express套件到demo資料夾並存入package.json

網頁與瀏覽器與伺服器

網頁前後端分別擔任的角色可以用下圖來呈現:

  1. 首先,使用者選定某個想去的網站,並告訴瀏覽器,請瀏覽器幫你去問伺服器
  2. 瀏覽器去詢問伺服器,伺服器再去詢問資料庫
  3. 資料庫回傳資料給伺服器,伺服器再回傳給瀏覽器
  4. 瀏覽器顯示靜態網頁給使用者(response)

其中,伺服器又細分成

  1. 網頁伺服器(Web Server)
  2. 應用程式(應程)伺服器(Application Server)

而Node.js則是負責以下工作:

  1. 建立伺服器
  2. 接收request
  3. 回傳response
  4. 與資料庫溝通

網址、網頁、伺服器

URL網址

全名為 “統一資源定位符(Uniform Resource Locator)”

URL指的是網頁的地址

URL裏頭每個位置分別記錄不同的資訊:

1.通訊協定(protocol),ex: http、https…
2.主機位址、網域名稱(domain name)
3.連接埠(port)
4.路徑
5.查詢字串

域名

IP位址的代稱

IP位置,全名為”網際網路協定位址(Internet Protocol Address)”

IP位置由四個0~255的數字所組成,ex: 172.217.160.68

網站、URL、IP三個角色可以比喻如下:

1
2
3
4
5
6
7
網站: 家
URL: 家的門牌 (有很多個)
IP Address: 家的經緯度 (唯一一個)

舉Google為例子
IP Address: 172.217.161.174
URL: www.google.com、https://www.google.com...

特殊IP:

1
2
IP: 127.0.0.1
URL: localhost

這組特殊的IP指的是本地主機,任何人的127.0.0.1都是自己的主機。這個地址讓所有的網站開發者可以藉由自己的電腦來測試網站。

埠口(port)

在網路世界中,你的電腦的port就好像一個港口。就是一個接口,讓別人連接,或是讓你接收別人的資訊。

0到1023號埠不要使用!因為是電腦系統本身在使用的。請盡量使用 5000~9999 的 port

request v.s response

  • 請求標頭(request Header)

    • 傳送一些你的資訊給你要 request 的伺服器。
      例如:你的時區、你習慣用什麼語言、你用的瀏覽器是什麼…等等。
  • 回應標頭(response Header)

    • 伺服器回傳資訊給瀏覽器時,也需要告訴瀏覽器一些資訊。
      例如:當前回傳的 response 是一個 html檔案、js 檔案、圖片、json物件…等等。

route(路由or路徑)

舉例來說,一個網址長這樣

https://zh.wikipedia.org /wiki/Node.js

斜線後面的就是route,用來分配不同的request到不同的網站,如同資料夾一樣。當使用者用getpost的方式傳request給這個路徑的時候,server就會執行後面的function。

DOM結構教學

  • DOM全名是 “Document Object Model”
  • HTML的DOM架構
    • 當瀏覽器要載入這個網頁的時候,就會生成這個 html 架構。
    • 也就是說,其實 html 本身就是一個物件(object)

HTML DOM 是為了方便讓你使用 js 對 HTML 元素進行以下操作:

  1. 選擇(select)
  2. 改變(change)
  3. 增加(add)
  4. 刪除(delete)
  • 選擇 DOM 物件 (Select)
  1. document.querySelector(selectors)
  2. document.querySelectorAll(selectors)
  • 改變 DOM 的內容 (change)
  1. 改變 HTML: innerHTML
  2. 改變 屬性(attribute): setAttribute(old,new)
  • 增加 DOM 的元素(add)
  1. document.createElement(element)
  2. document.appendChild(element)
  • 刪除 DOM 的元素 (delete)
  1. element.removeChild(element)
  2. element.replaceChild(new, old)

事件(event)

  • javascript 可以監聽 html 是否有 事件 發生,並且對其作出反應,例如有:
    • HTML input 輸入框發生改變。(onchange)
    • HTML button 被按了一下。(onclick)

表單(form)結構、GET、POST

1
2
3
4
5
6
7
<form action="送出目的地" method="資料傳送方式">
表單內容...
...
<input type="text" name="id" />
<input type="password" name="password" />
<input type="submit" />
</form>
  1. 送出目的地,ex: “http://127.0.0.1:8888"

  2. name名稱是表單把資料傳往伺服器的變數名稱

  3. 資料傳送方式分為以下兩種:

    • GET

      • 用網址來傳送值,伺服器從 request 中的網址收到這個值
      • 較不重要,可以公開(明信片),會出現在網址上面,直接採用url模組來分析網址就可以收信
    • POST

      • 別人無法看到內容,伺服器會用特別的方式收下這封信。
      • 較隱私(信封內有裝信件),收信較麻煩所以採用express框架

也就是說,當你用 GET 或 POST 傳 request 給這個PATH時,server 會執行後面的 function。

伺服器搭建

Q. 何謂模組(module)?

別人寫好的很多函式,可以提供給使用者引入來應用,俗稱API(Application progamming interface),當code愈來愈龐大就可以寫在不同檔案裏頭,即稱為模組。

Q. 如何用javascript客製化自己的模組?

在模組檔案裏頭,module.exports = xxx,提供接口,當別人使用該模組時,就會拿到xxx這個值

伺服器搭建有兩種方式,httpExpress框架,其中Express是適合輕型框架網站的開發。

  1. http來搭建伺服器的程式碼:

    1
    2
    3
    4
    5
    6
    7
    8
    let http = require('http');   //引入http模組

    //使用http模組裏頭的createServer函式,會回傳一個物件,有listen這個函式
    http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('<html><body>Demo</body></html>');
    res.end('Hello World!');
    }).listen(8080); //將伺服器監聽在8080這個port
    • response.writeHead(): 設定response header

    • content-type: 用來表示你當前傳的資料類別。[content-type 的官方文檔]

    • res.write(): 寫回應的網頁body內容

    • res.end(): 送出response

    • createServer().listen(8080): 開一個server在port 8080,而你連結的方式就是localhost:8080127.0.0.1:8080

      使用 parse 來分析url

      1
      let query = url.parse(request.url, true).query;
  2. Express框架搭建伺服器的程式碼:

    app.METHOD(PATH, function)

    METHOD分為getpost

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const express = require('express'); //引入Express
    const app = express(); // 建立server

    // 以下三行很重要!
    const bodyParser = require('body-parser');
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));

    app.get("/", function(req, res) {
    res.send("This is /");
    });
    app.get('/about', function(req, res) {
    console.log(request.query.id); //GET取得資料
    console.log(request.query.password); //GET取得資料
    res.send("This is about");
    });
    app.post('/post', function(req, res) {
    console.log(request.body); //POST取得資料
    res.send("This is post");
    });

    Express框架存取資料的地方:

    1. GET:
      • request.query
      • 資料都存在 request 物件的 query 物件裡面
    2. POST
      • request.body
      • 資料都存在 request 物件的 body 物件裡面

通常簡易的網站都用Express框架就可以處理,操作起來getpost都比較簡單一點,不需要每個route都要用parse來分析url。不然這樣會需要太多switch的case來決定流程的控制。

Express框架裏頭的其他操作:

  1. 讀取檔案 (ex:html、css、javascript…):

    1
    2
    3
    4
    5
    6
    let fs = require('fs') //需要引入fs這個模組
    fs.readFile('test.html', function(err, data){
    if (err) throw err; //callback是讀取資料後會執行的函式
    res.write(data); //data是檔案內容
    res.end();
    })
  2. res.send(): 傳送各種類型的回應,例如: 字串

  3. res.sendFile(): 傳送檔案類型的回應

  4. res.direct():重新導向到其他網站或位址

  5. res.download(): 提示要下載的檔案

  6. res.end(): 什麼都不做,直接結束回應程序

  7. app.use(func): 希望每個網頁進入時,server都先做同一件事

  8. next(): 當 server 收到 request,會找最上面的 “中介軟體” 運作,然後就會卡在裡面,next 是為了把使用權給下一個 “中介軟體” 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let express = require('express');
    let app = express();

    let func1 = function (req, res, next) {
    console.log('func1');
    next();
    }
    let func2 = function (req, res, next) {
    console.log('func2');
    next();
    }
    let func3 = function (req, res) {
    res.send('This is Func3, End!');
    }

    app.get('/test', [func1, func2, func3]);
    app.listen(8888);
  9. router: 把不同網頁寫在不同檔案裏頭,以作為分類用途

    Madi.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var express = require('express');
    var router = express.Router();

    // middleware that is specific to this router
    router.use(function timeLog(req, res, next) {
    console.log('Time: ', Date.now());
    next();
    });
    // define the home page route
    router.get('/', function(req, res) {
    res.send('Madi\'s home page');
    });
    // define the about route
    router.get('/about', function(req, res) {
    res.send('About Madi');
    });

    module.exports = router;

    index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let express = require('express');
    let app = express();
    let Madi = require('./Madi.js'); //引入Madi.js
    app.use('/Madi', Madi);

    let get_time = function (req, res, next) {
    console.log('Time:', Date.now());
    next();
    };

    app.use(get_time);

    app.get("/", (req, res) => {
    res.send("/");
    });
    app.get("/about", (req, res) => {
    res.send("/about");
    });
    app.listen(8888);
  10. app.use(express.static('public')): 把public中的檔案全部丟上去,缺點是使用者只能看到畫面,不能處理get或post,而且public資料夾中的所有資料都會被公開。

Firebase

Q. 何謂Firebase?

是一種”即時”的線上資料庫,不像傳統的資料庫是靠Table欄位來建立資料,資料儲存的方式是更直觀的 JSON Tree。

資料庫由以下三個元素組成:

  1. Collection: 收納用的資料夾
  2. Document: 資料夾中放的紙
  3. Data: 紙上面記載需要的內容

以下為網路參考資料

創建流程: 創建 Firebase Project

如何在網頁中引入Firebase當中的資料: 引入資料

1
2
3
4
5
6
7
8
9
10
11
12
13
let firebase = require('firebase'); //引入firebase

var firebaseConfig = {
apiKey:... ,
authDomain:...,
databaseURL:...,
projectId:...,
storageBucket:...,
messagingSenderId:...,
appId:...
};

firebase.initializeApp(firebaseConfig); //Initialize Firebase

接下來操作Firebase的過程會需要用到promise的概念,甚至是async/await非同步的概念。

取出資料(get)

1
2
3
4
5
6
const db = firebase.firestore(); //取得與database的連結
db.collection('ClassA').get().then(data => {
data.docs.forEach(doc => {
console.log(doc);
});
});

使用 get() 來取出collection中的document,get()取出的會是Promise型態(因為讀取網路中的資料需要時間)。

新增資料(add,set)

用add方法

1
2
3
4
5
db.collection('ClassA').add({
name: Madi,
age: 25,
gender: male
})

連 document ID 都做修改的新增方法

1
2
3
4
5
db.collection('ClassA').doc('student1').set({
name: Madi,
age: 25,
gender: male
});

刪除資料(delete)

1
db.collection('ClassA').doc("你要刪除的id").delete();

後端操作firebase利用async/await

1
2
3
4
5
6
7
8
9
10
11
app.get("/", async (req, res) => {
let html = '';
await db.collection('classA').get().then(data => {
data.forEach(doc => {
console.log(doc.data())
html += `${html}<div>${doc.id}: name = ${doc.data().name} age = ${doc.data().age}</div>`;
});
});
console.log(html)
res.send(html)
})

參考

[Node.js + Firebase]全端實戰網站開發-台大資工(講師邱榆洋)