React 結合 python API — 寫一個行事曆 App!使用 fastAPI + google sheet 當資料庫。

CRUD — 寫一個個人行事曆,前端用 React,後端用 python 的 fastAPI,資料庫使用 google sheet,並最後用 heroku 來佈署 python 檔。

Molly M
16 min readMar 22, 2022

✔ 前言

好無聊的話來寫一個個人行事曆吧(・ε・`)(?
本篇將結合前後端並佈署,實做一個能用的行事曆 App!(好像還沒看過有人這樣寫 CRUD 的教學文,恭喜終於不用再看 ToDo List )

將會學到

  • 使用 React Calendar
  • 使用 python fast API
  • 使用 google sheet 當資料庫
  • 佈署後端 heroku
  • 佈署前端 netlify

開發環境

  • ubuntu 20.04
  • python 3.8.10
  • node 16.13.2

✔ 目錄

▶ 前製作業 - github
▶ 開發 - 後端 python / google sheet
▶ 開發 - 前端 React peoject
▶ 佈署 - 佈署後端
▶ 佈署 - 佈署前端
▶ 總結

✔ 前製作業

建立 repo!

  1. 先在 github 新增一個 repo
  2. clone 到本機電腦資料夾
  3. 並在檔案中新增 frontend 資料夾、backend 資料夾
  4. 將目前的檔案推上 github ,依序下指令
  • $ git add .
  • $ git commit -m ‘init’
  • $ git push

(上述步驟可能會因為權限問題而推送失敗,如果有遇到問題可以再討論討論)

初始化專案資料夾

  1. 終端機進入 frontend 資料夾下指令 $ npx create-react-app .
  2. 終端機進入 backend 建立一個檔案 main.py
  3. 這時候 CRA 應該也跑完了,我們將 src 資料夾中不需要的檔案/程式碼去掉,如下圖:

✔ 開發

後端 python / google sheet

先從後端 python fastAPI 連上 google sheet 開始(完全在複製自己之前寫過的 文章 XD )

  1. 申請 google sheet api >新增專案

2. 在該專案中的資訊主頁,點選上方的「啟用 API 和服務」

3. 於搜尋框輸入「Google Sheets API」> 點選啟用

4. 進入憑證頁面,然後點上方「建立憑證」

5. 選「服務帳戶」

6. 輸入帳戶的詳細資料後按建立(二跟三可以不填)

7. 完成後下面的服務帳戶會多一個電子郵件>點進去電子郵件>服務帳戶詳細資料>複製電子郵件地址

8. 點上方的標籤「金鑰」,選擇「建立新的金鑰」

9. 選擇 JSON 後按建立,存到專案的資料夾中

10. 新增一張 google sheet 表,共用人員貼上剛剛複製的電子郵件,選擇為編輯者>傳送!

這樣連接 google sheet 的部份就完成了。

接著進到 backend 資料夾中

  1. 建立虛擬環境
$ python3 -m venv env

2. 進入虛擬環境 env

$ source env/bin/activate

3. 安裝套件

$ pip install fastapi
$ pip install "uvicorn[standard]"
$ pip install gspread oauth2client
$ pip install pydantic

4. 新增檔案 .gitignore,並在裡面輸入虛擬環境的資料夾名稱

env

5. 進入 backend/main.py 中,貼上以下的 code

import gspread
from oauth2client.service_account import ServiceAccountCredentials as SAC
Json = "剛剛存進來的json金鑰檔名.json"
Url = ["https://spreadsheets.google.com/feeds"]
Connect = SAC.from_json_keyfile_name(Json, Url)
GoogleSheets = gspread.authorize(Connect)
Sheet = GoogleSheets.open_by_key("表單網址d/後面的KEY")
Sheets = Sheet.sheet1
datas = ["03/11", "15:00", "dinner", "taipei"]
// 先塞一筆假資料
Sheets.append_row(dataTitle)
Sheets.append_row(datas)
print("寫入成功")
print(Sheets.get_all_values())
  • 試著 run 看看
$ python main.py

成功!!

接著使用 fastAPI,把他變成一個可以呼叫的 URL

import gspread
from oauth2client.service_account import ServiceAccountCredentials as SAC
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
Json = "你的JSON檔名.json"
Url = ["https://spreadsheets.google.com/feeds"]
Connect = SAC.from_json_keyfile_name(Json, Url)
GoogleSheets = gspread.authorize(Connect)
Sheet = GoogleSheets.open_by_key("你的sheet KEY")
Sheets = Sheet.sheet1
# ----- fast api -----
app = FastAPI()
@app.get("/")
def getAllData():
return Sheets.get_all_records()
class Info(BaseModel):
id: int
data: list
@app.post("/addNewEvents")
def getInformation(info: Info):
Sheets.append_row(info.data)
return {"status": "SUCCESS", "data": info}
  • 啟動
$ uvicorn main:app --reload

在網址輸入 http://127.0.0.1:8000/,會看到我們之前存入 sheet 的資料

使用 fast API 提供的 doc 文件進行測試,也可以正常新增資料

後端的程式碼部份完成~

但!還不能推上 github,我們還需要將秘密檔(.json)跟 key 另外處理

  1. 新增一個 key.py
sheetKey = '你的KEY'

2. 在 .gitignore 中新增兩行

env
calendar.json
key.py

好了~ 可以先推一版上 github (・ิω・ิ)!

前端 React peoject

處理前端的部份,進到 frontend 資料夾

  1. src 底下,新增一個檔案PersonalCalendar.js
import React from 'react'const PersonalCalendar = () => {
return (
<div>PersonalCalendar</div>
)
}
export default PersonalCalendar

2. 在App.js 中 import

  • 看一下畫面正不正常
$ yarn start

然後…

我們試著 fetch 資料,會發現很多跨域的 error

Access to fetch at 'http://127.0.0.1:8000/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

終於找到解答,跟著下面的步驟來解決它!

先回到後端(backend) main.py中,把 call API 的 URL 加入設定,新增以下幾行:

from fastapi.middleware.cors import CORSMiddlewareorigins = [
"http://localhost",
"http://localhost:3000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ... 以下省略

接著回到 PersonalCalendar.js 中,進行 GET & POST 測試

終於成功(痛哭流涕(´• ω •`)ノ)

