สร้าง API ด้วย FastAPI เร็ว แรง ฟิ้ว !!!

Bozz Srinikorn
8 min readSep 8, 2020

--

เริ่มต้นเขียน API ด้วย Python และ FastAPI ฟิ้ว ฟิ้ว ๆ

FastAPI

ก่อนอื่น ขอเล่นมุกก่อน

FastAPI meme

เดาว่าหลายคนอาจจะเคยเห็นโพสนี้ผ่าน feed กันมาบ้างแล้ว

ประมาณว่า มีประกาศรับสมัคร Dev ที่มีประสบการณ์กับ FastAPI เกิน 4 ปี ซึ่งความเป็นจริง FastAPI เพิ่งมาประมาณปี 2019 เอง ทำให้แม้แต่คนที่เป็น Contributor หลักเอง ก็ยังมีประสบการณ์ไม่ถึง ทั้ง ๆ ที่ตัวเองเป็นสร้างเองนะ ฮ่า ๆ

FastAPI คืออะไร

คือ Framework สำหรับการสร้าง API เขียนด้วย Python โดยมี uvicorn เป็นตัว จัดการ run server และด้วยความที่ใช้ uvicorn ที่เป็น ASGI ทำให้รองรับการทำงานแบบ Asynchronous ไปโดยปริยาย

Install Package

ตัวอย่าง code สร้าง API เริ่มต้นง่าย ๆ

Example of API via FastAPI

Run server

วิธี run server ก็ง่าย ๆ จะใช้ __main__ หรือใช้ uvicorn command ก็ได้ รวมถึงถ้าใครเคยใช้ gunicorn มาก่อน FastAPI ก็รองรับนะ

Example FastAPI with Uvicorn
Run server via python execute command
Run server via Uvicorn Command

จะเห็นได้ว่าเป็นการสร้าง server ที่ Lightweight มาก ๆ และไม่ซับซ้อน แค่ Import เข้ามา เรียก class จากนั้นก็ลุยได้เลย ลักษณะจะคล้าย Flask ถ้าใครเคยเขียน Flask มา จะต้องชอบ FastAPI อย่างแน่นอน (มั้งนะ)

มาดู Feature ของ FastAPI บ้าง

ก่อนอื่นเลย เพื่อให้เห็นภาพมากขึ้น ผมจะ mockup project ขึ้นมา แล้วนำ Feature ของ FastAPI มาใช้ประกอบประมาณ 20% จะได้เห็น code ด้วยไปเลย

เราจะลอง mockup ทำ API สำหรับ movies store อารมณ์ประมาณเป็นร้านซื้อขาย viedeo ง่าย ๆ กัน

โดย code ที่เห็นต่อไปนี้จะเป็นเพียง sample เท่านั้นเพื่อให้เห็นภาพของ Feature มากขึ้น ส่วนใครอยากเห็นของเต็มแบบขนาดเอาไป deploy ใช้ได้เลย สามารถไปดูได้ที่ GitHub ของผมที่นี่

เพราะอีกบทความนึงจะพูดถึงการทำ CI/CD บน Cloud Build ของ Google Cloud เราจะนำ FastAPI ไป deploy แบบ Serverless ของ GCP คลิกอ่านที่นี่

1. HTTP Method ทั่วไป [Request and Response]

เรามาดูตัวอย่าง code สำหรับ method http ที่ใช้เป็นประจำในการทำ API รวมถึงวิธีรับ Request ไม่ว่าจะเป็น Header, Cookie, Body, File และวิธี Response ต่าง ๆ ที่ FastAPI มีให้เราใช้แบบสำเร็จรูป

เริ่มต้นจากสิ่งง่าย ๆ นี้ก่อน

Example code API

ที่นี้ลองใช้ curl ในการยิง request ดู ก็พบว่าใช้ได้ปกติ เรียบง่าย

Mock request via cURL

ตัวอย่าง code ด้านบนเป็น GET Method ซึ่งเราสามารถกำหนด HTTP Method ผ่าน @app.method ได้เลย ไม่ว่าจะเป็น GET, POST, PUT, PATCH, DELETE

Query String

Example code — Query String
request via cURL with Query String

ในส่วนนี้ผมได้ทำการยิง request ไปและมี query string ชื่อ name=Parasite เพื่อค้นหาข้อมูลด้วยชื่อหนังและให้ return ข้อมูลกลับมา

