Skip to main content

· 6 min read

ต้องบอกเลยว่านี่เป็นการเดินป่าครั้งแรกในชีวิตที่มีการนอนในป่ากับธรรมชาติ blog นี้ผมอยากเขียนเพื่อเก็บความรู้สึกทั้งหมดและสิ่งที่ได้พบเจอในการเดินทางครั้งนี้ไว้เป็นความทรงจำ wk18k

เที่ยวเดินป่า | เปรโต๊ะลอซู จังหวัดตาก

ต้องบอกเลยว่านี่เป็นการเดินป่าครั้งแรกในชีวิตที่มีการนอนในป่ากับธรรมชาติ blog นี้ผมอยากเขียนเพื่อเก็บความรู้สึกทั้งหมดและสิ่งที่ได้พบเจอในการเดินทางครั้งนี้ไว้เป็นความทรงจำ สิ่งที่จะเขียนจากนี้จุดประสงค์เพื่อให้ตัวเองในอนาคตกลับมาอ่านแล้วได้นึกถึงความทรงจำตอนนี้ได้ หลายคนบอกทำไมไม่ทำ vlog หล่ะ เอาจริงๆนะ ผมเหนื่อย ห้าๆๆ เลยไม่ได้ถ่ายวิดีโอเกี่ยวกับความรู้สึกไว้เลย เลยมาเขียนบทความดีกว่า เพราะต่อให้อัดวิดีโอก็ไม่แน่ใจว่าจะสื่อสารออกความรู้สึกออกไปได้หมดมั้ยนะ blog นี้เป็นความเห็นส่วนตัวและยึดผมเป็นหลักนะครับ อาจจะมีการยืมรูปภาพมาจากคนอื่นๆ แต่ส่วนใหญ่ก็จะเป็นรูปที่มีผม (แนะนำนี่มัน Blog ส่วนตัวนะโว้ยย ห้าๆๆๆ) เชิญอ่านต่อได้เลยครับ

การเตรียมตัว

ต้องบอกเลยว่าต้องเตรียมอุปกรณ์เยอะมากเพราะไม่เคยซื้อมาก่อน แต่จะลิสต์สิ่งที่จำเป็นหลักๆคือ

นำของทั้งหมดใส่กระเป๋าเดินป่าแล้วน้ำหนักรวมๆ 10 Kg ยังไม่รวมเต็นท์ ถือว่าหนักเอาเรื่องอยู่ จะซื้อตามมั้ยแล้วแต่คุณเลย เพราะอยู่ที่ความชอบส่วนตัวเลย ถามว่าร่างกายต้องเตรียมอะไรมั้ย ผมก็ไม่ได้เตรียมอะไรเลยแค่ไม่ป่วยพอล่ะ มีออกกำลังกายเล็กๆน้อยๆ

วันเดินทาง

ผมได้ทำการเดินทางโดยการซื้อทริปประมาณ 3,xxx++ บาท (มั้งจำไม่ได้ 😂) เพราะงั้น เลยจะต้องออกไปเดินทางเพื่อไปรอที่ท่ารถ

alt text (ภาพจาก ช่อง WonderBegin )

เราได้ทำการนั่งรถขนส่งเพื่อเดินทางไปยัง อำเภอวังน้อย เป็นอำเภอหนึ่งในจังหวัดพระนครศรีอยุธยา แต่ความฮาก็บังเกิดขึ้น เมื่อคนขับรถตู้ดันเลยจุดลงท่ารถที่วังน้อยซะงั้น 😂 ทำเอาต้องพากันเดินย้อนกลับไป เหงื่อออกไปตามๆกัน alt text (ดูได้จากคลิปนี้ จากช่อง WonderBegin)

หลังจากถึงท่ารถที่วังน้อย เราก็ได้พากันพักเหนื่อยและหาอะไรทานระหว่างรอรถตู้ของทริปมารับ เมื่อรถมาถึงแล้ว เราก็นั่งรถยาวๆยันถึงที่หมายที่ร้านบ้านครูซัน อำเภออุ้มผาง จังหวัดตาก

alt text ( ภาพจาก Google Map )

ร้านบ้านครูซัน

เมื่อถึงร้านบ้านครูซัน จะเป็นร้านข้าวแกงที่มีคาเฟ่และห้องน้ำไว้อาบน้ำหรือทำธุระส่วนตัว เพื่อเตรียมก่อนจะออกเดินทางไปยังจุดทางเข้าน้ำตกห่างประมาณ 70 กม alt text (ภาพจาก ช่อง WonderBegin )

หลังจากทานอาหารมื้อเช้าเรียบร้อย ก็จะเดินทางต่อไปยังจุดทางเข้าน้ำตกเปรโต๊ะลอซู ระหว่างนี้จะไม่มีห้องน้ำใครปวดหนักให้บอกคนขับรถเลยครับ หรือไม่ก็รอไปถึงจุดลงจอดอีก 70 กม เลยจะมีห้องน้ำส้วมหลุดอยู่

alt text

วันที่ 1

โดยรถตู้จะจอดให้ลงที่จุดเดินทางเท้าเพื่อเดินเข้าไปยัง Base Camp หรือจุดตั้งแคมป์ของเราให้ตลอด 2 คืน 3 วัน สิ่งที่คุณต้องรู้คือที่นี่ไม่มีสัญญาโทรศัพท์เพราะฉะนั้นคุณจะถูกตัดขาดจากโลกภายนอกโดยสิ้นเชิง ทีมงานก็จะแจกแจงเรื่องต่ายๆ ใครเช่าอะไรบลาๆ รวมถึงลูกหาบ (เทคนนิคในกรณีเหมาลูกหาบหากมีน้ำดื่มจะให้ลูกหาบแบกขึ้นไปแนะนำให้แบกไปเองแล้วเอาของที่น้ำหนักเท่าๆน้ำดื่มให้ลูกหาบไป เพราะไม่งั้นจะมีปัญหาขาลง เพราะน้ำหนักน้ำดื่มหาย ความสมเหตุสมผลของเราคือก็แค่เอาของน้ำหนักเท่าๆกันไปแทนที่ก็จบ แต่ลูกหาบไม่คิดอย่างนั้น เขาจะคิดเงินเราเพิ่ม หรือถ้าใครเรื่องอะไรจะยอมก็แนะนำให้เอาเครื่องชั่งแบบแขวนติดตัวไปด้วย แต่ทางเราไม่อยากเถียงเลยตัดจบ อิอิ) จากนั้นก็ถ่ายรูปหมู่แล้วเตรียมตัวเดินทางกัน alt text ( ตำแหน่งจุดสำคัญหลักๆ ภาพจาก Google map)

alt text ( เตรียมตัวออกเดินทางได้ 321 )

โดยตลอดเส้นทางในการเดินคุณจะเจอดินโคลน และน้ำในลำคลองตลอดทั้งทาง แต่ก็ยังมีทางเลาะไปข้างๆเหมือนกัน สำหรับใครจะทำ content ก็สามารถไปเดินเส้นทางโคลนได้ไม่ว่ากันเพิ่มสีสัน แต่สำคัญใครไม่อยากเลอะโคลนมาก็จะมีทางเลี่ยง แต่ไม่ใช่ว่าจะดีเท่าไหร่หรอกนะ 😅

alt text

alt text

alt text ( ภาพจาก @tpjtrnk)

alt text ( ภาพจาก @tpjtrnk)

ในระหว่างทางก็จะมีร้านค้าของชาวบ้านเปิดให้บริการประมาณ 4 - 5 ร้าน รับทั้งเงินสดและโอน กรณีเงินโอนต้องดูด้วยว่าแถวนั้นมีสัญญาหรือไม่ (แนะนำเงินสดดีที่สุด)

alt text ( ภาพจาก ช่อง WonderBegin )

หลังจากลุยน้ำ ลุยโคลน ลุยแดดมาแล้ว เดินไปเรื่อยๆคุณจะได้พบกับทางเข้าชุมชน โดนตรงจุดนี้หากคุณมากับทริปจะไม่ต้องเสียค่าทางเข้าเพราะทางทีมงานจะแจกตั๋วเข้าตั้งแต่จุดลงรถแล้ว เพราะงั้นเก็บไว้ให้ดีอย่าให้หายหล่ะ

alt text

หลังจากเข้ามาแล้วคราวนี้แหละของแท๊คักปู่เอ้ย หลังจากเราเดินทางถึงร้านค้าก่อนถึงเนินยาว (ร้านป้าไก่มั้ง ผมก็จำไม่ได้ เพราะมัวแต่เหนื่อยอยู่ ห้าๆๆๆๆๆๆ)

alt text ( ร้านค้าสุดท้าย ก่อนถึง Base Camp )
( ภาพจาก ช่อง WonderBegin )

alt text ( เนินก่อนเข้าไป Base Camp )

alt text ( ภาพจาก @tpjtrnk )

หลังจากนี้จะเป็นทางขึ้นเขาชันระดับนึงเลย ที่ผ่านมาแค่พอได้ร้อนและหอบ แต่หลังจากนี้เหนื่อย x2 เลยก็ได้ว่าเพราะเป็นทางขึ้นเขาชันตรงดิ่งเลย (ผมนี่เกือบตุย ห้าๆๆๆ ไม่ได้ออกกำลังแบบนี้นานมากๆ พักได้คือพักเลย) พี่ทีมงานก็บอกอีกนิดเดียว นิดเดียวที่ขเาว่าไม่เคยถึงสักทีให้ตายสิ ชั้นจะบ้า 😮‍💨

alt text ( ภาพจาก ช่อง ใจฟูสัญจร )

alt text ( ภาพจาก ช่อง ใจฟูสัญจร )

alt text ( ภาพจาก ช่อง ใจฟูสัญจร )

หลังจากถึงแล้วก็หาที่กางเต็นท์โดยการเดินทางครั้งนี้พวกเรานำเต็นท์มากันเอง แวะหาซื้ออะไรทาน ลืมบอกไปว่าตรง Base Camp มีร้านค้าขายด้วย แต่ราคาก็ขึ้นมา x1.5 จากราคาปกติแล้วแต่ของ (แต่ยอมจ่ายเถอะราคาไม่ได้แพงถึงขั้นซื้อไม่ได้) ยกตัวอย่าง น้ำอัดลมจาก 13 บาทเป็น 20 บาท ยิ่งเป็นของอาหารอย่างไก่ย่าง ถ้าในปกติอาจจะไม้ละ 5-10 บาท ที่นั่นก็จะ 20 บาท แถมยังมีข้าวไข่เจียวขายด้วย แต่ไม่ได้ซื้อกินไปถามมาราคาประมาณ 40-50 จำไม่ได้ค่อยได้

alt text ( ภาพจาก @tpjtrnk )

alt text