確定抓取資料沒問題後,就要開始將資料塞進行事曆中啦,先來測試套件的使用

  1. 在/frontend 中安裝套件
$ yarn install @fullcalendar/daygrid @fullcalendar/react

2. import 套件

import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import "@fullcalendar/daygrid/main.css";
import "@fullcalendar/list/main.css";

3. 使用(超簡單)

<FullCalendar
events={events} // 所有事件
plugins={[dayGridPlugin]} // 行事曆長相
height='500px' // 樣式 可以不填
eventClick={function (arg) {
alert(arg.event.title)
alert(arg.event.start)
}} // 點擊事件要觸發的function
/>
  • 可以複製以下的 code 跑跑看

有了樣式後,就可以把 API 取得的資料塞進去

  1. 先將 google sheet 的資料格式改為 FullCalendar 的格式

2. 使用 useState 管理變數

const [events, setEvents] = useState(initEvents)
// initEvents 為上面原本的 events

3. 新增 function

function getData() {
fetch(url, {
method: 'GET',
redirect: 'follow',
})
.then((response) => response.json())
.then((result) => {
console.log(result)
setEvents(result)
})
.catch((error) => console.log('error', error))
}

將江!資料成功顯示在行事曆上了(感動(´༎ຶД༎ຶ`)

最後一步,新增資料到 google sheet 中

  1. 安裝套件 uuid
$ yarn add uuid react-datetime moment

2. 新增 state

const [startTime, setStartTime] = useState('2022-03-19T17:30')
const [title, setTitle] = useState('')
const newEvents = {
id: uuidv4(),
data: [uuidv4(), startTime, title],
}

3. 新增樣式及 onChange function

<Datetime
onChange={(date) => {
setStartTime(date._d)
}}
/>
title
<input
type='text'
name='title'
value={title}
onChange={(e) => {
setTitle(e.target.value)
}}
/>

4. 丟資料

<button
onClick={() => {
postData(url, newEvents)
}}
>
OK
</button>

完整的 code

✔ 佈署

佈署後端

在後端 /backend 資料夾中

  1. 新增檔案 runtime.txt(指定 python 版本)
python-3.8.10

2. 新增檔案 Procfile(給 heroku 看的檔案)

web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000}

3. 將我們的 python API 佈署到 heroku 上面

3–1. 建立 heroku 帳號

3–2. Create new app

3–3. 接著在終端機,輸入以下指令,登入 heroku

$ heroku login

3–4. 點任一鍵登入(會跳出網頁按下 Login)

3-5. 在 heroku 的 deploy 頁面可以看到步驟,依序下指令

$ git init$ heroku git:remote -a xxx(你的app名稱)$ git add .$ git commit -am "make it better"$ git push heroku master

上述都沒問題的話就會開始佈署!(也可以在 heroku 頁面查看 log)

成功後我們會在 setting 的 Domains 中看到一串 URL

點開他會發現是 Error (果然太順利肯定有問題 (・ิω・ิ)

在終端機輸入 $ heroku logs -- tail,就會發現是因為我們將 keyjson 檔案隱藏了,Git 抓不到值導致的,所以把這兩個檔案從 .gitignore 移除是最快的方法。
但是!身為一個不專業工程濕,我們是不能把 key 隨便 push 到雲端,這時候就要借用 heroku 的 Config Vars 功能。

在 settings 頁面可以看到 Config Vars

  1. key.py 中的 sheetKey 輸入進去,並在我們的 backend 資料夾中新增 .env
sheetKey = 'xxx'

2. .gitignore 會調整為

3. 測試一下

  • 在終端機輸入 heruko local,會 run 在本機的 0.0.0.0:5000

ㄛ耶成功 (๑´ㅁ`)

  • 將異動的部份推上 heroku,依序輸入下面的指令
$ git add.
$ git commit -m 'init'
$ git push heroku master

再回到我們佈署的 URL,可以看到資料都跟我們 google sheet 中的一樣,佈署後端的部份就到這邊結束了。

接著就是來測試 POST 的功能,啟動我們的前端專案 yarn start

PersonalCalendar.js 中的 url 改為上面佈署的網址

let url = 'http://127.0.0.1:8000'
=>
let url = 'https://xxxxx.herokuapp.com'

測試一下 post 的功能

佈署前端

終於前端的部份也完成,剩下佈署前端的部份

  1. 在前端 /frontend 資料夾下 Build 指令
$ yarn build

2. 打開 https://app.netlify.com/ 把打包完成的 /build 整包拉進去

3. 等它跑完

4. 在 site detail 中可以修改 URL 的名稱

進去自己設定的網址,就可以正式使用它啦 (๑´ㅁ`)!

✔ 總結

這篇也是寫了好幾天,寫到一半都會覺得,偏實做的文章都沒什麼人要看,但跟著這篇做(應該或許可能…)可以學到很多東西,有任何問題也可以一起討論。

個人在處理 CORS 的部份研究了一下,資訊程式領域真的好深rrr,希望自己藉由寫文章能慢慢的持續前進 ٩(ˊᗜˋ )و!

--

--

Molly M

Molly — Software Developer / 職稱是軟體研發工程師。 什麼都寫,專精於 React, Reacat Native APP (ง•̀_•́)ง ❤ 合作發案討論疑難雜症請洽: momolly1024@gmail.com