Try   HackMD

後端介接前端 Backend with FastAPI - Through the Galaxy

tags: backend

Copyright 2021, 月下麒麟


Target

浩瀚無垠的宇宙,人類用盡許多方法向這廣袤的星系呼喊
嘗試了一百萬年後,終於找到了規則與方法
接收到了外星人的回應

今天要講述的是後端該如何接收前端丟過來的資料

Concept

  • 前端要建立一個POST的請求(with jQuery and AJAX)
  • 後端要建立一個Route去接收前端的請求

補充:
可以了解前端如何發送請求,以及技術工具的演進
reference:什麼是 Ajax? 搞懂非同步請求 (Asynchronous request)概念
reference:[JS] AJAX 筆記

筆者是一個在學技術前,會想先知道這個工具的前後歷史
知道了它的演替後,再爬技術文章閱讀時,才能知道該作者用了哪一套工具、語法
因前端工具與框架繁多,特別在學前端時,更有這個感觸
瞭解了前因後果,才能對網路上或是書本上的資訊分門別類
最後再篩選出自己要的資訊,並加以學習實作

文章會以漸進式的方式敘述,順便記錄下我的Try & Error


(引用網路圖片)
很喜歡排球少年-稻荷崎的應援橫幅的標語 昨日無須追憶

Method

Part1

先來個基礎原型
frontend

var arr = {id:12 , name:"TT", height:178.5, weight:69.2, habbit:"router"} $.ajax({ url:"http://localhost:8000/index", type: "POST", dataType: "json", data: JSON.stringify(arr), success: function(res){ console.log(res); }, error: function(error){ console.log(error) } });

這樣的寫法比較沒什麼懸念,唯一要注意的是我有把資料轉成JSON格式(JSON.stringify)

backend
如下程式是可以運作的

@app.post("/index",) async def test(res:Request): print(res.method) # for uvicorn server res = await res.body() print(res) # for uvicorn server return res # for frontend web console log
  • 函數參數位置,res是變數Request是fastapi的請求模組Using the Request Directly
    是個封裝後非常有趣的模組,試想,fastapi在背後做了多少的動作,才完成這個請求!

  • 一樣的程式,拿掉asyncawait,就會報錯
    從uvicorn server端觀察


    可以發現POST字眼,程式第3行有執行,後面就報錯了

從react web console log觀察

這邊花了一些時間釐清
接著把return的內容改成"T.T",沒有了async、await、return一個str

@app.post("/index",)
async def test(res:Request):
    print(res.method)        
    res = await res.body()
    print(res)               
    return "T.T"

會得到一個warning,關於RuntimeWarning


lib\site-packages\anyio\_backends\_asyncio.py:743: RuntimeWarning: coroutine 'Request.body' was never awaited
原來就是要加入async、await,讓請求的機制完成

web console log上看到的資料

在做前後端介接時,需要反覆trace error,查看兩邊的terminal,才能查到問題


Part2

目標

我想讓上圖的結果變成下圖

簡言之,把後端return出來的內容可以變成Javascript的物件(object)

frontend

var arr = {id:12 , name:"TT", height:178.5, weight:69.2, habbit:"router"} $.ajax({ url:"http://localhost:8000/index", type: "POST", headers:{"Access-Control-Allow-Origin":"*", "Access-Control-Allow-Methods":"*", "Access-Control-Allow-Headers":"*", "Access-Control-Allow-Credentials":"true", "Content-Type":"application/json"}, dataType: "json", data: JSON.stringify(arr),//body success: function(res){ console.log(res); }, error: function(error){ console.log(error) } });

backend-1

from pydantic import BaseModel class PeopleBase(BaseModel): id: int name: str height: float weight: float habbit: str

backend-2

@app.post("/index",response_model=PeopleBase) async def test(res:PeopleBase): print(res) return res

部分程式省略,建議可看後端學習紀錄 Backend with FastAPI - Website with Departure

  • 眼尖的你可能有注意到,在backend-2 @app.post的路由後面還有加入response_model=PeopleBase
    其實它是用來做資料格式檢查用的,為fastapi的pydantic
    這樣就能讓return後的結果為JSON格式的物件

reference:pydanticJust Modern Python

  • 眼尖的你,也發現到這次我們加了許多header進去
    至於前端若以上一個範例去操作,會得到一個錯誤訊息

提示我們,這樣的格式是有問題的
這邊筆者算是嘗試出來的,尚未對這塊的原理做理解


Part3

最後,我們都能成功取得資料,也把格式都轉對了
當然,後端接收到資料,就是要將資料寫進去資料庫啊~

backend

# main.py @app.post("/index") async def method_Create(request: UserUpdate, db:Session= Depends(get_db)): try: res = await crud.create_user(db,request) return res except Exception as err: return HTTPException(**err.__dict__)
# schemas.py from typing import List from pydantic import BaseModel class PeopleBase(BaseModel): id: int name: str height: float weight: float habbit: str class PeopleUpdate(PeopleBase): class Config: orm_mode = True

這邊就不著墨各個模組細節
其大意為用UserBase為取得前端資料來源,建立具有DB空欄位的物件
接著,await crud裡的函式create_user去建立我們設定的資料

希望今天閱讀到這裡的你也會有收穫!