หลังจากนั้นก็พักผ่อนกันตามอัธยาศัย หาอาบน้ำ โดยจะมีห้องน้ำกับมีน้ำตก สามารถเลือกได้เลยอยากอาบที่ไหน แต่ว่าโซน บนน้ำตก ชาวบ้านห้ามผู้หญิงลงเล่น (คุณถามว่าทำไม ผมก็ไม่รู้เหมือนกันฟังมาจากทีมงานคนนึง) ระหว่างนั้นก็พากันเล่นเกม where wolfs เล่นเป็นบ้างไม่เป็นบ้างสนุกสนานกัน รอทางทีมงานก็ทำอาหารเย็น ก็ไปทานมื้อเย็นแล้วก็แยกย้ายทานยาก่อนนอนกัน บลาๆ พอตกดึกประมาณตีสอง ตื่นเพราะปวด __ เลยหยิบไฟฉาย พอออกมาบรรยากาศดีมากมีหมอกลงเย็นสบายมาก

วันที่ 2

ตื่นเช้ามาก็พากันไปทำธุระส่วนตัวแล้วต้มน้ำหาอะไรร้อนๆกัน ทางทีมงานก็เตรียมอาหารเช้าเป็นข้าวต้มให้

alt text ( ภาพจาก @tpjtrnk )

alt text ( ภาพจาก @tpjtrnk )

จากนั้นเวลา 07.00 น เรียกรวมพลเพื่อเตรียมจะขึ้นไปยังยอดสามหมื่น กับน้ำตกปิตุ๊โกร ตรงจุดนี้ทีมงานจะไม่บังคับใครไหวแค่ไหนก็พอแค่นั้นได้ หรืออยู่ Base Camp ก็ได้ (อันนี้แอบน่าสนใจมาก 😂 ไม่ได้ไม่ไหวก็ต้องบอกไหวไว้ก่อน) จากนั้นทีมงานก็จะแจกเชือกสีแดงเพื่อเป็นสัญญาลักษณ์ว่ามาจากทริปนี้นะ ไปไหนก็ต้องไปเป็นคู่ บลาๆ

alt text ( ภาพจาก ช่อง WonderBegin )

และแล้ววินาทีแห่งความยากลำบากก็ได้เริ่มขึ้น เริ่มต้นด้วยการเดินทางขึ้นเขาที่ชันแบบดันยาว ยาวแบบไม่มีอะไรกั้น ไอผมก็ฟิตก็เดินพยายามฝืนทำเท่ห์ สุดท้ายหอบสิครับ แรกๆก็เกาะกลุ่มกับเพื่อนๆ สักพักคนในกลุ่มขึ้นโน่นนนนน ก็เลยพักและให้เขาแซงไปก่อน วินาทีนั้นก็ทำให้รู้เลยว่า นี่สินะของจริงเมื่อวานนี้ของเด็กเล่นเลย 😂 ป่าแรกในรอบ 12 ปี เล่นกันซะแล้ว

alt text ( ภาพจาก ช่อง WonderBegin )

หลังจากกลุ่มของผมก็แบ่งออกเป็น 3 กลุ่มย่อย แนวหน้า 3 คน กับ แนวหลัง 3 คน และผมอยู่ตรงกลางคนเดียว (เศร้าหว่ะ 😂) ทำไมผมอยู่คนเดียวหน่ะหรอ หลังจากเขาแซงไปกันแล้ว ผมก็พยายามเดินรอกลุ่มแนวหลัง รออยู่นาน แต่ระหว่างนั้นก็เจอกับเพื่อนร่วมทริปที่มาด้วยกันเข้า ในใจผมคิดว่าไม่ทันคนในกลุ่มที่มาด้วยกันก็ไม่เป็นไรวะ อย่างน้อยเกาะคนในทริปด้วยกันก็ยังดี จากนี้ก็จะเป็นเรื่องราวที่ผมเจอคนเดียวแล้วนะครับ อาจจะดูเล่าเรื่องเกี่ยวกับตัวเองไปมาก (แต่มันก็ blog ส่วนตัวนี่หน่าไม่น่าจะเป็นผิดอะไร ถือว่าเป็นการแลกเปลี่ยนประสบการณ์แล้วกันนะครับ) ทุกอย่างจากนี้ล้วนเป็นความคิดเห็นส่วนตัวถ้าขัดกับหลักการหรือความคิดของใครต้องขออภัยมานะที่นี้ด้วยนะครับ

alt text ( พี่ชายคนนี้มาจากสมุทรสงครามมั้ง ห้าๆๆ something like that คอยเชียร์ให้เกาะไปกลุ่มเขาเรื่อยๆ )

alt text ( กลุ่มของพี่ชายคนนั้นที่มาจากสมุทรสงครามมั้ง )

ผมก็ได้เดินตามกลุ่มเขาไปเรื่อยๆ แต่จุดที่ต้องแยกทางกันคือตรงทางแยกไปน้ำตกรูปหัวใจ กับทางขึ้นไปดอยหมื่นสอง ถึงตรงนั้นเหนื่อยๆมาก น้ำก็เริ่มหร่อยหรอ เลยพักให้หายเหนื่อยดีกว่าฝืนไปต่อก่อน จากนั้นพวกกลุ่มพี่ชายก็นำไปกันก่อน ผมนั่งรอพักหายเหนื่อยสักพัก ก็เจอกับพี่ลูกหาบเสื้อแดงของทริปที่ซื้อมาเลยไปขอน้ำเขา เพื่อเตรียมตัว แล้วพี่แกก็ลงไปเติมน้ำพร้อมกับน้องลูกหาบของอีกทริป จนแกขึ้นมาก็เล่าให้ฟังว่า ผมหน่ะเกือบตกหน้าผาแล้วลงไปเอาน้ำเมื่อกี้ เราฟังก็ไม่รู้จะตอบยังไง จะให้กำลังใจก็จะอะไรอยู่ แต่ที่รู้ๆคือเราไม่ควรลงไปตักเองแน่ๆ จากนั้นแกก็นั่งพัก แล้วบังเอิญอีกทริปนึง เขาก็ขึ้นมาพอดีน้องก็ถามว่าเอาน้ำมั้ย ทั้งทีไม่ถามด้วยซ้ำว่าเราใช่ลูกค้าเขามั้ย เราก็เกรงใจเลยบอกไปว่า เอาครับ 😂 แหมจังหว่ะนั้นพกไว้เยอะๆก่อน แหละ จากนั้นก็เดินไปกับเขา เท่ากับตอนนี้ผมมีกลุ่มเดินแล้วรวมเป็น 5 คน เดินก็กะเนียนๆไป ถ้าเราไม่ไหวก็ว่าจะให้เขาไปก่อน แต่บังเอิญ กลุ่มนี้ก็พักทุกๆ 50 เมตร ไม่ต่างจากเรา ชอบเลย โดยจะมีพี่เบิร์ดอายุ 32 ชาวสุรินทร์ ซึ่งเขาเป็นทีมงานของอีกทริปสมมุติว่าคือทริป A แล้วก็น้องอีก 3 คนมารู้ชื่อทีหลังคือ น้องแอม อายุ 23 มั้ง , น้องบีม 26 มั้ง ทั้งสองคนคือลูกทริปของทริป B และน้องชม ไม่รู้อายุ แต่น้องอีกคนเรียกน้องเลยคิดว่าเป็นน้องแหละ เป็นคนพื้นที่ ลูกหาบของทริป B และผมเป็นลูกทริปของทริป C 😂 ทั้งสามทริปมารวมตัวกันโดยไม่ได้นัดหมาย

alt text ( ภาพพี่เบิร์ดคนมุมขวา )

alt text

พวกเราก็พากันเดินทางขึ้นไปเรื่อยๆ โดยพี่เบิร์ดแกเป็นสายเฮฮา หาเรื่องให้ทุกคนสนุกได้ตลอดทั้งทาง บางทีก็ตะโดนท้าทายว่า ไอ้ข้างบนลงมาต่อยกับกูเปล่า มึงเดินลงมานะ อะไรแบบนี้ ห้าๆ แล้วก็ค่อยแซวน้องบีมตลอดทั้งทาง แล้วก็พากันบ่นตลอดทาง และทุกๆ 50 เมตรแน่นอน นั่งพักสิครับรอไร ห้าๆ น้องชมลูกหาบของอีกทริปผู้ใจ้ดีก็หามะไฟมาให้น้องบีมกินได้ตลอดทั้งทาง ผมเองก็กินกับเขาด้วยเหมือนกัน แน่นอนว่าเปลือกมะไฟตามทางก็น่าจะมาจากกลุ่มนี้ แฮ๊ะๆ

( คลิประหว่างนั่งพักพี่เบิร์ดคุยกับน้องบีม )

alt text ( เดินไปพักไปจนถึงหมื่นสองจนได้ )

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

alt text

(คลิปเดินไปบ่นไปเหนื่อยไป ห้าๆ)

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

alt text

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

alt text

alt text

alt text

จากนั้นก็ดันมาช่วงปลายๆหมื่นห้าแล้ว ทางเป็นหินชันเลยต้องระวังให้ดีพลาดมาคือจนนอกจากเหนื่อยแล้วต้องมีสติตลอด ก็เลยถามพี่เบิร์ดว่าที่นี่มีคนเคย ... มั้ย แกก็บอกไม่รู้เหมือนกัน แต่เคยมีเคสที่อื่นประมาณ เป็นทหารแล้วตัวใหญ่เสียชีวิต กว่าจะร่างลงมาได้ลำบากกันมากอะไรงี้ หลังจากทนปวดหัวหลายชั่วโมงก็มาถึงแล้วววว หมื่นห้า

alt text

alt text

alt text

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

alt text

alt text

ต้องบอกเลยว่านี่เป็นการเดินป่าครั้งแรกในชีวิตที่มีการนอนในป่ากับธรรมชาติ blog นี้ผมอยากเขียนเพื่อเก�็บความรู้สึกทั้งหมดและสิ่งที่ได้พบเจอในการเดินทางครั้งนี้ไว้เป็นความทรงจำ wk18k ( คนแบกย่ามแดงคือน้องชม เดอะแบกน้ำของแท๊)

alt text ( แอคสักหน่อยล๊ากัน )

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

(หมื่นสองตอนไม่มีหมอก)

