# 後端介接前端 Backend with FastAPI - *Through the Galaxy* ###### tags: `backend` Copyright 2021, [月下麒麟](https://hackmd.io/@YMont/note-catalog) --- ## Target >浩瀚無垠的宇宙,人類用盡許多方法向這廣袤的星系呼喊 >嘗試了一百萬年後,終於找到了規則與方法 >接收到了外星人的回應... ==今天要講述的是後端該如何接收前端丟過來的資料== ## Concept * 前端要建立一個**POST**的請求(with jQuery and AJAX) * 後端要建立一個**Route**去接收前端的請求 補充: 可以了解前端如何發送請求,以及技術工具的演進 reference:[什麼是 Ajax? 搞懂非同步請求 (Asynchronous request)概念](https://tw.alphacamp.co/blog/ajax-asynchronous-request) reference:[[JS] AJAX 筆記](https://medium.com/%E9%A6%AC%E6%A0%BC%E8%95%BE%E7%89%B9%E7%9A%84%E5%86%92%E9%9A%AA%E8%80%85%E6%97%A5%E8%AA%8C/js-ajax-%E7%AD%86%E8%A8%98-b9a57976fa60) 筆者是一個在學技術前,會想先知道這個工具的前後歷史 知道了它的演替後,再爬技術文章閱讀時,才能知道該作者用了哪一套工具、語法 因前端工具與框架繁多,特別在學前端時,更有這個感觸 瞭解了前因後果,才能對網路上或是書本上的資訊分門別類 最後再**篩選**出自己要的資訊,並加以學習**實作** ==文章會以漸進式的方式敘述,順便記錄下我的Try & Error== ![](https://i.imgur.com/QTAxzLF.png) (引用網路圖片) 很喜歡排球少年-稻荷崎的應援橫幅的標語 **昨日無須追憶** ## Method ### Part1 先來個基礎原型 frontend ```javascript= 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 如下程式是可以運作的 ```python= @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](https://fastapi.tiangolo.com/advanced/using-request-directly/) 是個封裝後非常有趣的模組,試想,fastapi在背後做了多少的動作,才完成這個請求! * 一樣的程式,拿掉**async**、**await**,就會報錯 從uvicorn server端觀察 ![](https://i.imgur.com/l7PbUpQ.png) 可以發現POST字眼,程式第3行有執行,後面就報錯了 從react web console log觀察 ![](https://i.imgur.com/yRADylq.png) 這邊花了一些時間釐清 接著把return的內容改成"T.T",沒有了async、await、return一個str ```python @app.post("/index",) async def test(res:Request): print(res.method) res = await res.body() print(res) return "T.T" ``` 會得到一個warning,關於**RuntimeWarning** ![](https://i.imgur.com/Pr1FDYu.png) ```lib\site-packages\anyio\_backends\_asyncio.py:743: RuntimeWarning: coroutine 'Request.body' was never awaited``` 原來就是要加入async、await,讓請求的機制完成 web console log上看到的資料 ![](https://i.imgur.com/q1Xl1FV.png) ==在做前後端介接時,需要反覆trace error,查看兩邊的terminal,才能查到問題== --- ### Part2 **目標** ![](https://i.imgur.com/RBwHr9Z.png) **我想讓上圖的結果變成下圖** ![](https://i.imgur.com/maUFu5B.png) >簡言之,把後端return出來的內容可以變成Javascript的物件(object) frontend ```javascript= 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 ```python= from pydantic import BaseModel class PeopleBase(BaseModel): id: int name: str height: float weight: float habbit: str ``` backend-2 ```python= @app.post("/index",response_model=PeopleBase) async def test(res:PeopleBase): print(res) return res ``` 部分程式省略,建議可看[後端學習紀錄 Backend with FastAPI - Website with Departure](https://hackmd.io/@YMont/python-fastapi-2) * 眼尖的你可能有注意到,在backend-2 \@app.post的路由後面還有加入**response_model=PeopleBase** 其實它是用來做資料格式檢查用的,為fastapi的**pydantic** 這樣就能讓return後的結果為JSON格式的物件 reference:[pydantic](https://pydantic-docs.helpmanual.io/)、[Just Modern Python](https://fastapi.tiangolo.com/features/#just-modern-python) * 眼尖的你,也發現到這次我們加了許多**header**進去 至於前端若以上一個範例去操作,會得到一個錯誤訊息 ![](https://i.imgur.com/LO0JgIQ.png) 提示我們,這樣的格式是有問題的 這邊筆者算是嘗試出來的,尚未對這塊的原理做理解 --- ### Part3 最後,我們都能成功取得資料,也把格式都轉對了 當然,後端接收到資料,就是要**將資料寫進去資料庫**啊~ backend ```python= # 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__) ``` ```python= # 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去建立我們設定的資料 希望今天閱讀到這裡的你也會有收穫!