iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

上一篇我們認識了 Swagger,也實際用 Swagger 測試了:

GET /tasks

按下 Execute 之後,Swagger 幫我們發出 GET Request,最後顯示 API 回傳的資料。

不過目前為止,我們做的都還是「取得資料」。

如果今天是要新增一筆作業,只說「我要新增作業」是不夠的,還要告訴後端:

要新增的作業叫什麼名字?完成了沒有?

也就是說,這次的 Request 需要帶著資料一起送出去。

那這些資料要放在 Request 的哪裡?後端又要怎麼拿到?

今天就來看看 Request Body 和 api.payload。

資料可以放在 Request 的哪裡?

在 Day 3 我們有提到,Request 不是單純「傳資料」,而是向後端提出一個請求,而這個請求裡面可以帶著需要處理的資料。

常見放資料的位置有幾種,今天先認識兩個:

網址上(Query String)
例如:/tasks?completed=true
資料會直接出現在網址裡,常用在 GET 這種「查詢、篩選」的情況。

Request Body(請求主體)
資料放在 Request 的內容裡,不會出現在網址上。
新增、修改資料時(POST、PUT)通常會用這種方式。

今天我們要做的是「新增一筆作業」,所以會使用 Request Body。

Request Body 長什麼樣子?

我們之前在 Day 9 認識了 JSON,也知道 Web API 很常使用 JSON 來交換資料。

其實不只是後端回傳的 Response 可以是 JSON,前端送出的 Request Body 也可以是 JSON。

一個新增作業的 Request,大概可以想成這樣:

POST /tasks
Content-Type: application/json

{
  "name": "寫 Day 13",
  "completed": false
}

拆開來看:

POST /tasks → 我想要對作業這個資源做「新增」
Content-Type: application/json → 我這次帶的資料是 JSON 格式
{ … } → 這就是 Request Body,裡面是要新增的資料

Content-Type 可以先理解成「我這次傳的資料是什麼格式」,讓後端知道要怎麼讀這包資料。

後端要怎麼拿到 Request Body?

如果是只用 Flask,我們會這樣拿:

from flask import request
data = request.get_json()

而我們現在使用的是 Flask-RESTX,它提供了一個更短的寫法:

api.payload

payload 可以理解成「這次 Request 帶進來的資料」。

api.payload 其實就是 Flask-RESTX 幫我們處理好的 request.get_json(),拿到的結果會是一個 Python 字典。

這裡可以跟 Day 9 一起對照著看:

進來的時候:JSON → Python 字典(api.payload)
出去的時候:Python 字典 → JSON(Flask 幫我們轉)

先寫一個最小版本

先不要想太多,我們讓 POST /tasks 把收到的資料原封不動回傳回來,確認我們真的拿到了資料。
app.py

from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app, title='Task API', version='1.0')

@api.route('/tasks')
class Tasks(Resource):
    def get(self):
        return {
            "name": "Flask",
            "completed": False
        }

    def post(self):
        data = api.payload
        return {"你傳進來的資料": data}, 201

if __name__ == '__main__':
    app.run(debug=True)

這裡有兩個新東西:

第一個是 def post(self)
Day 11 有提到,Flask-RESTX 會用 method 的名字去對應 HTTP Method。
所以 /tasks 收到 GET Request 時執行 get(),收到 POST Request 時就會執行 post()。

第二個是 return 後面多了一個 201

return {“你傳進來的資料”: data}, 201

201 是 HTTP 狀態碼(Status Code),用來表示這次請求的結果。

常見的像是:

200 → OK,成功
201 → Created,成功而且建立了新資料
404 → Not Found,找不到

因為 POST 是新增資料,所以通常會回 201 而不是 200。
狀態碼在 Day 24 還會再講得更完整,今天先知道它是在表示「這次請求的結果」。

Swagger 上怎麼填資料?

存檔後重新啟動:

python app.py

打開:
http://127.0.0.1:5000/

這時候 Swagger 上會多出一個:
POST /tasks
https://ithelp.ithome.com.tw/upload/images/20260927/20183865cfczpZTXYG.png

Swagger 首頁,/tasks 下面同時有 GET 和 POST 兩個 Method

但是點開 POST /tasks 之後會發現一件事:

它只有一顆 Try it out,沒有地方可以讓我們填要傳的 JSON。

因為我們還沒有告訴 Flask-RESTX:這個 API 預期會收到什麼樣的資料。

所以就算程式裡面有 api.payload,Swagger 也不知道要幫我們準備輸入框。

這時候就需要 api.model 和 @api.expect。

api.model:描述資料長什麼樣子

api.model 是用來描述「一筆資料有哪些欄位、每個欄位是什麼型別」。

先在 import 的地方多加一個 fields:

from flask_restx import Api, Resource, fields

然後建立 model:

task_model = api.model('Task', {
    'name': fields.String(required=True, description='作業名稱', example='寫 Day 13'),
    'completed': fields.Boolean(required=True, description='是否完成', example=False),
})