หลังจากพักผ่อนเราก็พากันเดินลง ระหว่าทางก็นัดกันว่าจะไปเล่นน้ำตกกินมะม่วงด้วยถึงแม่จะไม่ถึงยอดสามหมื่นก็ตาม ห้าๆ น้ำตกรูปหัวใจอะไรไม่รู้จักไม่ไปเว้ย ข่อยเมื่อยแล้ว พี่เบิร์ดบอกกลับทางขึ้นง่ายสุด น้ำตกรูปอะไรช่างหัวมันแล้ว นึกถึง อะไรซ่าๆ (น้ำอัดลม ผมไม่ดื่มพวกของมึนเมาแล้ว ที่ดื่มเพราะสังคมต้องการแต่หลังๆมาคิดว่าทำไมเราต้องเอาสังคมด้วย ก็เลิกทุกอย่างจบ ชีวิตมีความสุขดี)หลังกลับมาก็เจอกลุ่มแนวหลัง กำลังเล่นน้ำกันอยู่ก็เลยแวะไปคุยแล้วไปเล่นน้ำกับกลุ่มที่เจอบนเขาต่อ จากนั้นก็แยกย้ายครับ สำหรับกลุ่มบนเขา ก็ไม่ได้ขอ contact อะไรไว้ เพราะอยากแค่ยินดีที่ได้รู้จัก แต่ก็ไม่ได้อยากรู้จักไปมากกว่านี้ เนี๊ยเท่ห์เลย (ปล. ลืมขอแฮ๊ะ หยอกๆ แค่รู้สึกว่าบางทีเราก็ไม่จำเป็นต้องมีคนรู้จักคนเยอะหรอก ชีวิตตัวเองก็วุ่นอยู่แล้วหน่ะ พอรู้จักเยอะเจอกันบางวันอารมณ์ไม่ทักทาย หาว่าหยิ่งอีก บลาๆ)

alt text ( แนวหลัง ภาพจาก ช่อง WonderBegin )

จากนั้นแวะไปเจอกลุ่มพี่ชายที่มาจากสมุทรสงคราม ก็คุยกันว่าเดินถึงไหนยังไง แล้วก็แยกย้ายกับ Base Camp ตัวเอง แล้วก็มาเม้าท์มอยกันต่อว่าไปถึงไหนยังไง ผมก็ไปหาอาบน้ำรอกินข้าว สักพักแนวหน้าขอเราก็กลับมากัน เดินไปถึงสามหมื่นเลย สุดยอดมาก ยอมเลย รายเอียดไปดูตามคลิปของทั้งสองช่องได้เลย

alt text ( แนวหน้า ภาพจาก ช่อง WonderBegin )

alt text ( กำลังโม้ใหญ่ว่า ไม่สามารถถ่ายให้เหมือนที่ตาเห็นได้ เพราะเขาแม่งโคตรใหญ่ๆๆๆๆ ภาพจาก ช่อง WonderBegin )

หลังจากนั้นก็หาอะไรกินผมก็กินมาม่าไปหนึ่งถ้วยรองท้อง รอไปประมาณ 19.00 น ว่าลงไปกินข้าวแล้วก็เล่น where wolfs แต่ข้าวยังไม่เสร็จ ก็เลยแบบเซ็งนิดๆบวกกับง่วงนิดๆ เลย Shut it ทั้งเกมทั้งข้าว พอมารู้ทีหลังว่าคนทำกับข้าวก็คือคนที่หาขึ้นยอดดอยนั่นเอง ถามว่าเข้าใจเขามั้ยก็เข้าใจ แต่เราเองก็เสียเงินให้เขาเพื่อมาเหมือนกัน แต่ก็ไม่ติดใจอะไร เพราะก็จัดการตัวเองได้ส่วนหนึ่งก็เลยหลับไปกรนไม่กรนไม่รู้แต่ที่รู้คือเย็นสบายมาก

วันที่ 3

ได้เวลากลับบ้านเราและนั่งรถเมื่อยตูด

alt text ( เช้าตื่นก็หาโปรตีนกิน ภาพจาก ช่อง WonderBegin )

หลังจากกินข้าวเช้าเรียบร้อยก็พากันเก็บเต็นท์อย่างเละเทะ เพราะฝนตกเมื่อวานหลังลงจากเขา

alt text ( ภาพจาก ช่อง ใจฟูสัญจร)

alt text ( ไก่อร่อยดี ภาพจาก ช่อง WonderBegin )

จากนั้น 08.30 น ก็พากันลงมีปัญหานิดหน่อยเรื่องของที่ลูกหาบต้องเอาลง แต่เขียนไว้ต้นๆบทความแล้วหล่ะตามนั้นครับ move on ระหว่างทางลงตรงเนินยาวใกล้ถึงร้านป้าไก่ ผมเห็นพรั่วยังไม่ได้ใช้งานร่วงกับร่อง ไอเราก็ใจดีซะด้วยซิเลยกะว่าจะเอาไปให้ที่รถตู้ (ถ้าไม่มีเจ้าของจริงๆก็เสร็จตู ห้าๆๆๆๆ 😈) แต่สุดท้ายเจอเจ้าของตัวจริงคือลูกทริปที่มาด้วยกัน เขาถามพอดีถือว่าเขาโชคดีที่ถามถูกคน (ว๊าอดได้ของฟรี หยอกๆ) จากนั้นแวะถ่ายรูปกับน้องช้าง ผมเองไม่ค่อยอินกับช้างเลี้ยงเท่าไหร่แฮะ แต่ก็เข้าใจบางทีการเลี้ยงอาจจะดีกว่าปล่อยให้อยู่ตามธรรมชาติก็ได้ (ความคิดเห็นส่วนตัว) alt text

แล้วก็ลงมาจนถึงทางขึ้นแล้วเย้ ก็แวะซื้อน้ำกินให้สดชื่นเตรียมตัวไปอาบน้ำที่วัดใกล้ๆ ระหว่างนี้เอง พบกับผู้หญิงคนนึงที่พี่เบิร์ดให้ยืมไฟฉาย เพราะ ผู้หญิงคนนั้นแกจะขึ้นไปสามหมื่น แต่กลัวกลับมืดเลยขอยืมไฟฉายพี่เบิร์ดไป แล้วแกจะเอามาคืน แล้วแกจำเราได้ว่าใช้คนที่อยู่ยอดหมื่นห้ามั้ย ก็เลยฝากคืนไฟฉายคืนพี่เบิร์ด ผมก็เลยเอาไปบอกพี่ทีมงานของทริปนี้ เขารู้จักกัน เขาก็บอกว่า มันบอกพี่อยู่ว่ามันให้ใครยืมไปก็ไม่รู้ ไฟฉายผมหายไปเลย (คิดในใจ ก็แซวเขาทุกคนไม่แปลกที่จำไม่ได้ ห้าๆๆ)

alt text ( ภาพจาก ช่อง WonderBegin )

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

alt text ( ภาพจาก @tpjtrnk )

แล้วก็ซื้อตั๋วรอรถนั่งกลับบ้าน ถึงบ้านก็เวลา 06.00 เช้า จบทริปแยกย้ายทำงานต่อ

สรุป

ในการเดินทางครั้งนี้ผมเชื่อว่าคนในกลุ่มก็อาจจะได้ประสบการณ์ไม่เหมือนกัน บางคนอาจจะได้ contact เพื่อนใหม่ๆ หรือบางคนอาจจะได้คนคุยใหม่ๆ แอ๊ะแปลกๆ ห้าๆ หรือบางคนได้ถ่ายรูปคู่กับธรรมชาติสวยๆ บางคนได้ถ่ายวิดีโอ vlog ให้ผมไปดูดรูปมาแฮ๊ะ ทั้งหมดทั้งมวล เป็นเรื่องของรสนิยมและความต้องการของแต่ละคน แต่สำหรับผมเอง ผมรู้สึกครบรสดี ความเหนื่อยที่เหนื่อยสุดๆ ความทรมาณ และความสนุกและการหัวเราะที่หัวเราะได้อย่าเต็มที่ ถือว่าเป็นประสบการณ์ที่ดีที่น่าจะหาได้แค่ที่เปโต๊ะลอซูและเฉพาะกับทริปนี้เท่านั้น การที่ผมเขียนบทความนี้ขึ้นแค่อยากบันทึกว้ในความทรงจำก่อนที่มันถูกลมเลือนหายไปในสักวัน ไว้ให้ตัวเองในอนาคตกลับมาอ่าน ต่อให้มันจะไม่ได้เป็นคำที่สวยหรูอะไร แต่มันก็มาจากความรู้สึกและความทรงจำใน monent นี้ สำหรับใครที่อ่านมาถึงตรงนี้คุณเก่งมากครับ ห้าๆๆ สำหรับใคใรที่ยังไม่เคยไปอยากให้ลองไปสักครั้ง มันเหนื่อยแน่ๆทรมาณแน่ๆ แต่เอาที่ไหวครับ จริงการที่คุณไปถึง Base Camp ได้คุณเองแล้วครับ เผลอนอนอยู่ Base Camp นอนแช่น้ำในน้ำตกชิวก็คุ้มแล้ว แล้วเจอกันทริปหน้า need ของผมในทริปหน้าคือการใช้อุปกรณ์ โฮ๊ะๆๆๆๆๆๆๆๆ ก่อนจบมาสรุปค่าใช้จ่ายกันคร่าวๆ

สรุปค่าใช้จ่ายทั้งหมดประมาณ 5000 บาท

  • — ค่าลูกหาบ
  • — ค่ารถตู้ไปรอท่ารถที่วังน้อย
  • — ค่ารถทัวร์กลับบ้าน
  • — ค่ามาม่า+น้ำดื่ม
  • — ช่วยค่าน้ำมันคนที่ไปส่งที่จุดลงท่ารถแรก
  • — ค่าอาหารของกินส่วนตัว
  • — ค่าทริปประมาณ 3,xxx

— wk-18k —

Credit

· 7 min read

FastAPI สู่ความสนุกและความเร็วในการสร้าง API

FastAPI เป็นเฟรมเวิร์ก Python ที่ทำให้การสร้าง API เป็นเรื่องง่ายและมีประสิทธิภาพ ด้วยการใช้งาน FastAPI, คุณสามารถพัฒนา API ที่มีประสิทธิภาพสูงและรวดเร็วได้อย่างง่ายดาย ในบทความนี้ และบทความนี้เราจะใช้ VSCODE ในการเขียนโค้ดกันนะครับ และเชื่อว่าหลายๆท่านก็คงใช้ มั้ง 😅

ติดตั้ง FastAPI และ Uvicorn

เริ่มต้นด้วยการติดตั้ง virtualenv กันก่อนแน่นอนว่าจะไปแบบเร็วๆเลย :

python -m venv _env
"venv/Scripts/activate"

มาเริ่มติดตั้ง FastAPI กัน:

pip install fastapi

แน่นอนว่าเราต้องใช้ตัวที่จะมาช่วยในการ run web server ก็คือ uvicorn:

pip insatll uvicorn

ติดตั้งเครื่องมือสำหรับยิง API กัน 🔨

  • ตัวหลักก็คือ Postman ดาวโหลดและติดตั้งฟรีได้เลยที่ https://www.postman.com/

  • ตัวต่อมาคือ https://hoppscotch.io/ เอามาเป็นตัวเลือกให้ลองใช้ไม่จำเป็นต้องโหลดสามารถใช้บนบราวเซอร์ได้เลย

สร้าง API ง่าย ๆ ⛏️

สร้างไฟล์ main.py และเขียนโค้ดต่อไปนี้:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

รัน FastAPI ด้วย Uvicorn:

uvicorn main:app
  • main คือชื่อไฟล์ main.py

  • app คือชื่อตัวแปร app = FastAPI()

หากต้องการใช้ server รีโหลดอัตโนมัติทุกครั้งที่บันทึก สามารถทำแบบนี้ได้:

