API 簡介與實戰練習


Posted by saffran on 2021-02-06

API 是什麼?為什麼需要它?

API 全名是 Application Programming Interface(應用程式介面)

最重要的就是這個 Interface(介面)
透過這個介面來跟別人溝通

例如:
USB 是一個介面

  • 對電腦來說 ->
    電腦只需要根據 USB 這個介面,去實作出「怎麼拿資料、怎麼傳資料」就可以了
  • 對「製作 USB 隨身碟的廠商」來說 ->
    廠商也只需要根據 USB 這個介面,去實作出「怎麼拿資料、怎麼存資料」就可以了

中間是透過 USB 這個介面來溝通的

使用 API

當我跟某人要東西時,有時沒辦法直接跟他要,就必須透過他提供的 API 來跟他要東西

提供 API

假設我是提供資料的人,我不希望別人都可以直接來存取我的資料庫(我不會直接把資料庫權限給別人),所以我需要提供一個 API,讓別人透過這個介面來存取資料

在這個介面上,我可以定義出「哪些東西可以給、哪些東西不能給」

透過 API,可以讓雙方交換資料

以下舉幾個例子:

作業系統提供了許多 API,我們必須要使用這些 API 才能達成我們想做的事情

我要怎麼知道我電腦目前的網路狀況?

作業系統有提供一個 API,來讓我知道網路狀況
我可以寫程式呼叫這個 API,來知道目前的網路狀況

我要怎麼讀取檔案?

讀取檔案一樣要透過作業系統,都是透過作業系統提供的 API 去讀取檔案(例如 JS 檔案、css 檔案等等)

我要怎麼拿到 Facebook 的好友資料?

我必須去串接 Facebook 的 API,才能拿到上面的好友資料

別人要怎麼拿到我網站上的資訊呢?

我需要提供 API 給別人,別人就可以透過 API 拿到我網站上的資訊

別人如何在我網站上新增資料呢?

我提供給別人「新增資料的 API」,別人就可以透過 API 在我網站上新增資料

API 與 Web API

API 跟 Web API 有什麼不同呢?

API

API 的提供者可以來自世界各地、四面八方,不一定要透過網路才能提供 API

例如:作業系統不用透過網路,也可以提供給我「讀取檔案、新增檔案」的 API

Web API

Web API 顧名思義就是會有「網路」

通常我們提到的 Web API 講的都會是 HTTP API,也就是「透過 HTTP 協定的形式所提供的 API」,透過 HTTP 這個協定來交換資料(透過 request 要資料,拿到的 response 就是我要的資料了)

HTTP API 例如:

Facebook 提供的 Graph API

twitter 提供的 GET statuses/home_timeline

裡面有提供一個 Resource URL https://api.twitter.com/1.1/statuses/home_timeline.json
只要呼叫這個 api 網址(也可以傳入一些參數)

request 就會長這樣(就是一個 HTTP 的 request)

GET https://api.twitter.com/1.1/statuses/home_timeline.json

歐付寶提供的 SDK 元件

SDK (software development kit)

可以把 SDK 想成是一個 library,裡面幫我做好了很多個 API

以上面 twitter 的例子來說,因為沒有 SDK,所以我需要自己呼叫 https://api.twitter.com/1.1/statuses/home_timeline.json 這個 api 網址

但如果 twitter 有提供 SDK 的話,我就只需要呼叫 twitter.getTimeline() 這個 function 就可以了(因為 SDK 已經幫我包裝好了)

Lorem Picsum 提供的「拿取假圖的 API」

我只要發送 request 到 https://picsum.photos/200/300 這個網址,得到的 response 就會是一張圖片

串接 HTTP API 實戰

這裡要使用的 API 是 Reqres
它有提供一系列讓我測試用的 API

用 Node.js 發送 request

「透過 Node.js 從“我的電腦”發送 request」跟「透過瀏覽器發送 request」是兩件完全不同的事情

用之前講過的 request 套件 來改:

request 套件只能在 Node.js 上面執行,無法在瀏覽器執行

底下這個預設的模板,沒有地方讓我寫 HTTP method,但是預設就會使用 GET
因此,這段的意思就是:
呼叫這個 request() 函式,就會幫我發送一個 GET request 到 https://reqres.in/api/users 這個網址去,然後把 response Body 印出來

request() 函式裡面的是一個 callback function,我用這個 callback function 來看到回傳的內容

const request = require('request');
request(
  'https://reqres.in/api/users', 
  function (error, response, body) {
    console.log(body);
  }
);

request() 函式裡面,描述「這個 request 所帶的 Header」會帶有這些資訊:
request 這個套件就會幫我發送「帶有這些資訊的 request」出去

