呼叫API

為何需要API

  • 目前網站、APP非常盛行,這些前端的介面如何取得資料,並且將資料呈現出來,就是一個資訊技術上的問題
  • 首先,資料的來源最為大家所熟知的就是資料庫了
  • 那麼APP或網站要呈現資料,就是直接連線到資料庫取得資料嗎?
  • 不行,會有很多問題,例如資料庫可能會無法負荷連線量、有可能會有資訊安全的問題

直接連資料庫是有危險的

  • 另外,如果我想取得的資料,我沒辦法連線到那個資料庫,那怎麼辦?
  • 所以API是一個機制,讓APP、網站可以藉由API去取得資料,再由API去資料庫取得資料,那麼APP或網站就不需要知道資料庫連線的帳號密碼,一樣可以取得資料了

使用API取得資料

API概述

url的組成(資料出處:https://www.welcometothejungle.com/en/articles/btc-url-internet)

  • API基本上就是一個網址

  • 而網址我們會帶著一些資訊,來告訴這網址我們需要索取的內容,例如:https://www.google.com/search?q=node.js

    • 上述例子,網址後面的q=node.js,這種將資訊直接帶在網址上的方式,稱為GET,資訊藉由**q=**來帶給API
    • 另一種常用的方式叫做POST,他是將資訊帶在傳輸的資料中,而不是網址上
  • API常見的傳輸資料叫做JSON格式,以下是一些Sample

    {
        "university": "SHU",
        "department": "Information Management"
    }
    {
        "universities": [
            {
                "name": "SHU",
                "location": "Taipei"
            },
            {
                "name": "NTU",
                "location": "Taipei"
            },
            {
                "name": "YZU",
                "location": "Taoyuan"
            }
        ]
    }

內容大綱

  • 主要讓大家先會使用呼叫API,再學會自己建立API給別人呼叫
  • 這裡都會以GET的方式為例子

呼叫API

推薦API測試工具

  • 這邊推薦給大家2個工具

    • Hoppscotch:可直接在網頁上測試API
    • Postman:需要下載App,但我自己比較常使用這個
  • 本次範例都先使用Hoppscotch來做說明

    Hoppscotch

  • 打開該網站,在畫面中會看到一個輸入框,直接輸入API https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json,並且按下「Send」,就可以看到回傳的資料了

    [
      {
        "sno": "500101001",
        "sna": "YouBike2.0_捷運科技大樓站",
        "tot": "28",
        "sbi": "0",
        "sarea": "大安區",
        "mday": "2021-11-03 09:43:03",
        "lat": "25.02605",
        "lng": "121.5436",
        "ar": "復興南路二段235號前",
        "sareaen": "Daan Dist.",
        "snaen": "YouBike2.0_MRT Technology Bldg. Sta.",
        "aren": "No.235, Sec. 2, Fuxing S. Rd.",
        "bemp": "28",
        "act": "1",
        "srcUpdateTime": "2021-11-03 09:54:16",
        "updateTime": "2021-11-03 09:54:50",
        "infoTime": "2021-11-03 09:43:03",
        "infoDate": "2021-11-03"
      },
      ...
    ]
  • 依照官方文件的說明,可以知道欄位資料的意義

    sno(站點代號)、sna(場站中文名稱)、tot(場站總停車格)、sbi(場站目前車輛數量)、sarea(場站區域)、mday(資料更新時間)、lat(緯度)、lng(經度)、ar(地點)、sareaen(場站區域英文)、snaen(場站名稱英文)、aren(地址英文)、bemp(空位數量)、act(全站禁用狀態)、srcUpdateTime、updateTime、infoTime、infoDate
  • 對這次範例來說,有用的資料為sno(站點代號)、sna(場站中文名稱)、sbi(場站目前車輛數量)

第一步、建立應用程式檔案

  • 首先打開檔案總管,在「文件」的資料夾下建立一個叫做workshop的資料夾

  • 對該資料夾按下滑鼠右鍵,選擇「以Code開啟」。如果你按下滑鼠右鍵沒有這個選單,就直接打開VS Code,在VS Code上方「檔案」->「開啟資料夾」,然後選擇剛剛在文件下建立的workshop資料夾

    打開VSCode

  • 要建立Node.js的應用程式,首先需要一個Javascript檔案,所以在剛剛開啟的VS Code專案下,選擇上方「檔案」-> 「新增檔案」,這時候畫面就會出現一個新的檔案名叫Untitled-1

  • 我們先將這個沒有命名的檔案存檔,在VS Code上方「檔案」->「儲存」,或你可以按下快速鍵「Ctrl + s」,接下來會跳出存檔視窗,檔案名稱儲存為index.js

第二步、輸入程式碼

  • 要呼叫API,我們要使用到Node.js的套件request

  • 完整程式碼如下:

    const request = require('request');
    
    var options = {
      method: 'GET',
      url: 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json'
    };
    request(options, function (err, response, body) {
      if (err) {
        console.error(err);
        return;
      }
      console.log(body);
    });
  • 程式碼說明:

    • 這表示我們這個程式碼會使用到request這個套件
    const request = require('request');
    • 這是建立一個要呼叫的API的資訊物件,options
      • method:就是要呼叫API的方式,這邊使用GET
      • url:要呼叫的API
    var options = {
      method: 'GET',
      url: 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json'
    };
    • 使用在第一行宣告使用的套件request,傳送2個參數
      • options:要呼叫的API資訊物件
      • function (err, response, body) {...}:這個叫做回呼函式(callback),當呼叫的API有收到結果時,就會從回呼函式傳回結果,這個回呼函式會有3個參數
        • err:如果有出錯時,err就會收到回傳的結果
        • response:可以從這個參數得到回傳的資訊,例如response.statusCode,取得連線的狀態碼,詳細說明見此,最常表示成功的代碼是200
        • body:如果呼叫API成功時,可以得到回傳的資料
    request(options, function (err, response, body) {
      ...
    });
    • 再來看回呼函式內的程式碼
    • if (err) {...}:表示如果回呼函式的第1個參數不是空的,就表示有發生錯誤
    • console.error(err):使用Javascript內建錯誤內容在畫面上的方式,印出發生的錯誤
    • console.log(body):使用Javascript內建印資料文字在畫面上的方式,印出API回傳的資料
    if (err) {
      console.error(err);
      return;
    }
    console.log(body);

第三步、執行應用程式

  • 要執行Node.js的應用程式需要用到指令模式,在Windows中稱為「命令提示字元」或是「終端機」或是「Power Shell」或是「Command Prompt」或是「cmd」,但VS Code中有內建這個功能

  • 在VS Code上方選單,「終端機」->「新增終端」,預設就會在VS Code最下方出現指令模式

    打開終端機

  • 這邊要說明Node.js最常使用到的指令之一,node <javascript的檔案>,本範例執行的例子如下:

    node index.js
  • 在VS Code的指令模式中輸入以上指令node index.js,並且按下Enter來執行

  • 糟糕!出錯了,錯誤應該跟下面一樣。這個錯誤表示你需要安裝套件,以下錯誤說到Error: Cannot find module 'request',表示缺少了request這個套件

    internal/modules/cjs/loader.js:883
      throw err;
      ^
    
    Error: Cannot find module 'request'
    Require stack:
    - C:\Users\liu71\Documents\workspace\2021-nodejs-api-workshop\call_api\index.js
        at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
        at Function.Module._load (internal/modules/cjs/loader.js:725:27)
        at Module.require (internal/modules/cjs/loader.js:952:19)
        at require (internal/modules/cjs/helpers.js:88:18)
        at Object.<anonymous> (C:\Users\liu71\Documents\workspace\2021-nodejs-api-workshop\call_api\index.js:1:17)
        at Module._compile (internal/modules/cjs/loader.js:1063:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
        at Module.load (internal/modules/cjs/loader.js:928:32)
        at Function.Module._load (internal/modules/cjs/loader.js:769:14)
        at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) {
      code: 'MODULE_NOT_FOUND',
      requireStack: [
        'C:\\Users\\liu71\\Documents\\workspace\\2021-nodejs-api-workshop\\call_api\\index.js'
      ]
    }
  • 安裝套件之前,我們要先建立Node.js的專案,這邊使用到最常使用的指令之二,npm init,在指令模式下輸入這個指令

    npm init
  • 會看到結果如下

    Press ^C at any time to quit.
    package name: (workshop)
    version: (1.0.0)
    description:
    entry point: (index.js)
    test command:
    git repository:
    keywords:
    author:
    license: (ISC)
    About to write to ...\workshop\package.json:
    
    {
      "name": "workshop",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC"
    }
    
    
    Is this OK? (yes) yes
  • 上述指令執行完畢後,在workshop資料夾下會出現一個package.json檔,這個檔案非常重要,會記錄這個專案有使用到的所有套件

  • 接下來才可以安裝套件,使用Node.js最常用到的指令之三,npm install --save <套件名稱>,本範例要在指令模式下輸入以下指令,並按下Enter執行:

    npm install --save request
  • 上述指令執行完畢後,會將這個專案要使用request套件的資訊寫到package.json中。未來如果分享專案給其他開發者,其他的開發者只要在資料夾目錄下輸入以下指令,就可以自動把這個專案要使用的套件一次裝回來

    npm install
  • 再次執行node index.js,就會看到API回傳的資料了,雖然很亂難以閱讀...

第四步、處理資料

  • 現在來處理回傳的資料,從回呼函式回傳的body是字串,我們必須先將他轉為JSON格式的資料才能在Node.js中使用

  • 以下是處理資料的完整程式碼

    const request = require('request');
    
    var options = {
      method: 'GET',
      url: 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json'
    };
    request(options, function (err, response, body) {
      if (err) {
        console.error(err);
        return;
      }
      let data = JSON.parse(body);
      console.log(Array.isArray(data));
      data.forEach(function (item) {
        console.log(item.sna + " " + item.sbi);
      });
    });
  • 程式碼說明

    • 使用JSON.parse(字串),只要字串符合JSON格式的規定,就可以將字串轉為JSON物件,並存在變數data

      let data = JSON.parse(body);
    • 如果我們看看API回傳的資料,可以用Array.isArray(變數)檢查資料是不是陣列,如果我們去執行程式碼,就會發現變數data目前是陣列

      console.log(Array.isArray(data));
    • 陣列變數.foreach(function (陣列中一個一個的物件) {...}):這是Javascript中常見的迴圈使用方式

      data.forEach(function (item) {
        ...
      });
      • 也可以用傳統的for迴圈尋訪陣列

        for (i = 0; i < data.length; i++) {
          let item = data[i];
        }
    • 我們在前面有檢查過API回傳的資料,其中sna(場站中文名稱)、sbi(場站目前車輛數量)是很有用的資訊,我們可以把資訊列印在畫面上

      data.forEach(function (item) {
        console.log(item.sna + " " + item.sbi);
      });
  • 接下來,使用node index.js再次執行程式,就會得到以下結果

    YouBike2.0_捷運科技大樓站 0
    YouBike2.0_復興南路二段273號前 0
    YouBike2.0_國北教大實小東側門 0
    YouBike2.0_和平公園東側 0
    YouBike2.0_辛亥復興路口西北側 1
    YouBike2.0_復興南路二段280號前 0
    YouBike2.0_復興南路二段340巷口 0
    YouBike2.0_新生南路三段52號前 0
    YouBike2.0_新生南路三段66號前 0
    YouBike2.0_新生南路三段82號前 0
    YouBike2.0_羅斯福路三段333巷9號旁 0
    YouBike2.0_辛亥路一段30號前 0
    YouBike2.0_和平復興路口西北側 0
    YouBike2.0_羅斯福路三段311號前 0
    YouBike2.0_大安**中心停車場 1
    ...

挑戰一下

  • 只印出目前場站車輛數大於5台的場站要怎麼做?

補充資料

  • 其他關於Javascript操作物件和陣列的教學,見此

  • request是使用在Node.js之上的,如果你在寫網頁程式,就沒辦法使用了。但也有對應的套件可以使用,例如fetch

  • 使用request的POST的方式,以下是簡單的範例程式碼:

    const request = require('request');
    
    let postData = {
        key1: "value 1",
        key2: "value 2"
    }
    request.post('API位置', form: postData, function (err, httpResponse, body) {
        if (err) {
            console.error(err);
            return;
        }
        ...
    });

建立API

  • 我們也可以自己建立API伺服器
  • 這次我們使用到的套件叫做express

建立GET API

第一步、撰寫程式碼

  • 在目前的專案新增一個檔案,稱為server.js,程式碼如下:

    const express = require('express');
    const app = express();
    
    app.get('/', function (httpRequest, httpResponse) {
        let data = {
            message: "Hello World"
        };
        return httpResponse.json(data);
    });
    
    app.listen(8080, function (err) {
        if (err) {
            console.error(err)
        }
    });
  • 程式碼說明:

    • 首先我們先引用套件express

      const express = require('express');
    • 建立express實體

      const app = express();
    • express的架構是讓我們以URL的Path做為不同的API入口,不同入口設定不同的接收器 (回呼函式)負責處理

    • 格式為app.<http方法>(路徑, 回呼函式),所以本範例就是建立了一個接收根路徑,接受GET的方式的接收器

    • 回呼函式的資料有兩個參數,httpRequest包含著用戶來呼叫API時的資訊;httpResponse是負責將資料傳回給用戶使用的參數

      app.get('/', function (httpRequest, httpResponse) {
          ...
      });
    • 再來看看接收器(回呼函式)的內容,藉由httpResponse.json(資料),一律回傳JSON格式的資料。所以這裡會回傳的資料就是{"message": "Hello World"}

      app.get('/', function (httpRequest, httpResponse) {
          let data = {
              message: "Hello World"
          };
          return httpResponse.json(data);
      });
    • 再來看看讓API伺服器的啟動方式,app.listen(Port號)就可以啟動了。而範例中的寫法還加上了回呼函式,用來接收啟動是否成功。如果回呼函式的參數err有值,就表示啟動出現問題了

      app.listen(8080, function (err) {
          if (err) {
              console.error(err)
          }
      });
  • 接下來使用指令

    • npm install --save express:安裝express套件
    • node server.js:啟動伺服器

第二步、測試API與CORS問題

  • 打開hoppscotch,在網址列輸入http://127.0.0.1:8080/,並按下Send

  • 出現錯誤了,Could not send request

    CORS錯誤

  • 這是CORS的安全性問題,這裡有詳細的說明,簡單來說,Hoppscotch是網頁應用程式,網頁應用程式不能夠隨便呼叫別人家的API,必須API特別允許才能呼叫,所以我們的API Server必須得解決這個問題

  • 打開server.js,程式碼更改如下

    const express = require('express');
    const app = express();
    const cors = require('cors');
    
    app.use(cors());
    
    app.get('/', function (httpRequest, httpResponse) {
        let data = {
            message: "Hello World"
        };
        return httpResponse.json(data);
    });
    
    app.listen(8080, function (err) {
        if (err) {
            console.error(err)
        }
    });
  • 程式碼說明:

    • 這邊引入了專門用來解決CORS問題的套件

      const cors = require('cors');
    • 在express再加上這一行就可以解除CORS的問題了

      app.use(cors());
  • 接下來執行指令

    • npm install --save cors:使用npm安裝cors套件
    • node index.js:再次執行應用程式

第三步、再次測試API

建立可以接收參數的GET API

第一步、撰寫程式碼

  • 以下是完整的程式碼:

    const express = require('express');
    const app = express();
    const cors = require('cors');
    
    app.use(cors())
    
    app.get('/', function (httpRequest, httpResponse) {
        let data = {
            message: "Hello World"
        };
        return httpResponse.json(data);
    });
    
    app.get('/param', function (httpRequest, httpResponse) {
        let key1 = httpRequest.query.key1;
        let key2 = httpRequest.query.key2;
        let data = {
            message: key1 + " " + key2
        };
        return httpResponse.json(data);
    });
    
    app.listen(8080, function (err) {
        if (err) {
            console.error(err)
        }
    });
  • 程式碼說明:

    • 建立另一個路徑/param的接收器

      app.get('/param', function (httpRequest, httpResponse) {
          ...
      });
    • 使用httpReques.query.參數名可以取得傳入的參數,本次例子參數是key1=Hello&key2=World,是兩個參數分別為key1和key2,所以就可以用以下的程式碼取得

      let key1 = httpRequest.query.key1;
      let key2 = httpRequest.query.key2;
  • 使用指令node server.js啟動

第二步、測試API

整合範例

  • 建立一個API,整合第一個YouBike的範例,將這個API打造成能夠由帶到API的參數,回傳只大於參數值的場站列表

  • 這是用戶呼叫你建立的API,而你的API再去呼叫YouBike的API,再由你的API處理資料後,回傳必要的資訊

    範例圖示

  • API預定樣式http://localhost:8000/ubike?bike_num=3

    • 路徑:/ubike
    • 參數:bike_num
    • 方法:GET

第一步、撰寫程式碼

  • 這次建立一個新的檔案ubike_api.js

  • 以下是完整的程式碼:

    const express = require('express');
    const app = express();
    const request = require('request');
    const cors = require('cors');
    
    app.use(cors());
    
    app.get('/ubike', function (httpRequest, httpResponse) {
        let count = httpRequest.query.bike_num;
    
        let options = {
            url: 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json',
            method: 'GET'
        }
    
        request(options, function (err, response, body) {
            if (err) {
                console.error(err);
                return httpResponse.json({
                    'message': 'Error'
                });
            }
    
            let data = JSON.parse(body);
            let matchResult = [];
            data.forEach(function (item) {
                if (item.sbi > count) {
                    matchResult.push(item.sna);
                }
            });
    
            return httpResponse.json(matchResult);
        });
    });
    
    app.listen(8000, function (err) {
        if (err) {
            console.error(err);
        }
    });
  • 程式碼說明:

    • 首先這次的範例啟動在8000 Port

      app.listen(8000, function (err) {
          if (err) {
              console.error(err);
          }
      });
    • 接下來看向API,建立路徑/ubike的接收器

      app.get('/ubike', function (httpRequest, httpResponse) {
          ...
      });
    • 在接收器的回呼函式內,接收呼叫API時傳上的參數bike_num,將數值存在變數count內

      let count = httpRequest.query.bike_num;
    • 接下來建立request所要用來呼叫API的資訊 (這個如果忘記,記得回頭看呼叫API的章節)

      let options = {
          url: 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json',
          method: 'GET'
      }
    • 緊接著使用request來呼叫API,並處理錯誤發生時的狀況

      request(options, function (err, response, body) {
          if (err) {
              console.error(err);
              return httpResponse.json({
                  'message': 'Error'
              });
          }
          ...
      });
    • 再來處理正常的情況,將回傳的資料轉換成物件/陣列

      request(options, function (err, response, body) {
          ...
          let data = JSON.parse(body);
          ...
      });
    • 接下來執行data的迴圈,從迴圈值中找到sbi(場站目前車輛數量)大於變數count的物件,並將符合條件的物件的sna(場站中文名稱)使用Javascript的陣列操作方法push()存在matchResult中

    request(options, function (err, response, body) {
        ...
        let matchResult = [];
        data.forEach(function (item) {
            if (item.sbi > count) {
                matchResult.push(item.sna);
            }
        });
        ...
    });
    • 最後,使用httpResponse.json()回傳資料給用戶

      request(options, function (err, response, body) {
          ...
          return httpResponse.json(matchResult);
      });
  • 接下來使用指令node ubike_api.js啟動

第二步、測試API

總結

  • 為什麼要使用API?
  • Node.js常用的相關指令
  • request套件的使用方式
  • express套件的使用方式
  • CORS的問題及解決方法
  • 兩者如何整合