uvicorn main:app --reload

แล้วจะได้เลขไอพีออกมาในลักษณะนี้ http://127.0.0.1:8000

นำไปทดสอบยิง API ด้วย Postman กัน ⛏️

กดที่เครื่องหมาย + ได้เลย:

จะมี Method ให้เลือกมากมาย ตอนนี้เราเลือก Get ตัวอื่นๆเดี๋ยวเราจะเลือกในอนาคตกัน

วาง http://127.0.0.1:8000 ลงในช่อง Enter URL text

จากนั้นกด Enter หรือปุ่ม Send ได้เลย

เราจะได้ข้อมูลที่ถูก return ออกมาแล้วตามโค้ดที่เราเขียนไว้

ประมาณนี้คร่าวๆเกี่ยวกับการใช้ Postman เราจะเขียนโค้ดกันต่อ

Method ใน FastAPI

FastAPI รองรับวิธีการ HTTP หลักๆ ดังนี้:

  • GET: ใช้สำหรับดึงข้อมูลจากเซิร์ฟเวอร์

  • POST: ใช้สำหรับส่งข้อมูลไปยังเซิร์ฟเวอร์

  • PUT: ใช้สำหรับอัปเดตข้อมูลบนเซิร์ฟเวอร์

  • PATCH: ใช้สำหรับอัปเดตบางส่วนของข้อมูลบนเซิร์ฟเวอร์

  • DELETE: ใช้สำหรับลบข้อมูลบนเซิร์ฟเวอร์

นอกจากนี้ FastAPI ยังรองรับวิธีการอื่นๆ เพิ่มเติม เช่น:

  • OPTIONS: ใช้สำหรับดึงข้อมูลเกี่ยวกับตัวเลือกที่มีสำหรับ endpoint

  • HEAD: ใช้สำหรับดึง header ของ response โดยไม่ต้องดึง body

โดยการใช้ decorator กับ method ลักษณะนี้:

  • @app.get : ใช้สำหรับ GET method

  • @app.post : ใช้สำหรับ POST method

  • @app.put : ใช้สำหรับ PUT method

  • @app.delete : ใช้สำหรับ DELETE method

  • @app.patch : ใช้สำหรับ PATCH method

  • @app.options : ใช้สำหรับ OPTIONS method

  • @app.head : ใช้สำหรับ HEAD method

Method GET

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

จากโค้ดคุณจะเห็นว่า .get(“/”) ภายในวงเล็บคือชื่อ path ที่เราจะระบุใช้งานอย่างเช่นในการทดสอบที่ Postman เช่น http://127.0.0.1:8000/ แต่ถ้าเปลี่ยนเป็น .get(“/hello”) ในการใช้ URL จะเป็นลักษณะนี้ http://127.0.0.1:8000/hello ในโค้ดคุณอาจจะเป็น async อยู่ก่อนการประกาศฟังก์ชัน นั่นก็เพื่อให้มันทำงานแบบ asynchronous นั่นเอง ก็คิดง่ายๆว่าเรา เข้าเว็บนึงแล้วจะดูข้อมูลอะไรสักอย่างถ้าเน็ตเราไม่ได้เราก็ต้องทำการรอจนกว่าข้อมูลจะมาครบเราถึงจะเอาข้อมูลในเว็บนั้นไปใช้งานได้ ก็คล้ายๆกับ async โดยในส่วนที่รอจะเรียกว่า await รอจนกว่าจะเสร็จค่อยไปทำอย่างอื่นต่อประมาณนั้น

คุณสามารถส่งข้อมูลเข้ามาได้ในลักษณะนี้ :

@app.get("/{message}")
async def hello_world(message):
return {"message": message}

เมื่อเทสใน Postman url คือ http://127.0.0.1:8000/hello wk:

หรือคุณอยากรับข้อมูลมากกว่า 1 ตัว:

@app.get("/{message}/{name}")
async def hello_world(message, name):
return {"message": f"{message} - {name}"}

ทดสอบใน Postman url คือ http://127.0.0.1:8000/hello/wk:

หรือคุณอยากจะเอาข้อมูลของ name จาก url http://127.0.0.1:8000/?name=wk นี้ก็ทำได้แบบนี้:

from fastapi import FastAPI, Request
from models import User

app = FastAPI()


@app.get("/")
async def hello_world(request: Request):
name = request.query_params.get("name")
return {"message": name}

ลองทดสอบเหมือนเดิมเลย

คุณก็จะได้ข้อความมาแล้วในลักษณะประมาณนั้น แต่การส่งข้อมูลแบบนี้อาจจะถูกดักข้อมูลได้โดยบางท่าเราอาจจำเป็นต้องส่ง post เพื่อสิ่งที่ดีกว่าในหัวข้อต่อไป

Method POST

เราจะทำการสร้าง method post กันโดยใช้ชื่อว่า /add-user และ ตั้งชื่อฟังก์ชันเป็นอะไรก็ได้ แต่พยายามตั้งให้มันเหมือนสมกับ path หน่อยเพื่อจะได้ไม่งงทีหลังแล้วกันนะ โดยเราจะทำการรับข้อมูลผ่าน request โดย request จะเป็น payload ที่เราส่งเข้ามาซึ่งในส่วนของ payload คืออะไรก็คือก้อน object หรือ json นั่นเองที่เราจะต้องส่งเข้ามา โดยลักษณะข้อมูลจะเป็นลักษณะนี้

{
"name":"test"
}

แล้วเราก็จะเอามาเก็บใส่ตัวแปร name แล้ว return ข้อมูลออกไปแบบนี้:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(request: dict):
name = request.get('name')
return {"message": name}

เมื่อเราเอาไปเทสใน Postman ทำการเปลี่ยน URL เป็น http://127.0.0.1:8000/add-user และเปลี่ยน raw ด้วย ก็จะเป็นลักษณะนี้:

จากนั้น payload นี้ไปใส่ แล้วกด Send ได้เลย:

{
"name":"test"
}

การใช้ Pydantic

Pydantic เป็นไลบรารีที่ใช้สำหรับการประกาศโมเดลข้อมูล (data models) ใน FastAPI โดยมีวัตถุประสงค์เพื่อช่วยให้เราสามารถกำหนดรูปแบบของข้อมูลที่เราต้องการรับหรือส่งออกได้อย่างง่ายดาย นอกจากนี้ Pydantic ยังมีฟีเจอร์การตรวจสอบความถูกต้องของข้อมูลที่มีประสิทธิภาพ ทำให้เราสามารถตรวจสอบและจัดการข้อผิดพลาดที่เกิดขึ้นได้อย่างมีประสิทธิภาพ

เพื่อให้โค้ดเรานั้นดูดีและเมื่อนักพัฒนาคนอื่นมาดูโค้ดเราจะได้ง่ายเหมือนกันด้วยจำเป็นต้องใช้ Pydantic มากำหนดทิศทางหรือสิ่งที่ต้องการเข้ามาจาก payload ใน FastAPI เราสามารถประกาศโมเดลข้อมูลโดยใช้ Pydantic ได้โดยใช้เครื่องหมาย : เพื่อระบุชนิดข้อมูลของแต่ละฟิลด์ ตัวอย่างการประกาศโมเดลข้อมูลด้วย Pydantic แบบนี้:

from pydantic import BaseModel

class User(BaseModel):
id: int
username: str
email: str

เรามารวมกันโค้ดเราก็จะเป็น:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
id: int
username: str
email: str

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(user: User):
username = user.username
return {"message": username}

ทดสอบใน Postman ด้วย payload:

{
"id":1,
"username":"test",
"email":"test@test.com"
}

หากกรณีไม่ครบเช่น:

{
"id":1,
"username":"test"
}

แต่เพื่อความ clean ก็ควรแยกส่วนออกไปเป็นอีกหนึ่งไฟล์จะดีกว่าเพราะงั้นสร้างไฟล์ใหม่ชื่อว่า models.py เพื่อเก็บ class User(BaseModel): ไว้ต่างหากแล้วค่อย import เข้ามาใช้งานงั้นเริ่มกัน

ในไฟล์ models.py:

from pydantic import BaseModel

class User(BaseModel):
id: int
username: str
email: str

ในไฟล์ main.py:

from fastapi import FastAPI
from models import User

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(user: User):
username = user.username
return {"message": username}

สวยงามมากกว่า และอนาคตหากจะสร้าง class pydantic ก็ไปสร้างที่ไฟล์ models.py แล้วค่อยเรียกออกมาใช้งานลักษณะนี้

Method Put

จะใช้ในการแก้ไขข้อมูลทั้งหมดหรือเป็นการเปลี่ยนค่าใหม่ทั้งหมดของข้อมูล ตัวอย่างเช่น

  • นาย ก ชอบกินลูกอม และชอบสีเหลือง

ใช้ put ในการเปลี่ยนข้อมูล

  • นาย ก ชอบขนม และชอบสีดำ

อะไรประมาณนั้น เพื่อให้เห็นภาพตอนที่เรา post เรายังไม่ได้เก็บข้อมูลอะไรไว้เลยเพราะงั้นจะไปแก้กันก่อนลุย

from fastapi import FastAPI
from models import User

app = FastAPI()

users_list = []

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.get("/users")
async def users():
return users_list

@app.post("/add-user")
async def add_user(user: User):
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

โดยสร้างตัวแปร users_list ขึ้นมาเป็น global และเพิ่มเส้น /users เพื่อแสดงข้อมูลผู้ใช้งานทั้งหมด ในเส้น /add-user หากมีการส่ง payload เข้ามาครบก็จะถูกเพิ่มเข้าไปใน users_list

ต่อไปเราจะไปทำการเขียนเส้น method put กัน:

from fastapi import FastAPI
from models import User

app = FastAPI()

users_list = []

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.get("/users")
async def users():
return users_list

@app.post("/add-user")
async def add_user(user: User):
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

@app.put("/edit-user/{user_id}")
async def edit_user(user: User, user_id: int):
if len(users_list) > 0 :
for i in range(len(users_list)):
if users_list[i].id == user_id:
users_list[i] = user
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

ในการทำงานของโค้ดคือจะทำการรับ id ผู้ใช้งานที่จะแก้ไขเข้ามาแล้วทำไการเช็คว่า len(users_list) > 0 เท่าเพื่อป้องกันกรณีไม่ได้มีผู้ใช้งาน จากนั้นทำการวนซ้ำเพื่อเปลี่ยนแปลงข้อมูลตาม payload ที่รับเข้ามา

นี่คือตัวอย่างการใช้ PUT ก็จะทรงๆนี้ครับ

Method PATCH

บางท่าเราก็ไม่ได้อยากแก้ไขข้อมูลมันทุกตัวหรอก เราจะแก้บางตัวเท่านั้น เพราะงั้นการมีอยู่ PATCH เลยเข้ามาช่วยตรงนี้จะจำง่ายๆก็เหมือนกับอัปเดตแพทช์อะไรประมาณนี้ เพราะอัปเดตแพทช์คืออัปเดตอะไรบางอย่างหรือเปลี่ยนแปลงบางอย่างไม่ใช่ว่าจะเปลี่ยนทุกระบบในเกมงั้นมาดูตัวอย่างโค้ดกัน