จะเห็นว่า แค่เพิ่ม argument ใน function ของ API และกำหนด type ก็สามารถใช้ได้แล้ว ส่วน None ก็คือ เรา define default value ของ param นี้ไว้ หรือจะสื่อในทำนองว่า field นี้ non-required นะ

แต่ถ้าอยากทำให้ field นี้ required ล่ะ ทำอย่างไร

Example FastAPI code — Query String request

เราก็แค่เอา None ออกเท่านั้น เหมือนว่าเราไม่ได้ define default value ของ param ตัวนั้นไว้

โอเคที่นี้มาลองยิง request อีกรอบ ทั้งใส่และไม่ใส่ field name

request via cURL with Query String with required field

จะเห็นว่า ถ้าไม่ได้ใส่ name ใน query string, API ก็จะทำการ return error กลับมาให้เลย

Header

ก่อนที่เราจะรับ header เราต้องทำการ import Header เข้ามาก่อน โดยเราสามารถ import จาก FastAPI ได้เลย ตามรูปครับ

Example FastAPI code — Header Request

เรามาต่อกันที่ API /member โดย path นี้จะมีหน้าที่เช็คว่าลูกค้าที่เข้ามาใช้งานมีข้อมูลในระบบแล้วหรือยัง

request via cURL — Header required

จากตรงนี้เราก็จะเห็นว่าการรับ Header นั้นก็ง่าย ๆ สามารถใช้ Header() ได้เลยทันที โดยเรากำหนดให้ชื่อ key ของ Header ตัวนั้น ๆ และกำหนดค่าที่รับมาเป็น type string ได้เลย ส่วนถ้าอยากให้เป็น non-required ใน function ก็ใส่ None ใน Header แบบนี้ Header(…) => Header(None)

Cookie

ต่อจาก Header ก่อนหน้า เราเห็นแล้วว่าถ้าสมมุติต้องการจะรับค่า Header จาก Client เราก็สามารถทำได้ง่าย ๆ ที่นี้เราจะมาดูตัวต่อไปที่สำคัญเช่นกันก็คือ Cookie

Example FastAPI code — Cookie

ก่อนอื่นให้เรา import Cookie เข้ามาก่อน ตามด้านบน แล้วมาต่อกันที่ check_token จะเห็นว่าลักษณะจะคล้าย ๆ เหมือน function ที่ผ่านมาเกือบทั้งหมด เพียงแค่เปลี่ยน argument ให้รับค่าจาก Cookie เท่านั้น

ลอง curl ดูหน่อย ใช้ได้จริงไหม

request via cURL — Cookie

Body

เราลองใช้ GET มาเยอะละ ทีนี้ลองเปลี่ยนเป็นฝั่ง POST บ้าง

ก่อนอื่นที่จะทำการรับ body ผมอยากแนะนำ lib ตัวนึงที่ทาง FastAPI มักจะใช้ร่วมกันเพื่อรับ body message โดยมีชื่อว่า pydantic ซึ่งจะถูกนำมาใช้เพื่อให้งานของเราพัฒนาได้ง่ายขึ้นหรือจะมองเป็น class object ก็ได้นะ

Example FastAPI code — Body

เราจะเห็น MoviesObject ที่เป็น class มี field name และ genre ที่เป็น type string อยู่นั้น โดยคราว ๆ ในตอนนี้เราจะใช้มันทำหน้าที่เป็น object เพื่อเราให้สามารถทำการ reference type ได้ จาก param ที่เราได้รับค่าจาก body ซึ่งถ้าใครเขียน Python แล้วสนใจวิธีการสร้าง object ด้วย pydantic ก็สามารถไปหาอ่านเพิ่มเติมได้

เรากลับมาดูตรง insert_movies() ที่เป็น function สำหรับการเพิ่ม ข้อมูล video เข้าไป โดยมี argument req_body ที่จะมี type เป็น MoviesObject และรับค่าจาก Body(…) ในขณะเดียวกันก็มี x_username จาก Header เช่นกัน

request via cURL — Header and Body

ข้อดีของการใช้ BaseModel มาช่วยในการรับ body message คือเราสามารถคุม schema (JSON) ได้ง่ายขึ้น เพราะเรากำหนดได้ว่า field ไหนเป็น required หรือ non-required รวมถึง type ของ field และสามารถกำหนด default value ได้ด้วย

เช่น ถ้า client ส่ง JSON มาไม่ครบ ก็จะ return error กลับไปเลย