method: GET
URL: https://reqres.in/api/users

執行 node index.js,就可以看到 https://reqres.in/api/users 這個 API 所返回的資料(response Body)


根據這個 API 的格式,只要在 API 網址後面加上 /2 就可以拿到「ID = 2」這個 user 的資料

const request = require('request');
request(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    console.log(body);
  }
);

process.argv

現在,我要做的功能是:
使用的 API 網址是 https://reqres.in/api/users ,但是當我輸入指令 node index.js 6 就可以拿到「ID = 6」這個 user 的資料

作法如下:
Node.js 有提供一個內建的 module 叫做 process

先把 process 引入進來
接著印出 process.argv

  • argv 的全名是 argument variables,也就是「參數」
const request = require('request');
const process = require('process');

console.log(process.argv)

request(
  'https://reqres.in/api/users', 
  function (error, response, body) {
    console.log(body);
  }
);

執行 node index.js 6,可以看到 console.log(process.argv) 印出的結果是一個 array:

  • array 的第一個元素 '/usr/local/bin/node' 就是剛剛輸入的指令 node index.js 6 裡面的 node(指令的第一個參數)
  • array 的第二個元素 '/Users/saffran/Desktop/test/index.js' 就是剛剛輸入的指令 node index.js 6 裡面的 index.js(指令的第二個參數)
  • array 的第三個元素 '6' 就是剛剛輸入的指令 node index.js 6 裡面的 6(指令的第三個參數),也就是「我要的 ID」
    [ '/usr/local/bin/node', '/Users/saffran/Desktop/test/index.js', '6' ]
    

因此,程式碼就可以改成這樣:

在 API 網址後面加上 process.argv[2] 來取得 array 的第三個元素(指令的第三個參數)

const request = require('request');
const process = require('process');

request(
  'https://reqres.in/api/users/' + process.argv[2], 
  function (error, response, body) {
    console.log(body);
  }
);

這時,當我執行 node index.js 6 時,就可以拿到「ID = 6」這個 user 的資料了

注意!在API 網址後面要先寫好 /,因為 process.argv[2] 只會取得 6,而不是 /6

用 post 新增 user

參考文件 request-Forms

const request = require('request');
const process = require('process');

request.post(
  { 
    url: 'https://reqres.in/api/users', 
    form: { 
      name: 'Harry',
      job: 'wizard' 
    } 
  }, 
  function (error, response, body) {
    console.log(body);
  }
);

執行 node index.js 就可以看到我剛剛新增的資料了

DELETE

const request = require('request');

request.delete(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    const json = JSON.parse(body); // 小括號裡面一定要是一個「JSON 格式的字串」才行
    console.log(json);
  }
);

執行 node index.js,卻出現了錯誤:Unexpected end of JSON input

這是為什麼呢?

先把 body 印出來看看:

const request = require('request');

request.delete(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    console.log('body: ', body) // 先把 body 印出來看看
    const json = JSON.parse(body); // 小括號裡面一定要是一個「JSON 格式的字串」才行
    console.log(json);
  }
);

執行 node index.js,會看到:body 印出來是空的,因為 body 並不是一個 JSON 格式的字串,因此下一行的 JSON.parse(body) 就會出現錯誤「Unexpected end of JSON input」

再把 status code 印出來看看:

const request = require('request');

request.delete(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    console.log(response.statusCode)
  }
);

執行 node index.js,會看到:204,代表「成功刪除,但 body 沒有內容」,這是很正常的,因為我是使用 DELETE,刪除完之後也沒什麼資料需要回給我

PATCH

印出 status code 和 body 看看

const request = require('request');

request.patch(
  { 
    url: 'https://reqres.in/api/users/2', 
    form: { 
      name: 'Romeo'
    }
  }, 
  function (error, response, body) {
    console.log(response.statusCode)
    console.log(body)
  }
);

output:
回傳 200 代表修改成功了

200
{"name":"Romeo","updatedAt":"2020-07-24T10:02:26.286Z"}

Custom HTTP Headers

request 套件也可以客製化 HTTP Headers

範例程式碼:
這裡所帶的 'User-Agent': 'request' 只是為了要示範「可以客製化 HTTP Headers」這件事,因此要帶什麼 Headers 可以自己決定

headers 這個 key 裡面,對應到的 value 就是我想要帶的 Headers

const request = require('request');

const options = {
  url: 'https://api.github.com/repos/request/request',
  headers: {
    'User-Agent': 'request'
  }
};