@app.patch("/update-user/{user_id}")
async def update_user(user: User, user_id: int):
if len(users_list) > 0 :
for i in range(len(users_list)):
if users_list[i].id == user_id:
users_list[i] = User(**user.dict())
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

จากโค้ดรูปแบบคล้ายกับ put มาก แต่ที่ต่างคือ users_list[i] = User(**user.dict())

ทดสอบใน Postman กัน payload คือ

    {
"id":1,
"username":"admin_1234"
}

ผลลัพธ์จะบอกว่าต้องส่ง email มาด้วยนะ แต่ถ้าเราส่ง email ไปมันก็จะเป็น Put ไม่ใช่หรือ เพราะงั้นเราจะไปแก้ไขกันที่ไฟล์ models.py เราจะใส่ค่าเริ่มต้นกรณีไม่ใช่ payload เข้ามา ในลักษณะแบบนี้

from pydantic import BaseModel


class User(BaseModel):
id: int
username: str
email: str = ""

กลับไปแก้ main.py เพื่อดักข้อมูลกันต่อ

@app.patch("/update-user/{user_id}")
async def update_user(user: User, user_id: int):
if len(users_list) > 0:
for i in range(len(users_list)):
if users_list[i].id == user_id:
if user.email == "":
user.email = users_list[i].email
users_list[i] = User(**user.dict())
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

โดยจะทำการดักถ้าไม่ได้ส่ง email ก็จะไปดึงข้อมูลเก่าที่อยู่ใน users_list ออกมา

    if user.email == "":
user.email = users_list[i].email

ทำการทดสอบกัน

แล้วดูข้อมูลกัน

Method DELETE

ส่วนตรงนี้จะง่ายมากทำคล้ายๆกับที่ผ่านๆมาเราต้องการแค่ user_id แล้วจะทำไปลบออกใน users_list และเราจะสร้างเส้น /delete-user/{user_id} ในลักษณะนี้ เป็นยังไงมาลุยกันเลย

@app.delete("/delete-user/{user_id}")
async def delete_user(user_id: int):
if len(users_list) > 0:
for i in users_list:
if i.id == user_id:
users_list.remove(i)
return {"message": "ลบผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

ทำการทดสอบกัน โดยเราจะไม่ส่ง payload ไปเพราะเราจะส่งคล้ายกับ get โดยข้อมูลที่มีจะมีสองก้อน

จากนั้นเราจะไปลบกันด้วยเส้นที่เราสร้างขึ้นมากัน

กลับไปดูข้อมูลทั้งหมดกัน

ต่อไปคือส่วนเสริม

  • สร้างเส้น get-user/{user_id}

ข้อมูลที่เราดึงมาตอนนี้คือเป็นการดึงข้อมูลทั้งหมดออกมา แต่เราอยกาดึงข้อมูลแค่ตัวหล่ะทำยังไง เราก็จะสร้างเส้น get-user/{user_id} แล้วส่งข้อมูลแค่ตัวเดียวออกไปกันลุย

@app.get("/get-user/{user_id}")
async def get_user(user_id: int):
if len(users_list) > 0:
for i in users_list:
if i.id == user_id:
return i
return {"message": "ไม่พบผู้ใช้งาน"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

มาทดสอบกัน

  • สร้าง autoid

จะดีกว่าไหมอยากให้ระบบสร้างเลข id เรียงให้เราอัตโนมัติไปเลย ตอนส่ง payload จะได้ไม่ต้องกังวลเรื่องของ id ซ้ำกันไป เพราะงั้นมาลุยกัน

แน่นอนว่าเราจะไม่ id ไปใน payload แล้ว เพราะงั้นเราจะไปแก้ในไฟล์ models.py ให้ id มีค่าเริ่มต้นกัน

from pydantic import BaseModel


class User(BaseModel):
id: int = 0
username: str
email: str = ""

และไปแก้ในไฟล์ main.py ที่เส้น add-user โดยเราจะทำแบบง่ายที่สุดคือนับจำนวนของ users_list ว่ามีกี่ตัวโดยใช้ len() เข้ามาช่วยเท่ากับว่าถ้าไม่มีสมาชิกอะไรในนั้นมันจะได้ 0 ออกมานั่นเองมาดูโค้ดกัน

@app.post("/add-user")
async def add_user(user: User):
user.id = len(users_list)
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

มาทดสอบกันเลยโดย payload คือ

{
"username":"test",
"email":"rtes@cx.cd"
}

แล้วลอง Send สักสองสามคลิกดู แล้วไปทดสอบเส้น /users อีกครั้งก็จะเห็นว่ามันมีเรียงให้อัตโนมัติแล้ว!!

และนี่ก็คือคร่าวๆของ FastAPI โดยบทความต่อไปเราจะลองทำการเชื่อมต่อกับ database อย่าง mongoDB หรือรับ JWT เสร็จลง cookie กันดู ซึ่งก่อนหน้ามีบทความเกี่ยวกับเชื่อมการ Authentication ด้วย firebase สามารถไปลองดูกันเพิ่มเติมได้นะที่ https://medium.com/@watchakorn-18k/%E0%B8%81%E0%B8%B2%E0%B8%A3-authentication-%E0%B9%83%E0%B8%99-fastapi-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-firebase-admin-sdk-94c4eb1040d4 หวังว่าจะมีประโยชน์กันนะครับแล้วเจอกันใหม่

Github : https://github.com/watchakorn-18k

Linkedin : https://www.linkedin.com/in/watchakorn/

· 2 min read

generate from Bing

MongoDB คืออะไร?

MongoDB เป็นฐานข้อมูล NoSQL ประเภท Document-oriented ซึ่งหมายความว่าข้อมูลจะถูกเก็บในรูปแบบของเอกสาร JSON

การใช้งาน MongoDB กับ Python

การใช้งาน MongoDB กับ Python นั้นง่ายมาก เพียงแค่ติดตั้งไลบรารี PyMongo

pip install pymongo

แนะนำให้ใช้ virtual environment ก่อนติดตั้ง

python -m venv venv

เปิดใช้งาน virtual environment

"venv/Scripts/activate"

เซิร์ฟเวอร์ MongoDB

มีสองแบบคือ:

— ติดตั้งเปิดใช้งานเองบนเครื่องด้วยโปรแกรม WinNMP สำหรับ windows

— ใช้ฟรีจาก Cloud ของ https://www.mongodb.com/

Database Manager MongoDB

มีสองที่แนะนำ:

dbgate

Studio 3T

การเชื่อมต่อ MongoDB

database.py
from pymongo import MongoClient

# เชื่อมต่อกับ MongoDB
client = MongoClient("localhost", 27017)

# เข้าถึงฐานข้อมูล
db = client["my_database"]

# เข้าถึงคอลเล็กชัน
collection = db["my_collection"]

เพิ่มข้อมูลลงใน MongoDB

app.py
from database import collection

payload = {"name": "wk", "location": "thailand"}
collection.insert_one(payload)

ดึงข้อมูลจาก MongoDB

app.py
from database import collection

# ค้นหาข้อมูล
result = collection.find_one({"name": "wk"})

# ดึงข้อมูลทั้งหมด
result_all = collection.find()

print(result)
print([x for x in result_all])

อัปเดตข้อมูลใน MongoDB

app.py
from database import collection

# อัปเดตข้อมูล
collection.update_one({"name": "wk"}, {"$set": {"name": "watchakorn"}})

ลบข้อมูลใน MongoDB

app.py
from database import collection

# ลบข้อมูล
collection.delete_one({"name": "watchakorn"})

เพิ่มข้อมูลหลายรายการใน MongoDB

app.py
from database import collection

# เพิ่มข้อมูลหลายรายการ
documents = [
{"name": "John Doe", "age": 30},
{"name": "Jane Doe", "age": 25},
{"name": "Peter Smith", "age": 40},
]
collection.insert_many(documents)

ค้นหาและอัปเดตข้อมูลในหนึ่งขั้นตอนใน MongoDB

app.py
from database import collection

# เพิ่มข้อมูลหลายรายการ
document = collection.find_one_and_update(
{"name": "Jane Doe"}, {"$set": {"age": 31}}
)

· 11 min read

generate from Bing


การ Authentication ใน FastAPI ด้วย Firebase Admin SDK

สวัสดีครับ เหล่านักพัฒนาผู้รักความสะดวก!

เบื่อไหมครับกับการเขียนโค้ด Authentication ซ้ำๆ วนไปวนมา ‍ อยากจะมีระบบที่ใช้งานง่าย ปลอดภัย และรวดเร็วทันใจ ⚡️ ถ้าใช่! บทความนี้เหมาะกับคุณมาก

วันนี้ผมจะมาแนะนำวิธีการใช้ Firebase Admin กับ FastAPI สองยักษ์ใหญ่ที่จะช่วยให้คุณสร้างระบบ Authentication สุดเจ๋งได้แบบง่ายๆ โดยไม่ต้องเสียเวลาเขียนโค้ดเยอะแยะ บทความนี้แทบจะละเอียดมาก ไม่เหมาะกับผู้ขี้เกลียดอ่านอะไรเยอะแยะ 😁 เพราะงั้น ขั้นตอนไหนคุณรู้อยู่แล้วข้ามไปได้เลย 🧐

เตรียมตัวให้พร้อม แล้วไปลุยกันเลย!

ขอบเขตเครื่องมือ

ToolVersionLink
Python3.11.5https://www.python.org/downloads/release/python-3115/
Visual Studiocurrenthttps://code.visualstudio.com/
Postmancurrenthttps://www.postman.com/
OSWindows 11

เริ่มต้นใช้งาน Firebase Admin

  1. เข้าใช้งาน Firebase ก่อน

  • ตั้งชื่อได้อิสระได้เลย เสร็จแล้วก็กด Continue โล้ดดดด

  • จะเป็นการถามว่าจะใช้งาน Google Analytics หรือเปล่า ถ้าไม่ก็ติ๊กตรง Enable… มันก็จะปิด แต่ว่าระบบแนะนำให้เปิด ก็เปิดแล้วกัน จากนั้นกด Continue ได้เลย แต่กรณีคุณปิดมันจะเป็นปุ่ม Create project

  • หากคุณเปิดเพราะว่าเชื่อบทความ (^_^) ทำการเลือกบัญชีได้เลย แต่หากไม่เคยใช้ Google Analytics อาจจะต้องสร้างบัญชีก่อนนะครับถึงจะมีให้เลือก แต่ถ้าไม่อยากวุ่นกดที่ Previous เพื่อย้อนกลับไปปิดก็ได้เช่นกัน เพื่อทุกอย่างเรียบร้อยกด Create project ได้เลย

  • จากนั้นก็รอสักระยะ

  • เสร็จแล้ว!!! กด Continue ได้เลย!

  • เมื่อมาถึงหน้านี้หากท่านยังไม่เคยใช้งานมาก่อน ตั้งสติและใจเย็นๆ 😮‍💨😮‍💨 บทความนี้เราจะใช้แค่ Authentication เท่านั้น! 🧐

  • แทบเมนูด้านซ้ายเลือก Build และ **Authentication **ที่เราหมายปอง

  • เช่นเดิมหน้านี้ก็ใจเย็นๆ จุดหมายเราคือปุ่ม Get stated แต่ถ้าคุณอยากรู้อะไรเพิ่มเติมก็ดูคลิปที่เขานำมาให้รอก็ได้ ถ้าไม่เก่งอังกฤษ (เหมือนผม 😅) ก็เปิดคำบรรยายแล้วแปลอัตโนมัติเป็นไทย ก็ได้เช่นกัน ถ้าพร้อมแล้วค่อยมากด Get stated

  • เหล่านี้คือเหล่า providers ที่เราสามารถใช้ในการ **Authentication **ได้แต่สิ่งที่เราสนใจแบบง่ายๆพื้นๆคือ **Email/Password **ส่วนอื่นๆไม่ต้องไปสนใจ! เพราะผมยังใช้ไม่เป็น 😎เพราะงั้นเลือก **Email/Password **ได้เลย

  • ทำการ Enable ที่ **Email/Password **แล้วเลือก Saveได้เลย ส่วน Email link (passwordless sign-in) ยังไม่ได้ลองเช่นกันครับผม เพราะงั้นช่างมัน 😅

  • ถ้าได้แบบนี้เป็นการเสร็จในส่วนนี้แล้ว

ต่อไปเราจะไปเตรียมโฟลเดอร์และไฟล์

จัดการโฟลเดอร์และไฟล์ให้เตรียมพร้อมใช้งาน

  1. สร้างโฟลเดอร์

  • เพื่อไว้เก็บทุกอย่างที่เราจะทำ ส่วนนี้จะเป็นส่วนที่ยากที่สุดเพราะไม่รู้ว่าจะใช้ชื่อว่าอะไรดี 😅 จริงๆจะใช้ชื่อว่าอะไรก็ได้ แต่ทางผมของตั้งชื่อว่า **fastapi-firebase-auth **แล้วกันจากนั้นเข้าไปในโฟลเดอร์ให้เรียบร้อย
  1. เราจะทำการนำโฟลเดอร์ที่เราสร้างไปใช้งานใน **Visual Studio Code **หากคุณใช้ windows คุณสามารถทำตามนี้ได้เลย (หากคุณใช้อย่างอื่นคุณต้องค่อยๆ cd ไปยังโฟลเดอร์ของคุณและใช้ code . ได้เหมือนกัน)

  1. เนื่องจากว่าคุณจำเป็นต้องติดตั้ง firebase_admin และ fastapi ดังนั้นเราจึงต้องสร้าง virtual environment ขึ้นด้วยด้วยคำสั่ง
python -m venv .env
  • โดย .env คือชื่อจะใช้ชื่ออะไรก็ แต่แนะนำว่าใช้ชื่อนี้ง่ายกว่า

  • เมื่อทุกอย่างเรียบร้อยจะเป็นแบบนี้จากนั้นทำการเปิดใช้งานด้วยคำสั่ง
.env/Scripts/Activate.ps1
  • หากคุณใช้ Terminal คือ Power Shell (ตรงนี้หากไม่ได้ลองศึกษาเรื่อง virtual environment python เพิ่มเติมนะครับ)

  • แต่หากไม่ได้จริงๆ ก็ไม่เป็นไร สำหรับมือใหม่มันอาจจะยุ่งยาก ทำขั้นต่อไปกันได้เลย!

ติดตั้งสิ่งต่างที่ต้องใช้กัน

  1. ติดตั้ง firebase-admin กัน https://pypi.org/project/firebase-admin/
pip install firebase-admin

  1. ติดตั้ง FastAPI กัน https://pypi.org/project/fastapi/ เพื่อใช้เป็น web framework
pip install fastapi

  1. ติดตั้ง Uvicorn กัน https://pypi.org/project/uvicorn/ เพื่อใช้เป็นตัว run server
pip install uvicorn

ส่วนนี้เสร็จเรียบร้อยต่อไปเราจะกลับไปที่ firebase เพื่อเอาอะไรบางอย่างจากมัน 🧐

ไปเอา Certificate จาก Firebase

  1. จากที่หน้าที่เราทำกันล่าสุดที่ firebase ทำการที่รูป ⚙️ หรือรูปเฟือง และเลือก Project settings

  1. จากนั้นไปที่แท็ป Service Accounts

  1. เลือกเป็น Python ก่อนจากนั้นก็ Generate new private key

  1. เลือก generate key

  1. คุณจะได้ไฟล์ .json มาจากนั้นนำมันมาใส่ไว้ในโฟลเดอร์ที่เราเตรียมก่อนหน้านี้

  1. เปลี่ยนชื่อใหม่ให้มันเพื่อความใช้งานง่าย จริงจะใช้ชื่อว่าอะไรก็ได้ แต่ส่วนนี้แนะนำแบบ 100% ให้ใช้ชื่อ **service-account.json **แบบนี้ดีกว่า ระวังด้วยหากคุณจะเปลี่ยนชื่อที่โฟลเดอร์เลย ไม่ต้องใช้ .json แต่ถ้าคุณเปลี่ยนใน vscode ต้องใช้ .json ด้วย ไม่งั้นจะเป็นลักษณะนี้

แบบที่ถูกคือ ✅

ข้อมูลในนี้สำคัญหากคุณจะนำไปใช้ต่อระวังอย่าให้หลุด แต่ถ้าหลุดก็ไปลบแล้วเริ่มใหม่ 😅

เรียบร้อย เราจะไปส่วนต่อไปกัน

ลุยโค้ดกันได้เลย !

  1. สร้างไฟล์ app.py ขึ้นมา

  2. จากนั้นเราจะมาเริ่มใช้งาน FastAPI กันโดยคัดลอกโค้ดด้านล่างไปใส่ที่ app.py ได้เลย

app.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}
  1. จากนั้น run server ด้วย uvicorn แล้วลอง test ด้วย Postman ดู
uvicorn app:app --reload
  • app แรกคือชื่อไฟล์ app.py และ app ต่อไปคือชื่อตัว app = FastAPI() และ reload คือ เมื่อคุณแก้ไขโค้ดและบันทึกมันจะรีโหลด web server ให้อัตโนมัติโดยไม่ต้องมาหยุดแล้วรันใหม่ โอเคนะ 😎

  1. สร้าง **init_firebase.py **เพื่อจะใช้เป็นเชื่อมต่อกับ firebase ด้วยไฟล์ service-account.json ที่เราได้มา โค้ดดังนี้
init_firebase.py
from firebase_admin import credentials, initialize_app

cred = credentials.Certificate("service-account.json")
initialize_app(cred)
print("firebase init done")
  1. กลับไปแก้ไขโค้ด app.py เพิ่มเติม
app.py
from init_firebase import *
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}
  1. แล้วบันทึกรอรีโหลดหากมีข้อความ **firebase init done **ก็ถือเรียบร้อย 😎

  1. สร้างไฟล์ชื่อว่า models.pyเราจะทำการกำหนดรูปแบบ payload ที่เราต้องการนำเข้ามาซึ่งเราจะทำแบบง่ายๆจะรับเข้ามาแค่ email กับ password แบบนี้