request via cURL — Header and Body, Error Format

ถ้ากรณีเราต้องการกำหนด default value สำหรับ field นั้น ๆ เองก็สามารถทำได้ง่าย ๆ ตาม code ด้านล่าง

Example FastAPI code —Body with Pydantic model

จะเห็นว่า field name มี default value เป็น untitled แล้ว ทีนี้ลองยิง request แบบไม่ตรงตาม schema บ้าง โดยผมจะไม่ส่ง field name ไป

request via cURL — Header and Body with non-full format

จะเห็นว่าไม่มีการ return 422 กลับมาเลย เพราะเราปรับ field ให้เป็น non-required แล้ว แถมก็ตั้ง default value ของ field name ไว้ ทุกอย่างเลยใช้ได้ปกติ

เพิ่มเติมเราจะเห็น genre ที่เป็น field แนวตัวเลือกได้ ก็คงจะนึกถึง Enum กัน โดยตัว Pydantic ก็ support ในส่วนนี้ด้วยลองไปดูกัน

Example FastAPI code —Body with Pydantic Enum Model

โดยเราจะต้องทำการ import Enum ตามรูป และสร้าง class object ขึ้นมาเพื่อให้เป็น reference สำหรับ ประเภทของ video

ทีนี้ลองมายิง request กันดู

request via cURL — Cookie and Body with enum format

แล้วถ้าสมมุติ gerne ที่ client ส่งมา ไม่ได้มีอยู่ใน list ที่เรากำหนดไว้ล่ะ จะเกิดอะไรขึ้น

request via cURL — Cookie and Body with enum format, error

ก็จะเห็นว่า client ได้รับ return error กลับไปนั้นเอง

File

มาต่อเนื่องกันกับฝั่ง POST แต่คราวนี้มาดูพวก Body ที่ไม่ใช่ JSON บ้าง

เราจะมาดูที่การอ่าน File ไม่ว่าจะเป็นอะไรก็ตาม ในตัวอย่างนี้จะลอง test ด้วยการ upload รูปเข้าไป แล้วให้ return ชื่อไฟล์ กับ size ตามมา

Example FastAPI code — File, UploadFile

ก่อนอื่นให้เรา import File และ UploadFile เข้ามาก่อน จากนั้นให้มาดูที่ insert_image_profile() แล้วก็ยิง request ดู

request via cURL — POST File

ในตรงนี้ถ้าใครลองแล้วใช้ไม่ได้หรือเกิด error ฝั่ง server ให้ลองเช็คว่าใน environment ของเรามี python-multipart แล้วหรือยัง

Response

ตอนนี้เราเห็น GET / POST แบบเบื้องต้นไปบ้างละ ในหัวข้อนี้จะพูดถึงการปรับแก้ไข Response message บ้าง

โดยต้องย้อนกลับไปนิดนึงว่าตัว FastAPI ใช้ Starlette มา develop ตัว Framework ต่อ ซึ่ง starlette ก็เป็น framework สำหรับทำ web service เหมือนกัน ทำให้เราสามารถเรียกใช้ method ของ starlette ได้เลยโดยไม่ต้องลง package starlette เลย (เพราะถูกลงมาให้ตอนลง FastAPI)

Example FastAPI code — Response and Starlette

โดยครั้งก่อน /movies ของเราเป็นการ POST เพื่อแก้ไข object ใน server แต่เราอยากเปลี่ยน status code จาก 200 OK ให้เป็น 201 Created สามารถทำง่าย ๆ ด้วยการ import starlette.response เข้ามา แล้วเรียกใช้ JSONResponse แล้วปรับแก้ตรงส่วน return ของ function

ทีนี้ลองยิง request ดู

request via cURL — POST with 201 Created

จะเห็นว่า status code กลายเป็น 201 Created แล้ว

เพิ่มเติมใน starlette.response ไม่ได้มีแค่ JSONResponse อันที่จริงมีมากกว่านั้นเยอะมาก เช่น PlainTextReponse, HTMLResponse, FileResponse, StreamingResponse บลา ๆ เยอะมากให้เราเรียกมาใช้ได้เลยตามความต้องการ

2. API Documents