function callback(error, response, body) {
  if (!error && response.statusCode == 200) {
    const info = JSON.parse(body);
    console.log(info.stargazers_count + " Stars");
    console.log(info.forks_count + " Forks");
  }
}

request(options, callback);

上面的程式碼,如果把 optionscallback 都直接放到 request() 函式裡面,就會是這樣:

const request = require('request');

request({
  url: 'https://api.github.com/repos/request/request',
  headers: {
    'User-Agent': 'request'
  }
}, 
function (error, response, body) {
  if (!error && response.statusCode == 200) {
    const info = JSON.parse(body);
    console.log(info.stargazers_count + " Stars");
    console.log(info.forks_count + " Forks");
  }
});

User-Agent (user 的代理人)通常指的就是「瀏覽器」,也就是「幫我送 request 的是哪個程式」,因為瀏覽器會代理 user 去發送 request
User-Agent 是比較文言文的講法)

例如:
開啟這個網址 https://lidemy-book-store.herokuapp.com/books?_limit=5 打開 devtool 的 Network tab,在 Request Headers 可以看到 User-Agent 會寫一些跟瀏覽器相關的資訊:

  • Chrome/84.0.4147.89 就是我現在的 Chrome 版本
    server 就會知道「這個 user 目前是使用 Chrome/84.0.4147.89 幫他送 request 過來」
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
    

資料格式的選擇

關於資料結構,在傳送、接收資料時,資料都會有一定的格式
兩種最常用的資料格式,就是:

  • XML
  • JSON

XML

XML 的全名是 Extensible Markup Language,跟 HTML 一樣都是一種標記語言

用標籤的形式來表示資料

JSON

JSON 的全名是 JavaScript Object Notation,是一種資料格式

JSON 會如此熱門的原因:

  • JSON 是基於 JavaScript 的「物件」所產生的一種資料格式,因此跟 JavaScript 的相容性非常好
  • JSON 所佔的空間比 XML 少很多
    (XML 要使用很多標籤,光是標籤就佔了很多空間)

注意!JSON 只是長得像物件,但其實是「字串」

JSON.parse() 把「JSON 格式的字串」轉成物件

這裡再用前面講到的 Reqres API 為例

const request = require('request');

request(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    console.log(body);
  }
);

執行 node index.js 會印出:

{"data":{"id":2,"email":"janet.weaver@reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"},"ad":{"company":"StatusCode Weekly","url":"http://statuscode.org/","text":"A weekly newsletter focusing on software development, infrastructure, the server, performance, and the stack end of things."}}

console.log(body) 看起來是一個「JavaScript 的物件」,但其實 印出的是一個「字串」(一個 JSON 格式的字串)

因此,要先把字串轉成物件,才能用「物件的方式」存取到裡面的值:
可以用一個 JS 的函式叫做 JSON.parse(),來把「JSON 格式的字串」轉成「JavaScript 的物件」

注意!JSON.parse() 的小括號裡面一定要是一個「JSON 格式的字串」才行

const request = require('request');

request(
  'https://reqres.in/api/users/2', 
  function (error, response, body) {
    const json = JSON.parse(body); // 小括號裡面一定要是一個「JSON 格式的字串」才行
    console.log(json);
  }
);

這時,印出的 console.log(json) 就會是「JavaScript 的物件」了

{
  data: {
    id: 2,
    email: 'janet.weaver@reqres.in',
    first_name: 'Janet',
    last_name: 'Weaver',
    avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg'
  },
  ad: {
    company: 'StatusCode Weekly',
    url: 'http://statuscode.org/',
    text: 'A weekly newsletter focusing on software development, infrastructure, the server, performance, and the stack end of things.'
  }
}

因此,我就可以使用 console.log(json.data.first_name) 印出 Janet 了!

JSON.stringify() 把物件轉成「JSON 格式的字串」

const obj = {
  name: 'Harry',
  job: 'wizard'
}

console.log(obj)

這時,印出的 obj 就是一個「JavaScript 的物件」:

{ name: 'Harry', job: 'wizard' }

如果我想要把這個「JavaScript 的物件」轉成「JSON 格式的字串」,就可以使用一個函式叫做 JSON.stringify()

const obj = {
  name: 'Harry',
  job: 'wizard'
}

console.log(JSON.stringify(obj))

這時,印出的 JSON.stringify(obj) 就是一個「JSON 格式的字串」:

{"name":"Harry","job":"wizard"}

#Network







Related Posts

21. State

21. State

模組化 (Module):require 和 export

模組化 (Module):require 和 export

菜逼八寫Flutter(3) - 列表、圖片Widget

菜逼八寫Flutter(3) - 列表、圖片Widget


Comments