models.py
from pydantic import BaseModel


class User(BaseModel):
email: str
password: str
  1. เราได้รูปแบบ payload แล้วเราจะกลับไปที่ app.py เพื่อทำการสร้างเส้น api ใหม่คือเส้น /signup เพื่อใช้ในการสมัครสมาชิกนั้นเองโดยจะใช้ method เพื่อจะรับ payload เข้ามา และเหมือนเดิมคัดลอกไปวางทับโค้ดเดิมใน app.py ได้เลยอย่าลืมบันทึุกด้วย 😎
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}

@app.post("/signup")
def sign_up(user: User):
return user
  1. เทสใน Postman ได้เลยโดย payload เอาไปใส่ที่ตรง raw ได้เลยแล้วกด **Send **จะได้แบบรูปแบบ
payload
    {
"email":"wk23@gmail.com",
"password":"213sad2w2"
}

  1. ต่อเราจะทำการโค้ดในส่วนของการสมัครสมาชิกลงใน firebase กันจากโค้ดนี้ไปใส่ที่ app.py แล้วลองทดสอบเหมือนก่อนหน้าอีกครั้ง
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User
from firebase_admin import auth

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
print(user)
return user

11 . ลองกลับไปที่ firebase ส่วนของ Authentication คุณจะเห็นว่ามี email ตัวอย่างของเราโผล่ที่นั่นแล้ว!!

  1. แต่หากกลับไป Postman เราได้ข้อมูลอะไรมาเยอะแยะเลย เราจะมาดูส่วนที่สำคัญหน่อยแล้วกัน

payload
    {
"_data": {
"localId": "l6fNK2I2RwOft7FXV1ACpEEgX3e2",
"email": "wk23@gmail.com",
"passwordHash": "UkVEQUNURUQ=",
"emailVerified": false,
"passwordUpdatedAt": 1708104981299,
"providerUserInfo": [
{
"providerId": "password",
"federatedId": "wk23@gmail.com",
"email": "wk23@gmail.com",
"rawId": "wk23@gmail.com"
}
],
"validSince": "1708104981",
"disabled": false,
"createdAt": "1708104981299"
}
}
  • localId หรือ user_id เป็นรหัสสุ่มขึ้นมาไม่ซ้ำหรือใช้มันเป็น primary key ได้นั่นเอง

  • emailVerified เป็นสถานะที่บอกว่าอีเมลที่สมัครสมาชิกเข้ามานี้ทำการยืนยันอีเมลแล้วหรือยัง

  1. ต่อเราจะทำการสร้างเส้น /signin กันหรือเส้นที่จะใช้ในการเข้าสู่ระบบกัน แต่ว่าโชคร้ายที่ firebase_admin นี้ไม่มีการ sign in ด้วย email และ password 😭 แต่อย่าเศร้ามีวิธีแก้ 😎
  • ทำการติดตั้ง requests https://pypi.org/project/requests/ เพิ่มเติม อย่าลืมหยุด web server โดยไปที่ Termibal การกด Ctrl + C
pip install requests
pip install python-dotenv
  • กลับไปที่ firebase เลือก Project settings

  • คัดลอกข้อมูลที่อยู่หลัง Web API Key ไว้

  • สร้างไฟล์ _env เฉยๆ ไม่มีนามสกุลไฟล์นะ ไว้เก็บข้อมูลสำคัญอย่าง Web API Key จากนั้นวางลงแบบนี้ อย่าลืมเปลี่ยน key นะของใครของมันถ้าไม่เชื่อ error ไม่รู้น๊า