นับว่าเป็นของดีของเจ้าตัว FastAPI เลย เพราะว่าถ้าหากใครเคยประสบปัญหาเวลาคุยกับทีมหรือเพื่อนร่วมงาน ไม่ว่าจะเป็น Backend ด้วยกันเอง หรือ Frontend ที่จะคอยถามข้อมูลประว่า path ไหนทำอะไร รับอะไร return อะไร ต้อง auth ไหม วิธีการแก้ปัญหาก็มักจะจบที่ทำ document กลางขึ้นมา เพื่อให้ทุกคนในทีมได้รับรู้อย่างเท่ากัน

แต่ประเด็นมันอยู่ตรงที่เราจะทำ document รึเปล่า 5555555

FastAPI documents

หลายคนอาจจะรู้จักตัว swagger ที่ใช้ YAML มาช่วยเขียน docs ให้เรา ซึ่งก็ช่วยได้ดีระดับนึง แต่พองานเรา scale ใหญ่ขึ้นก็จะมีปัญหาขึ้นมา ประมาณว่า ขี้เกียจ

แต่ถ้าคุณใช้ FastAPI จะไม่เจอปัญหาพวกนั้น เราแค่ทำหน้าที่ dev อย่างเดียว เพราะ FastAPI จะ generate docs ให้เราทันที

Example of API default document

แค่เรา run server ขึ้นมาแล้วเปลี่ยน path ไปที่ /docs อย่างของผมเป็น 0.0.0.0:8080/docs เพียงเท่านี้เราก็จะได้หน้า documents ของ API ขึ้นมาแล้วครับ สามารถใช้ Test ได้ด้วย อันนี้โคตรดี

FastAPI documents

ตัวอย่างแบบชัด ๆ

GET [Query String]

FastAPI document — GET Method | request and response

POST [JSON]

FastAPI document — POST Method | request
FastAPI document — POST Method | response

POST [File]

FastAPI document — POST File | request and response

ซึ่งใน docs สามารถบ่งบอกลักษณะของ request ในรูป cURL ได้ แล้วก็มีบอกว่า path function นี้จะ return อะไรไปบ้าง ในระดับ object เลยทีเดียว และก็สามารถแต่งเติมได้ตามอิสระ เช่นจะเขียนอธิบาย path ว่าให้รับอะไรบ้าง โดยเราสามารถเขียนได้ใน code ของ function เราเลย

Example FastAPI code — API docs modifeid

เราสามารถลองปรับแต่งหน้าตา API docs ได้ตามต้องการ ตั้งแต่ app ที่เพิ่ม title , description, version และ insert_movies() เพิ่มคำอธิบายภายใต้ function จากนั้นให้เราไปดูที่ /docs

Example of API document with Modified

จะเห็นว่า document ของตัว FastAPI อ้างอิงตาม code เราแทบจะเป๊ะ ๆ ๆ

เพิ่มเติมโดยอันที่จริงปรับแต่งได้มากกว่านี้หรือถ้าใครอยากเข้าไปในระดับ JSON เลยก็ให้ไปที่ /openapi.json ได้เลย

เพิ่มเติมอีกรอบ ถ้าใครเบื่อหน้าตา /docs ให้ลองเปลี่ยนไปที่ /redoc จะได้ style อีกแบบนึง

FastAPI Documents — Redoc

ในส่วนของการ modify API docs ลูกเล่นค่อนข้างเยอะ ถ้าเราต้องจะปรับแต่งก็สามารถทำได้ หรือลองศึกษา OpenAPI เพิ่มเติมดูครับ

โดย path ทางเข้าของ API docs สามารถเปลี่ยนชื่อได้หรือแม้กระทั่งจะปิดการใช้มันก็สามารถทำได้เช่นกัน

3. Dependencies, Middleware, Router

หลังจากที่เราผ่านการสร้าง API ในและวิธีใช้งาน Document เบื้องต้นแล้ว ผมมี feature ที่อยากจะนำเสนอ คือ Router และ Dependencies

Middleware

แต่ก่อนอื่นอยากให้เห็นวิธีการใช้งาน Middleware ของ FastAPI สักหน่อย

FastAPI middleware

อันนี้เป็นตัวอย่างง่าย ๆ ก็คือเราปรับแต่งตัว middleware ว่าให้ทำการ เพิ่ม Header ชื่อ X-Process-Time ซึ่งก็คือระยะเวลาตั้งแต่ client request เข้ามา จนกระทั่ง response กลับไปหา client

ทดลองยิง request ดู

request via cURL to look up Header[“X-Process-Time”]