拆開來看:

api.model('Task', {...})

第一個參數 ‘Task’ 是這個資料模型在 Swagger 文件上顯示的名字。

‘name’: fields.String(…)
表示有一個欄位叫 name,型別是字串。

‘completed’: fields.Boolean(…)
表示有一個欄位叫 completed,型別是布林值(True / False)。

required=True → 這個欄位是必填
description → 這個欄位的說明,會顯示在 Swagger 上
example → Swagger 上會用它當範例值,測試的時候很方便

@api.expect:告訴 API 預期收到什麼

有了 model 之後,再把它掛到 post() 上面:

@api.route('/tasks')
class Tasks(Resource):
    @api.expect(task_model)
    def post(self):
        data = api.payload
        return {"你傳進來的資料": data}, 201

@api.expect(task_model)

可以理解成:「這個 API 預期收到的 Request Body,長得像 task_model 這樣。」

完整的 app.py:

from flask import Flask
from flask_restx import Api, Resource, fields

app = Flask(__name__)
api = Api(app, title='Task API', version='1.0')

task_model = api.model('Task', {
    'name': fields.String(required=True, description='作業名稱', example='寫 Day 13'),
    'completed': fields.Boolean(required=True, description='是否完成', example=False),
})

@api.route('/tasks')
class Tasks(Resource):
    def get(self):
        return {
            "name": "Flask",
            "completed": False
        }

    @api.expect(task_model)
    def post(self):
        data = api.payload
        return {"你傳進來的資料": data}, 201

if __name__ == '__main__':
    app.run(debug=True)

實際測試

重新啟動後打開 http://127.0.0.1:5000/
點開 POST /tasks,這次會發現多了一塊 Example Value,而且裡面已經幫我們填好範例:

{
  "name": "寫 Day 13",
  "completed": false
}

點開 POST /tasks 之後,右邊出現 Example Value / Model 的畫面
https://ithelp.ithome.com.tw/upload/images/20260927/201838652SHXD3ud9y.png

按下 Try it out,就可以直接修改裡面的 JSON,例如改成:

{
  "name": "洗碗",
  "completed": false
}

再按 Execute。
https://ithelp.ithome.com.tw/upload/images/20260927/20183865vWHOqTW8sS.png
Try it out 之後可以編輯 JSON 的輸入框

Swagger 會顯示 Response:

{
  "你傳進來的資料": {
    "name": "洗碗",
    "completed": false
  }
}

而且 Code 那一欄會是 201。
https://ithelp.ithome.com.tw/upload/images/20260927/201838655TeqdOLnc0.png
Execute 之後的 Response,可以看到 201 和回傳的資料

整個流程:

Swagger 填好 JSON 並按下 Execute
       ↓
發出 POST Request 到 /tasks,Request Body 帶著 JSON
       ↓
Flask-RESTX 找到 /tasks 對應的 Resource
       ↓
因為是 POST Request
       ↓
執行 post()
       ↓
api.payload 把 JSON 轉成 Python 字典
       ↓
回傳結果(Response)

一個很容易誤會的地方

@api.expect(task_model) 看起來很像「檢查」,但它預設其實不會擋。

也就是說,如果我們故意送一包這樣的資料:

{
  "abc": 123
}

它還是會成功執行,然後回傳:

{
  "你傳進來的資料": {
    "abc": 123
  }
}

故意傳錯誤的欄位,結果還是 201 的畫面
https://ithelp.ithome.com.tw/upload/images/20260927/20183865xP25O4Y87R.png

因為 @api.expect 預設只負責「文件上的說明」,讓 Swagger 知道要顯示什麼輸入框。

如果要真的擋下來,需要寫成:

@api.expect(task_model, validate=True)

加上 validate=True 之後,少填必填欄位就會直接回 400。
這部分屬於 Input Validation,Day 25 會專門再講,今天先知道有這件事就好,不然很容易以為「我明明有寫 model,為什麼亂傳也會過」。

今天我們讓 API 從「只會回傳資料」變成「可以接收資料」。

Request Body:Request 帶資料進來的地方,Web API 常用 JSON
api.payload:Flask-RESTX 幫我們把 Request Body 的 JSON 轉成 Python 字典
api.model:描述資料有哪些欄位、什麼型別
@api.expect:告訴 Flask-RESTX 和 Swagger,這個 API 預期收到什麼

不過現在有一個問題:
我們雖然收到了資料,但只是把它原封不動回傳回去,並沒有真的把它存起來。
所以現在新增完,再用 GET /tasks 去查,也查不到剛剛新增的東西。

下一篇就來把資料真的存起來,完成 CRUD 的前兩個:新增(POST)和查詢(GET)。


上一篇
Day 12|Swagger API 文件
下一篇
Day 14|CRUD:新增與查詢
系列文
從零開始的後端開發:用 Flask 實作 REST API,搞懂 API 與資料庫之間如何協作 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言