_env
FIREBASE_WEB_API_KEY=AIzaSyBQ8J_XYglnULI1U8vHnPYD-uz1TEjrxZY
  • สร้างไฟล์หน่อยแล้วกันเวลาเรียก FIREBASE_WEB_API_KEY จะได้เรียกง่ายๆชื่อว่า settings.py แล้วใส่โค้ดนี้ลงไปได้เลย
settings.py
import dotenv
import os

dotenv.load_dotenv("_env")

FIREBASE_WEB_API_KEY = os.environ.get("FIREBASE_WEB_API_KEY")
  • แล้วทำการสร้างไฟล์ rest_firebase_api.py เราจะทำการใช้ของที่ firebase_admin ของ python ที่ไม่ได้ใส่มาใช้กัน คัดลอกโค้ดไปได้เลย
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)

r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()
  • ต่อกันยังไม่จบในส่วนนี้เราได้ทำการสร้างฟังก์ที่ไว้ใช้ sign in เรียบร้อยแล้ว ต่อเราจะไปเขียนโค้ดในส่วน app.py กันต่อเหมือนเช่นเดิม คัดลอกไปเลย
app.py
from init_firebase import *
from fastapi import FastAPI
from models import User
from firebase_admin import auth
from rest_firebase_api import sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
return user
  1. บันทึกแล้วรันด้วยคำสั่งเดิมและเทสใน Postman ได้เลยถ้าขึ้นแบบข้างล่างแสดงว่าสำเร็จ หากไม่สำเร็จเช็ค email กับ password ใน payload ใหม่ดูน๊า

  1. ทีนี้ลองเทสกรณีรหัสผิดหรืออีเมลผิดดู
payload
    {
"email":"pg23@gmail.com",
"password":"213sad2w2"
}

กับ

payload
    {
"email":"wk23@gmail.com",
"password":"sdsddsd"
}

payload
    {
"email":"wk23@gmail.com",
"password":"213sad2w2"
}

  1. ถือว่าเยี่ยมเลยเราทำระบบ login เสร็จเรียบร้อยแล้วต่อไปเราจะให้มัน response ข้อมูล email_verified เข้ามาด้วยเพราะเราอยากรู้สถานะของผู้ใช้รายนี้ว่ายืนยันอีเมลหรือยังเราอาจจะเอาสถานะนี้ไปจำกัดสิทธิ์อะไรในอนาคตได้ เริ่มกันเหมือนเดิมคัดลอกไปได้เลย เราจะแก้ไขโค้ดในส่วน *def sign_in(user: User): *นี้เท่านั้นเพราะงั้นจะเอามาแค่ส่วนนี้จะได้ประหยัดพื้นที่นะ ที่ไฟล์ **app.py **เหมือนเช่นเคย
app.py
@app.post("/signin")
def sign_in(user: User):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
return user
  1. ลองเทสบน Postman ได้เลย ก็จะพบมาสถานะยังไม่ได้ยืนยันอีเมล

  1. และเส้นต่อไปแน่น่อนว่าต้องเป็นเส้น /send-email-verify โดยใช้เป็น method get หากมายิงเส้นส่งอีเมลไปหาอีเมลของผู้ใช้ที่มาการสมัครสมาชิกเข้ามา แต่ว่าการจะใช้เส้นนี้ได้จำเป็นต้องมี idToken สิ่งจะมาจาก เส้น /signin

โดยจะมี 2 ท่าให้เล่นคือ

    1. เส้น /send-email-verify ทำการรับ email และ password เข้ามาและเรียกฟังก์ชัน sign_in_with_email_and_password(email, password) เหมือน เส้น /signin วิธีนี้ไม่จำเป็นต้อง sign-in ก็ verify email ได้
    1. เส้น /signin หลังจากยิงเส้นนี้แล้วให้ทำการบันทึก idToken เป็น cookie เอาไว้ แล้วเส้น /send-email-verify ไม่จำเป็นต้องรับอะไรเข้าไป ไปดึง cookie idToken ที่บันทึกมาใช้ได้เลย วิธีนี้เลยจำเป็นต้อง sign-in ก่อนถึงจะ verify ได้

แน่นอนว่าเราเป็นคนคูลๆแบบสองมันดูยากกว่าแบบแรก เพราะงั้นเราเลือกวิธีที่ 2 😅 เพราะมันดูสมเหตุสมผลกว่าแค่นั้นเอง งั้นมาเริ่มโค้ดกันเหมือนเดิมเราจะไปแก้ที่ เส้น /signin โค้ดด้านล่างก็เอาไปใส่ที่ app.py

app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response
from models import User
from firebase_admin import auth
from rest_firebase_api import sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user

โดยจะดักด้วยว่าถ้าผู้ใช้งานกรอกอีเมลหรือรหัสผ่านไม่ถูกจะไม่ทำการ set_cookie

— key จะเปลี่ยนเป็นชื่ออื่นได้ — value คือข้อมูล idToken ที่อยู่ใน user — expires=datetime.utcnow() + datetime.timedelta(hours=1) cookie นี้จะถูกลบหรือหมดอายุไปในอีก 1 ชั่วโมง สามารถเปลี่ยนได้เช่น days=1 หรือ minutes=1 หรือ seconds=10 และแปลงเป็น timestamp อีกที

  • บันทึกแล้วทดสอบได้เลย แล้วไปที่แท็ป Cookies

  • ก่อนที่เราจะไปสร้างเส้น /send-email-verify กันต่อคงต้องหยุดก่อนสหาย เพราะว่าเสียใจด้วย firebase_admin ก็ไม่มี send-email-verify ให้เราใช้อีกแล้วครับท่าน 😭 ตั้งนั้นเราต้องใช้ท่าวรยุทธ์ rest เหมือนการ sign in เลย เพราะงั้นกลับไปที่ rest_firebase_api.py แล้วใส่โค้ดนี้ลงไปได้เลย
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)

SEND_VERIFY_EMAIL_URL = "https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode"


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)
r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()


def send_verify_email(id_token):
headers = {"Content-Type": "application/json"}
url = f"{SEND_VERIFY_EMAIL_URL}?key={FIREBASE_WEB_API_KEY}"
data = {"requestType": "VERIFY_EMAIL", "idToken": id_token}
response = requests.post(
url, headers=headers, json=data, auth=("api_key", FIREBASE_WEB_API_KEY)
)
return response
  • เรียบร้อยพร้อมใช้งานที่นี้ไปสร้างเส้น /send-email-verify ที่ **app.py **ได้แล้ว 😎 ลุย
app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response, Request
from models import User
from firebase_admin import auth
from rest_firebase_api import send_verify_email, sign_in_with_email_and_password

app = FastAPI()


@app.get("/")
def index():
return {"message": "Hello World"}


@app.post("/signup")
def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user


@app.get("/send-email-verify")
def send_email_verify(request: Request):
id_token = request.cookies.get("id_token")
if id_token:
send_verify_email(id_token)
else:
return {"message": "please login first 😭"}
return {"message": "send email verify 😎"}
  • มาลองเทสกับ อันดับแรกอาจจะต้องใช้ อีเมลที่เราสามารถรับส่งที่ firebase จะส่งมาให้เราได้ หรืออาจจะใช้ Temp Mail ทดสอบก็ได้

  • ขั้นแรกต้องเริ่มตั้งแต่ต้น คือ signup นั่นเอง เริ่มกัน ผมจะใช้ Temp Mail อันดับแรกไปเอามาก่อนที่ Temp Mail คัดลอกอีเมลมาโดยอีเมลมันจะสุ่มนะบอกไว้ก่อนอย่าใช้ตามผมเชื่อผม 😎 ใช้ของที่คุณได้ดีกว่า

  • และเตรียม payload ไว้
payload
    {
"email":"cahinab325@fkcod.com",
"password":"12345678"
}
  • เอาไปใส่แล้วยิงเส้น /signup ได้เลย

  • และเราต้อง sign in ก่อนถึงจะใช้ เส้น /send-email-verify ได้เพราะไม่งั้น อาจจะเกิดอาการเศร้าได้นะ 😭 เช่นแบบนี้ เพราะผมดักไว้แล้ว 😁

  • โอเคเอาจริงล่ะ เราต้อง sign in ก่อน เริ่มได้ payload เดิมเลย ยิงที่เส้น /signin กันลุย
payload
    {
"email":"cahinab325@fkcod.com",
"password":"12345678"
}

  • แล้วต่อพระเอกของเราคือเส้น /send-email-verify 🥳 มาลองดูกัน อย่าลืมเปลี่ยน method เป็น GET นะ

  • ต่อไปก็เช็คที่ Temp Email ที่เราได้มาได้เลยว่ามีอะไรส่งมาไหม?

  • ทำการยืนยันอีเมลโดยคลิกลิงก์ได้เลย

  • ก็จะได้หน้าตาแบบนี้นั่นเอง

  • กลับตรวจสอบที่เส้น /signin อีกรอบว่า email_verified เปลี่ยน true หรือยัง ถ้าเรียบร้อยแสดงว่าใช้งานได้

  1. แน่นอนว่าข้อความที่ส่งไปในอีเมลของผู้ใช้นั้นดูไม่สวยเลย คุณสามารถแต่งได้นะโดยไปที่ firebase ที่ Authentication แล้วเลือกแท็บ Templates

  1. แก้ไขข้อความบางส่วนได้ไม่สามารถแก้ Message ได้เพราะ firebase บอกว่าเพื่อเป็นการป้องกันสแปม

  1. แต่คุณสามารถเปลี่ยนภาษาได้นะ โดยไปที่ Template language

คร่าวๆ ก็จะประมาณนี้ แต่ถ้าอยากไปต่อหล่ะก็ลิงก์ที่มันยาวๆ ตอนยืนยันอีเมล์ เราสามารถจัดการให้มันยิงตรงมายัง web server เราได้ 😎 เป็นยังไปดูส่วนถัดไปเลย

ปรับแต่ง URL ยืนยันอีเมล

  1. เราจะต้องทำการเขียนโค้ดกันต่อโดยที่ **rest_firebase_api.py **เราจะทำการเปลี่ยน url จาก https://fir-fastapi-33292.firebaseapp.com เป็นเส้น api ที่สร้างขึ้นเอง แต่ก่อนจะไปจุดนั้นต้องสร้างฟังก์ชันที่จะใช้ในการรองรับมันก่อนโดยการทำงานเดิมนั้น firebase จะส่งออกมาสองข้อมูล คือ mode=action&oobCode=code หรือก็คือ mode=verifyEmail&oobCode=IpA6RRNFU….. บลาๆ แต่สิ่งที่เราสนใจคือ oobCode เพราะงั้นเราจะทำการดึงข้อมูล oobCode ออกมาจาก url บ้านั่นกันและเอามาใช้กันในฟังก์ชันที่จะสร้างขึ้นชื่อว่า verify_email แน่นอนเตรียมลุยมาดูกันจะออกมาท่าวรยุทธ์ไหน