จะเห็นว่า server return Header ที่ชื่อ x-process-time มาด้วย ซึ่งเป็นหน่วยระยะเวลาของกระบวน request จนกระทั่งจบ response

Router

พอเรางานเริ่ม scale ใหญ่ขึ้นเรื่อย ๆ ก็จะเกิดปัญหาให้ยุ่งเหยิงกันนิดหน่อย ถ้าเราไม่ได้จัดระเบียบไว้ตั้งแต่ตอน design

ก่อนอื่นผมอยากให้เห็น structure ของ project ก่อน

.
├── app
│ ├── api
│ │ ├── member.py
│ │ └── movies.py
│ ├── db.py
│ ├── handlers.py
│ ├── helper.py
│ └── model.py
├── main.py
├── Pipfile
├── Pipfile.lock
├── README.md
├── requirements.txt
└── test_main.py

ผมจะยกตัวอย่างตัว member.py ว่าการประกาศ APIRouter() นั้นทำอย่างไรในเบื้องต้น

Example FastAPI code — Router declare as decorator

เริ่มต้นคือให้เรา Import APIRouter เข้ามา แล้ว assign ใส่ตัวแปรตัวนึงไว้ เพื่อให้เราใช้ในการสร้าง API Path กับ function

และฝั่ง handler.py จะเป็นไฟล์ที่กำหนด Router ซึ่งจะ import api/….py เข้ามา

Example FastAPI code — Router in handlers

ถ้าใครอยากเห็น code เต็มใน GitHub คลิกที่นี้เลยครับ

ถ้ามองเป็นภาพจะเห็นเป็นลักษณะตามรูปด้านบน คือเราไม่จำเป็นจะต้องเขียน /api/v1 ที่ function การทำงาน api ของเราในทุก ๆ ครั้ง เราจะให้ router ใน handler จัดการในส่วนนี้รวมถึงการระบุ path prefix ของ API เรา

Example FastAPI code — Prefix API Path

ซึ่งอ้างอิงจากครั้งก่อน code เราเป็นยังไง API docs เราเป็นอย่างนั้น โดยปริยาย

FastAPI documents with Router organized

อย่างภาพด้านบนคือผมได้ทำการแบ่ง member กับ movies เป็นคนละ router กัน

ซึ่งในเคสนี้อาจจะมองเห็นภาพเล็กไปนิดนึง ถ้าสมมุติถ้างานใหญ่ขึ้น ตามรูปด้านล่าง จะเป็นประโยชน์มาก ในการแบ่งหน้าที่ของแต่ละ API service อย่างชัดเจน ไม่ตีกัน ไม่ต้องมาลุ้นว่า code เราจะไปอยู่ผิดที่ผิดทางไหม

Example of Router Path Prefix

Dependencies

ถ้ากรณีที่เรามี function ที่ต้องคอยเช็ค header ในทุก ๆ request ที่เข้ามาใน path นั้น ๆ อยู่แล้ว หรือ function ที่ซ้ำซ้อนแบบใช้ทุกที่ วิธีแก้ไข ก็คงไม่พ้นทำ decorator หรือจะ Inherit class หลักมาก่อนแล้วค่อย implement แต่ถ้าใช้ FastAPI เราไม่ต้องกังวลเรื่องนี้ เพราะอะไรไปดูกัน (เขียนเองยังรู้สึกอวย)

ต่อจากตัวก่อน router ให้ลองคิดตามดูว่าถ้าทุก function API ต้องการ validate header ก่อน execute API นั้น function ที่ว่าควรจะอยู่ตรงไหน

Example FastAPI code — Dependencies

จาก code ตัวอย่างคือ เราต้อง import Depends เข้ามาก่อน แล้วให้เพิ่ม dependencies ให้กับ router ของ movies ซึ่งก็คือ get_x_card_id_token() ส่งผลให้ต่อไปนี่้ทุก ๆ ครั้งที่มีการ request เข้ามาที่ api ของ movies จะทำการ validate header ก่อน ว่ามี X-Card-ID อยู่ใน header หรือไม่ และค่าของ X-Card-ID นั้นถูกต้องตรงกับ database รึเปล่า ถ้าไม่มีหรือมีแล้วก็ไม่ถูกก็จะ return 400 กลับไป

Diagram API Router with Depedencies function

4. Testing