rest_firebase_api.py
from settings import FIREBASE_WEB_API_KEY
import requests
import json

SIGN_IN_WITH_PASSWORD_URL = (
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
)

SEND_VERIFY_EMAIL_URL = "https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode"

VERIFY_EMAIL_URL = (
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo"
)


def sign_in_with_email_and_password(
email: str, password: str, return_secure_token: bool = True
):
payload = json.dumps(
{"email": email, "password": password, "returnSecureToken": return_secure_token}
)
r = requests.post(
SIGN_IN_WITH_PASSWORD_URL, params={"key": FIREBASE_WEB_API_KEY}, data=payload
)

return r.json()


def send_verify_email(id_token):
headers = {"Content-Type": "application/json"}
url = f"{SEND_VERIFY_EMAIL_URL}?key={FIREBASE_WEB_API_KEY}"
data = {"requestType": "VERIFY_EMAIL", "idToken": id_token}
response = requests.post(
url, headers=headers, json=data, auth=("api_key", FIREBASE_WEB_API_KEY)
)
return response


@app.get("/verify-email")
async def verify_email_link(request: Request):
oob_code = request.query_params.get("oobCode")
print(oob_code)
if oob_code:
response = verify_email(oob_code).json()
if "error" in response:
return {"message": response["error"]["message"]}
else:
return {"message": "please click link at email 😭"}
return {"message": "verify email 😎"}
  1. มาสร้างเส้นที่จะให้ผู้ใช้งานเข้ามายืนยันกัน ขอใช้ชื่อว่า /verify-email แล้วกันง่ายดี มาลุยโค้ด จะมีการปรับขนาดใหญ่ เพราะผมลืม async 😅 จริงๆควรใส่ทุก ฟังก์ชันเลย เพราะว่าลืมแต่ครั้งนี้ใส่ให้ครบแล้ว กลับที่ **app.py **แล้วลุยเลย
app.py
from datetime import datetime, timedelta
from init_firebase import *
from fastapi import FastAPI, Response, Request
from starlette.requests import Request as StarletteRequest
from models import User
from firebase_admin import auth
from rest_firebase_api import (
send_verify_email,
sign_in_with_email_and_password,
verify_email,
)

app = FastAPI()


@app.get("/")
async def index():
return {"message": "Hello World"}


@app.post("/signup")
async def sign_up(user: User):
email = user.email
password = user.password
user = auth.create_user(email=email, password=password)
return user


@app.post("/signin")
async def sign_in(user: User, response: Response):
email = user.email
password = user.password
user = sign_in_with_email_and_password(email, password)
email_verified = auth.get_user_by_email(email).email_verified
user["email_verified"] = email_verified
if "idToken" in user:
response.set_cookie(
key="id_token",
value=user["idToken"],
expires=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
)
return user


@app.get("/send-email-verify")
async def send_email_verify(request: Request):
id_token = request.cookies.get("id_token")
if id_token:
send_verify_email(id_token)
else:
return {"message": "please login first 😭"}
return {"message": "send email verify 😎"}


@app.get("/verify-email")
async def verify_email_link(request: Request):
oob_code = request.query_params.get("oobCode")
print(oob_code)
if oob_code:
response = verify_email(oob_code).json()
if "error" in response:
return {"message": response["error"]["message"]}
else:
return {"message": "please click link at email 😭"}
return {"message": "verify email 😎"}
  1. เนื่องจากว่าเราเคยสมัครบัญชีนี้แล้ว ถ้าไม่อยากเปลืองอีเมลก็สามารถไปลบได้ที่ firebase เลยเหมือนเดิมแล้วก็ไปที่ Users แล้วเลือกผู้ใช้ที่จะลบ ถ้ายังไม่ขึ้นลองกดปุ่ม reset ข้างปุ่ม Add user ทางขวา

  1. ทำการเปลี่ยน url ที่จะถูกส่งไปยังอีเมลก่อน ไปที่ Templates แล้วเลือกรูปดินสอหรือแก้ไข

  1. เลือก Customize action URL แล้วก็ใส่ http://localhost:8000/verify-email หรือ http://127.0.0.1:8000/verify-email แล้วก็ Save เลย

  1. แล้วก็เทสเหมือนเดิมตั้งแต่แรกเลย ขอส่งเป็นรูปยาวๆนะจ๊ะ 😁
payload
    {
"email":"toxis52325@fkcod.com",
"password":"12345678"
}

เส้น /signup

เส้น /signin

เส้น /send-email-verify

หน้าอีเมล

หลังคลิกลิงก์

เส้น /signin ก่อน verify email

เส้น /signin หลัง verify email

ยอดเยี่ยมไปเลยใช่มั้ยการ Authentication ด้วย Firebase จริงๆมีอะไรที่อยากทำอีกเช่น Reset password , เปลี่ยนอีเมล , SMS verification และเชื่อมต่อฐานข้อมูลอย่าง MongoDB พวกนี้ แต่บทความนี้ยาวล่ะ ไม่รู้ว่าจะมีคนมาอ่านหรือเปล่า 😅 แต่ก็นั่นแหละใครที่มาอ่านถือว่าเรามีวาสนาต่อกันนะครับ ท้ายนี้ก็ขอบคุณที่อ่านบทความยันจบนะครับ เพราะว่าตอนแรกการใช้ Firebase สำหรับผมเป็นอะไรที่คิดว่ายากมากๆเลย แต่พอได้เริ่มเขียนและลองศึกษาอย่างจริงจังล่ะ เขาเตรียทุกอย่างไว้ให้เยอะมากนะครับ ใครอยากดูท่าวรยุทธ์ต่างๆเพิ่มเติมไปดูได้ที่ https://github.com/firebase/firebase-admin-python/blob/master/integration/test_auth.py เขาเขียนไว้ให้แล้วเราแค่ดึงมาใช้ หวังว่าจะได้เจอกันอีกบทความหน้านะครับถ้าผม ขยัน 😁 ใครติดโค้ดตรงไหนรันไม่ได้ไปดูได้ที่นี่ครับ https://github.com/watchakorn-18k/fastapi-firebase-auth

· 2 min read

ข้อความ Commit แบบ Semantic: ยกระดับทักษะการเขียนโปรแกรมของคุณ

เคยไหม? เวลาต้องย้อนกลับไปดู commit เก่า ๆ รู้สึกสับสน อ่านแล้วไม่เข้าใจว่า commit นั้น ๆ เปลี่ยนแปลงอะไรไปบ้าง ปัญหานี้สามารถแก้ไขได้ง่าย ๆ เพียงแค่เปลี่ยนรูปแบบการเขียนข้อความ commit ให้เป็นแบบ Semantic Commit Messages

Semantic Commit Messages คืออะไร?

Semantic Commit Messages คือรูปแบบการเขียนข้อความ commit ที่มีความชัดเจน กระชับ สื่อความหมายได้ครบถ้วน ประกอบไปด้วย 3 ส่วนหลัก ดังนี้

  • ประเภทของการเปลี่ยนแปลง (type): ระบุประเภทของการเปลี่ยนแปลง ตัวอย่างประเภทที่นิยมใช้ เช่น
info
  • feat: เพิ่มฟีเจอร์ใหม่สำหรับผู้ใช้
  • fix: แก้ไขข้อบกพร่องสำหรับผู้ใช้
  • docs: อัปเดตเอกสาร
  • style: ปรับแต่งรูปแบบโค้ด
  • refactor: ปรับโครงสร้างโค้ด
  • test: เพิ่มหรือแก้ไขโค้ดทดสอบ
  • chore: อัปเดตงานอื่น ๆ ที่ไม่เกี่ยวกับโค้ด product
  • ขอบเขต (scope): ระบุส่วนที่ได้รับผลกระทบจากการเปลี่ยนแปลง (ไม่จำเป็นต้องระบุ)

  • หัวข้อ (subject): อธิบายหัวข้อหลักของการเปลี่ยนแปลง

ตัวอย่าง

info
  • feat: เพิ่มปุ่ม "บันทึก"
  • fix: แก้ไขข้อผิดพลาดในการแสดงผลรูปภาพ
  • docs: อัปเดตเอกสารประกอบ API
  • style: ปรับแต่ง format ของโค้ด
  • refactor: เปลี่ยนชื่อตัวแปร count เป็น total
  • test: เพิ่มการทดสอบสำหรับฟีเจอร์ใหม่
  • chore: อัปเดตสคริปต์ build

ข้อดีของการใช้ Semantic Commit Messages

tip
  • อ่านเข้าใจง่าย: ข้อความ commit ที่มีความชัดเจน กระชับ ช่วยให้เข้าใจเนื้อหาได้ง่าย โดยไม่ต้องเสียเวลาตีความ
  • ติดตามการเปลี่ยนแปลง: ง่ายต่อการติดตามว่ามีการเปลี่ยนแปลงอะไรเกิดขึ้นบ้างใน project
  • ค้นหาข้อมูล: ค้นหา commit ที่ต้องการได้อย่างสะดวกรวดเร็ว
  • ทำงานร่วมกัน: ช่วยให้การทำงานร่วมกันเป็นทีมราบรื่นขึ้น สมาชิกในทีมสามารถเข้าใจการเปลี่ยนแปลงของผู้อื่นได้ง่าย

การเริ่มต้นใช้งาน Semantic Commit Messages

การเริ่มต้นใช้งาน Semantic Commit Messages นั้นง่ายมาก เพียงแค่ทำตามขั้นตอนดังนี้

  • เลือกประเภทของการเปลี่ยนแปลง
  • ระบุขอบเขต (ถ้ามี)
  • เขียนหัวข้อที่อธิบายเนื้อหาการเปลี่ยนแปลง

ตัวอย่างเพิ่มเติม

info
  • feat(profile): เพิ่มฟีเจอร์แก้ไขข้อมูลส่วนตัว
  • fix(login): แก้ไขข้อผิดพลาดในการเข้าสู่ระบบ
  • docs(api): อัปเดตเอกสารประกอบ API เกี่ยวกับการชำระเงิน
  • style(components): ปรับแต่ง format ของโค้ดใน component
  • refactor(utils): เปลี่ยนชื่อฟังก์ชัน calculateTotal เป็น computeSum
  • test(features): เพิ่มการทดสอบสำหรับฟีเจอร์ใหม่
  • chore(deps): อัปเดตเวอร์ชันของ library ที่ใช้

แหล่งข้อมูล

tip

การใช้ Semantic Commit Messages เป็นวิธีง่าย ๆ ที่ช่วยยกระดับทักษะการเขียนโปรแกรมของคุณ ช่วยให้การทำงานร่วมกันเป็นทีมราบรื่นขึ้น และช่วยให้ติดตามการเปลี่ยนแปลงต่าง ๆ ใน project ได้อย่างมีประสิทธิภาพ

ลองนำ Semantic Commit Messages ไปใช้กับ project ของคุณ เริ่มต้นวันนี้!