การทำ test ถือว่าเป็นเรื่องจำเป็นในการทำ software อยู่แล้ว ยกเว้นเราจะไม่ทำ ฮ่า ๆ ถ้าใครเคยเขียน test ด้วย python ก็น่าจะคุ้นเคยกับตัว unittest และ pytest ซึ่งก็ใช้กันอย่างแพร่หลาย โดย FastAPI ก็มีตัวช่วยให้ test api ให้ง่ายขึ้นเหมือนกัน (โคตรขาย เอาตรง ๆ ใช้ postman ก็ได้)

Example FastAPI code — Testing

เริ่มต้นด้วยการสร้างไฟล์ test_xx.py ตามที่เราต้อง แต่ต้องขึ้นด้วย test_ และภายชื่อ function ก็ต้องขึ้นต้นด้วย test_ เหมือนกันครับ

ต่อด้วย import app ของเรามาก่อนและ fastapi.testclient เพื่อให้เราทำการ mockup app และ request ขึ้นมาได้

จากนั้นก็ทำเขียน test ตาม testcase ของเรา อย่างตัวอย่าง code ด้านบนคือทดสอบว่า /api/v1/member สามารถทำงานได้ถูกต้องตามที่เราต้องการหรือไม่

ทั้งนี้ผมใช้ pytest ช่วยเพื่อให้เห็นภาพมากขึ้นว่า testcase ของเราทำงานเป็นอย่างไรบ้าง ตามรูปด้านล่าง

Pytest , Testing with FastAPI

จะเห็นว่าผ่านหมด 20 testcase ก็ไม่แปลกแหละ ผมเขียนเอง อันไหน error ก็ลบไป ล้อเล่น

5. Deployment

อันนี้มีได้หลายท่ามาก ๆ ให้มองว่า FastAPI เป็นแค่ application ทั่วไปก็ยังได้ ไม่ได้มีอะไรซับซ้อน ซึ่งก็แล้วแต่ความต้องการว่าจะเอา project เราไป host ไว้ที่ไหน ไม่ว่าจะเป็น stateless หรือ stateful ก็ได้ทั้งนั้น จะใช้ gunicorn เหมือน Flask และ Django ก็ได้

แต่ท่าที่ผมจะใช้คือ

Cloud Build ทำ CI/CD แล้ว deploy บน Cloud Run

และทุกอย่างจะอยู่บน Google Cloud ซึ่งก็จะมีพวก GitHub / Docker / Google Cloud บลา ๆ ผสมอยู่ด้วย ตาม flow รูปด้านล่าง

CI/CD Workflow

ดังนั้นถ้าใครมีความคุ้นเคย Docker/ Container หรือ GitLab CI/CD, AWS CodeBuild พวกนี้ ก็ตามไปอ่านได้ครับ คลิกที่นี่ ถือว่าเปิดโลกฝั่ง Google Cloud

Conclusion

สิ่งที่ผมเขียนมาทั้งหมดนี้เป็นเพียงประมาณ 10–20 % ของเจ้าตัว FastAPI เท่านั้น ยังไม่ได้ลงลึงอะไรมาก แต่อย่างน้อยคิดว่าน่าจะพอช่วยเหลือคนที่กำลังเริ่มต้นกับ framework ตัวนี้ได้ ด้วยความที่ FastAPI เพิ่งมาไม่นานประมาณปลายปี 2019 ทำให้คนยังไม่รู้จักแพร่หลายเท่า Flask หรือ Django แน่นอน แต่คิดว่าอนาคตมาคงมาแน่ ๆ (เดาล้วน ๆ )

ส่วน Feature อื่น ๆ ที่สำคัญ เช่น WebSocket, GraphQL บลา ๆ ผมจะทดลองใช้ดู เผื่ออนาคตได้ใช้ขึ้นมาจริง ๆแล้วถ้าเวิคจะมาเขียนเป็นบล็อคแชร์ให้ทุกคนอ่านอีกทีครับ

ทั้งนี้ ถ้าบทความนี้ ผิดพลาดในส่วนใด ๆ สามารถทักท้วงหรือคอมเม้นกันได้ครับ

อ่อ ถ้าใครสนใจจะลองทำ CI/CD ต่อ ลองอ่านอันนี้ได้ครับ

ขอบคุณครับ

--

--

Bozz Srinikorn
Bozz Srinikorn

Written by Bozz Srinikorn

bozzlab.github.io | Software/Data Engineer | Google Cloud Certified | AWS Certified

Responses (1)