ทำ Backend API ด้วย Node.js และ MongoDB กันดีกว่า
สวัสดีครับ วันนี้มาแนะนำการเขียน RESTful API สำหรับ Node.js ด้วยการใช้ Express.js และ MongoDB กันนะครับ ซึ่งจริงๆแล้วบทความ API ด้วย Node.js นั้นเคยเขียนไว้แล้วหลายบทความเลย เช่น
- มาทำ RESTFul API ด้วย Node.js กับ Express กันดีกว่า
- ทำ RESTFul API ด้วย Node.js, Express และ MongoDB (ใช้ Mongo.js)
- ทดลองใช้ Hapi.js สร้าง RESTFul API แบบง่ายๆ
- MongoDB คืออะไร? + สอนวิธีใช้งานเบื้องต้น
และส่วนใหญ่ก็นานแล้ว 4-5 ปี ฉะนั้น ก็เลยคิดว่าทำเป็นบทความใหม่เลยน่าจะดีกว่าครับ เนื่องจากว่ามีคนสนใจและยังเข้ามาอ่านบทความเก่าๆอยู่พอสมควร
สำหรับใครที่กำลังหัดเขียน Node.js และ MongoDB สามารถติดตามอ่านบทความซีรีย์ สอนทำเว็บไซต์ด้วย Node.js, Express และ MongoDB ซึ่งผมนำบทความที่เคยเขียน ไปใส่เป็นส่วนหนึ่งในซีรีย์นี้ครับ
เนื้อหาบทเรียน
- ตอนที่ 1 - NodeJS คืออะไร + ทำการติดตั้ง Node.js และ Node.js เบื้องต้น
- ตอนที่ 2 - ทบทวนพื้นฐาน JavaScript และ Modern JavaScript ES6, ES7+
- ตอนที่ 3 - ว่าด้วยพื้นฐาน Node.js / Callback / Sync และ Async
- ตอนที่ 4 - เริ่มต้นทำเว็บด้วย Node.js และ Express.js
- ตอนที่ 5 - ลองหัดใช้ Template Engine ชื่อ Pug
- ตอนที่ 6 - เริ่มต้นกับ MongoDB
- ตอนที่ 7 - ทำ Backend API ด้วย Node.js และ MongoDB กันดีกว่า (บทความนี้)
- ตอนที่ 8 - Express Generator / Middleware
- ตอนที่ 9 - ทำระบบ Login ด้วย Passport.js
- ตอนที่ 10 - การ Hosting และ Deploy Production
Table of Contents
- Step 1 : Create Project
- Step 2 : Express Routing
- Step 3 : Making first API
- Step 4 : Testing with Postman
- Step 5 : Connect MongoDB
- Step 6 : CRUD with Mongoose
Step 1 : Create Project
เริ่มต้นทำการสร้างโปรเจ็คด้วย npm init
ขึ้นมา หรือจะสร้างไฟล์ package.json
ขึ้นมา และตั้งชื่อ name
กับ version
ก็พอ ก็ได้ครับ
จากนั้น ติดตั้ง express
: สำหรับเป็น Web Framework เอาไว้จัดการ Routing
สร้างไฟล์ server.js
ขึ้นมา ไฟล์นี้จะเป็นไฟล์หลักของเรา
สำหรับใครที่เคยใช้ Express มาก่อน สามารถ generate โปรเจ็ค ด้วย
express-generator
ได้นะครับ ข้อดีคือมีไฟล์ routing, view template ให้เลย รายละเอียด Express Generator
ทดลอง Start server ด้วยคำสั่ง
หรือจะทำเป็น script เพื่อสั่ง npm start
ก็ได้ครับ ใน package.json
เพิ่มลงไป
ตัว Server จะรันด้วย port 9000 ครับ ตัวอย่างหน้าเว็บ เมื่อเปิด http://localhost:9000
แนะนำติดตั้ง Chrome Extension ชื่อ JSON Viewer เพื่อให้สามารถดู JSON ในหน้าเว็บได้สะดวกขึ้น
Step 2 : Express Routing
จากโค๊ดไฟล์ server.js
จะเห็นว่า เรามีการเรียก
ตัว app.get()
เป็น function ของ Express ที่เอาไว้กำหนด routing (url) สำหรับ HTTP GET ของเว็บไซต์เรา และรับ parameter 2 ตัวครับ ตัวแรกเป็น
- path ที่เราต้องการ เช่น
/
หรือ/home
หรือ/about
อะไรพวกนี้ - callback function : ที่เป็น function ที่มี parameter เป็น request และ response
request
(req) : จาก callback คือค่าที่รับมาจากทาง Client (Browser)response
(res) : เป็น object ที่เราจะ return กลับไปให้ Client (Browser) ตัวอย่างส่ง JSON กลับไป ด้วยres.json()
Route Method
ใน Express เราสามารถกำหนด HTTP Method ได้ด้วยการเรียก function ของ app
ได้เลย เช่น
หรือสามารถใช้ app.all('/', fn)
เพื่อ match ทุกๆ HTTP Method ก็ได้เช่นกันครับ
Route Path
การกำหนด Route path เราสามารถกำหนดเป็น String, RegEx หรือใส่ route parameter ได้
ในตัวอย่าง กำหนดแบบ String ถ้าเราเข้าเว็บแล้วมี url ที่ไม่ใช่ /
มันก็จะไม่แสดง message
แบบใช้ RegEx ถ้าหากว่า url ที่เราเข้ามีคำว่า hello
มันก็จะแสดง message หมด เช่น
หรือการกำหนด parameter ก็ได้ เช่น
เราสามารถ กำหนด dynamic url ได้ด้วยการใส่ semicolon แล้ว access ค่าด้วย req.params
ตัวอย่างเช่น แก้ไขไฟล์ server.js
เป็นแบบนี้
เมื่อเข้า url http://localhost:9000/hello/this-is-message จะได้ response แบบในรูป
อย่าลืม stop server ด้วย CTRL + C ก่อน แล้ว start server ใหม่อีกครั้งครับ ถ้าอยากได้ server restart ทุกครั้งที่กดเซฟ สามารถติดตั้ง Nodemon เพิ่มเติมได้
ทดลองเปลี่ยน url เป็น /hello/message
ที่ต้องการได้ เพื่อดู response ที่ส่งกลับได้ครับ
สามารถดูเรื่อง Express Routing เพิ่มเติมได้
Step 3 : Making first API
ต่อมาครับ เริ่มทำ API จริงๆแล้ว โดยตอนนี้สิ่งที่ผมจะทำคือ เป็น API สำหรับรายละเอียดของสินค้า โดยกำหนด Resource Endpoint ไว้ดังนี้
GET /products
: สำหรับแสดงรายการ Product ทั้งหมดPOST /products
: ไว้สำหรับการเพิ่มข้อมูล Product ใหม่GET /products/1
: สำหรับแสดงรายละเอียดของ Product จาก id = 1PUT /products/1
: สำหรับไว้อัพเดทค่า Product ที่มี id = 1DELETE /products/1
: สำหรับลบข้อมูลของ Product ที่มี id = 1
โดยจะเห็นว่าผมกำหนด endpoint เพื่อใช้ HTTP Method (GET, PUT, POST, DELETE) ให้สอดคล้องกับ path name ทำให้ API ดูอ่านง่ายกว่า และใช้ noun แบบ plurals แทนการตั้งโดยใช้ verb แบบ getProductAll
, getProductById
, createNewProduct
อะไรพวกนี้
ต่อมาหลักการสำหรับ API Design คร่าวๆละกันครับ นอกเหนือจากนี้คือ
HTTP Method
จากตัวอย่างด้านบน ขอเสริมเพิ่มอีกนิดครับ คือเราจะใช้ HTTP Method มาช่วยในการกำหนด API Endpoint ให้กับ server ครับ เพราะว่าอะไร เพราะว่าเรามี HTTP Method อยู่แล้ว เราเลยเอามาใช้ประโยชน์ และการตั้ง endpoint จะได้ดูง่ายขึ้น คือ
GET
: สำหรับขอ request จาก server เช่น รายชื่อทั้งหมด หรือรายชื่อเดี่ยวPUT
: สำหรับ update ค่า โดยเราจะส่งมากับ payloadPOST
: สำหรับ create หรือเพิ่มค่าใหม่DELETE
: สำหรับลบค่า
HTTP Response
200 OK
: จะเป็น response ปกติที่ไม่มีอะไรผิดพลาด201 created
: สำหรับ return กรณี create new data (ใช้กับ POST)204 no content
: สำหรับกรณี DELETE (ลบข้อมูลเรียบร้อยแล้ว) ก็จะ response empty กลับไป400 Bad request
: จะใช้กรณีที่ Server เรารับค่ามาไม่ตรงกับ API Design ไว้ เช่น ส่ง payload มาเกิน401 Unauthorized
: สำหรับกรณีที่เราไม่รู้ว่า client ที่ request มาเป็นใคร คือยืนยันตัวไม่ได้ เช่น ไม่มี token หรือ token ผิด403 forbidden
: สำหรับกรณีที่เรา authentication ผ่าน คือรู้ว่าใคร แต่ authorize ไม่ผ่าน คือ url นี้ไม่อนุญาตให้เข้าถึง เช่น เป็น user แต่ขอ request เข้าถึงหน้า admin ก็จะไม่อนุญาต เป็นต้น404 not found
อันนี้น่าจะปกติเหมือนเว็บทั่วไป คือกรณี request url ไม่มีในระบบ5xx
: ส่วนตระกูล 500, 503 จะเกิดจากปัญหาที่ฝั่ง Server ของเรา เช่นระบบล่ม code crash เป็นต้น
Naming
ต่อมาเรื่องการตั้งชื่อเล็กน้อย จริงๆแล้วตัว request และ response ของ JSON เราจะตั้งชื่อยังไงก็ได้ ที่นิยมมี 2 แบบคือ camelCase
และ snake_case
ซึ่งก็แล้วแต่ภาษาที่ implement ทั้งฝั่ง client/server หรือแล้วแต่ตกลงกันครับ เช่น
เทียบกับ
โดยในบทความนี้ขอยึดแบบ camelCase
นะครับ เพราะเป็น Naming ที่ JavaScript ส่วนใหญ่ใช้
Response
สำหรับ Response data เราสามารถส่งได้แบบ envelop กับไม่ได้ envelop ครับ (คือจะหุ้มด้วย {}
) เช่น
แบบ Envelop
และแบบไม่ enveloped
ต่อมาที่ไฟล์ server.js
ทำการเพิ่ม endpoint ต่างๆตามที่ requirement กำหนดไว้ ด้านบน เป็นดังนี้
โดยที่ตอนนี้เรายังไม่มีค่าจริงจาก Database ก็เลยใช้ mock data ขึ้นมาก่อนครับ
ทดสอบโดยการเปิด Browser
- http://localhost:9000/products : ได้รายการทัง้หมดของ Product
- http://localhost:9000/products/1003 : โชว์เฉพาะ id = 1003 (ลองเปลี่ยนเป็น 1001, 1002 ดู)
Step 4 : Testing with Postman
ต่อมาครับ เราสามารถเทส Url endpoint ได้เฉพาะ HTTP GET การจะเทส PUT/POST/DELETE เราจำเป็นต้องมี Tool ช่วยครับ และ Tool ที่ง่ายและเป็นที่นิยมก็คือ Postman นั่นเองครับ
ทดสอบ HTTP GET
ซึ่ง Postman เราสามารถกำหนด URL ได้เหมือนกับ Browser เลยครับ และสามารถเลือก HTTP Method ได้ตามรูปด้านล่างครับ
ทดลองกด Send ดูได้เลย เลือก GET และ url เป็น http://localhost:9000/products จะเห็น result แบบเดียวกับเปิดบน Browser
ทดสอบ HTTP POST
ต่อมาลองเปลี่ยนเป็น POST และกำหนด Body เป็น raw
และ content-type
เป็น JSON application/json
ดังรูปด้านล่างครับ
ทดสอบดูและได้ผลลัพธ์คือ !!!! ว่างเปล่าครับ
เพราะว่า Express ไม่สามารถรับค่า request body ได้ default จะเป็น undefined จะสามารถใช้ได้ก็ต่อเมื่อเราใช้ body-parser middleware ครับ
เพิ่มโค๊ด ไปที่บรรทัด 4 ของไฟล์ server.js
เป็นแบบนี้ครับ
เมื่อก่อน body-parser เป็น middleware ที่มาพร้อม Express ก่อนจะเอาออกไปแยกเป็น library ชื่อ
body-parser
ก่อนที่เวอร์ชั่นหลังๆ ก็นำกลับมารวมกันอีกครั้งครับ ฉะนั้นหากเจอว่าใช้app.use(bodyParser.json())
ก็คือแบบเดียวกันนะครับ
ลองดูผลลัพธ์ใหม่
ต่อมาการเทส PUT/DELETE ก็ทำเช่นเดียวกันกับ POST ครับ คือเลือกเปลี่ยน HTTP Method โดย
PUT
จะส่ง body แบบเดียวกันกับPOST
DELETE
ไม่ต้องส่ง body อะไรไป
Step 5 : Connect MongoDB
ต่อมาหลังจากมี API คร่าวๆแล้ว ต่อมาเราจะมา Connect MongoDB กันนะครับ ด้วย Mongoose
สำหรับใครที่ไม่เคยใช้ MongoDB แนะนำอ่านบทความ MongoDB ประกอบครับ MongoDB คืออะไร? + สอนวิธีใช้งานเบื้องต้น
ทำการติดตั้งตั้งผ่าน npm
ต่อมาทดสอบให้แน่ใจก่อนว่าเรารัน Mongo Server อยู่ เปิด Console/Terminal หรือ RoboMongo (ที่เป็น GUI) ดูก็ได้ครับ
ถ้า Mongo start อยู่แล้วก็ไม่มีปัญหาครับ ถ้ายัง ก็อย่าลืม start MongoDB ขึ้นมานะครับ จากนั้นทำการเพิ่มโค๊ดส่วนนี้ลงไป
เพื่อสั่งให้ Mongoose
ทำการ connect MongoDB บนเครื่องเรา โดยใช้ database ชื่อ node-api-101
(สามารถเปลี่ยนชื่อได้ตามต้องการ)
{ useNewUrlParser: true }
: ส่วนนี้เป็น Option ถ้าไม่ใส่จะ warning ว่าการ connect mongodb ด้วย url แบบ string ในอนาคตจะ depreacated แล้ว (ซึ่งไม่ใส่ก็ได้ แต่จะมี warning แค่นั้น)
ต่อมา สร้าง Model ง่ายๆ และ add ลง database ทุกครั้งที่ start server แบบนี้
จากนั้นลอง Start server ใหม่ แล้วดูผลลัพธ์ใน Mongo เราต้องมี db และ collection ใหม่ เป็นค่า JavaScript
ที่ถูกเซฟไปนั่นเอง
Mongoose Schema
ใน Mongoose เราต้องทำการกำหนด Model เป็น Schema สำหรับ Collection ที่จะเซฟครับ ซึ่งตัวอย่างคือ Product ฉะนั้นก็จะต้องทำการสร้าง schema ของ Product ขึ้นมา แบบนี้
ทำการสร้างไฟล์ใหม่ชื่อ product.js
ไว้ในโฟลเดอร์ใหม่ เป็น models
โดย Schema นั้นเพียงแค่ระบบ Type ให้มันว่าจะเป็นอะไรตัวอย่างเช่น String
, Number
หรือ [String]
สำหรับ Array ที่ข้างในเป็น String นั่นเองครับ (ซึ่งจะมี type พิเศษคือ ObjectId
เป็น type สำหรับ unique id ของ MongoDB ครับ)
จากนั้นก็ทำการสร้าง mongoose.model()
ด้วย Schema ที่เรา define ไว้ แล้วก็ export
ไปให้ไฟล์อื่นสามารถ import
Model มาใช้ได้ครับ
Step 6 : CRUD with Mongoose
ต่อมาทำ CRUD (Create, Read, Update, Delete) ข้อมูลจาก Database กันครับ โดยเราจะเปลียนทั้งหมดจากที่ใช้ mock data เป็น database จริงๆ
Save data
ต่อมา ผมกลับมาแก้ไฟล์ server.js
ตรงส่วนของ app.post('/products')
เพื่อที่จะรับค่าจาก Postman แล้ว save ข้อมูลลง Database ครับ
โดย ผมใช้ async/await เข้ามาใช้จัดการข้อมูล Promise แทน then()
ยังคงเป็น async อยู่ โดย new Product(payload)
ทำการสร้าง object product จาก payload ที่ได้รับจาก Postman (ซึ่งต้องตรง schema กับที่เราสร้างไว้ใน model ด้วยนะครับ) เมื่อทำการ save product เรียบร้อยแล้ว ก็ให้ return HTTP Status 201 กลับไป ด้วยคำสั่ง res.status(201).end()
ทดสอบรัน Server และลองยิง Postman แบบ POST เพื่อสร้าง product ก้อนใหม่ มี body แบบนี้
ผลลัพธ์ที่ได้ต้องเป็นแบบนี้ครับ
โดยที่ค่า
_id
ที่ถูกเก็บไว้ใน MongoDB จะเป็น unique id ที่ MongoDB generate ไว้ให้นะครับ สามารถเอาไปใช้เป็น unique id ต่างๆ ได้ เช่น url สำหรับ detail หรือ edit หรือ delete product ด้วย_id
find()
ต่อมาเราสามารถ Query collection ผ่าน Mongoose ได้ด้วยคำสั่ง find()
ครับ เช่น
หรือ Query โดยกำหนด condition คือ
มาแก้ไขส่วน app.get('/products')
กันบ้าง ให้ Query ข้อมูลแทนข้อมูล mock data ครับ
findById() และ findOne()
ต่อมาการ query โดยได้ result เป็นค่าๆเดียวๆครับ คือ findById
โดยใช้ ObjectId ของ MongoDB ครับ เช่น
หรือใช้ findOne()
ก็ได้ เช่นกันคือ
จะเห็นได้ว่า findById()
มีค่าเท่ากับ findOne({ _id: '' })
ถ้าเราหา _id
เท่านั้น
กลับมาแก้ server.js
ของ app.get('/products/:id')
เพื่อให้ Query ข้อมูลจาก Database กันครับ
Update
ต่อมาการ Update ข้อมูล เราใช้ฟังค์ชั่น findByIdAndUpdate()
โดยอัพเดทจาก _id
ครับ และค่าที่ต้องการอัพเดทจาก payload ที่ user ส่งมา
แต่กรณีที่ update ปกติ Mongo จะทำการ เอาค่าใหม่ไปทับค่าเก่าเลย ฉะนั้นเราต้องใช้ $set
เพื่อให้มันอัพเดทเฉพาะค่าที่เราส่งไป ค่าอื่นยังคงอยู่เหมือนเดิม แบบนี้
กลับมาปรับ server.js
สำหรับ app.put('/products/:id')
กันครับ
Delete
ต่อมาการ Delete ครับ เราจะใช้คำสั่ง findByIdAndDelete()
เพื่อหา product ที่มี _id
ที่เราต้องการ คล้ายๆ findByIdAndUpdate()
เพียงแต่ว่าไม่ต้องส่ง data อะไรไป เช่น
นอกเหนือจากนี้เรายังลบ product ที่ตรงเงือนไขเราได้ด้วยคำสั่ง findOneAndDelete()
เช่น
มาแก้ server.js
ส่วน app.delete()
กันครับ เป็นแบบนี้
Options
เราจะสังเกตเห็นว่า Mongoose มันจะ generate __v
field มาให้เรา เราสามารถเอาออกได้ ด้วยการใส่ option ในตอนที่ define schema ครับ และนอกจากนี้ ผมก็จะใช้ option timestamps
ด้วยครับ เพื่อให้มัน auto generate createdAt
และ updatedAt
เป็นเวลาที่ data นั้นถูกสร้าง หรือถูก edit นั่นเอง
เพิ่ม { timestamps: true, versionKey: false }
ลงไปที่เป็น parameter ที่ 2 ของ Schema
แบบนี้ครับ
เป็นอันเรียบร้อย ครั้งต่อไปที่ data ถูกสร้างลง Database ก็จะมี createdAt
และ updatedAt
และลบ __v
ให้เราอัตโนมัติ
และส่วนสุดท้าย กรณีที่เรา connect mongodb บางครั้งมี Error เราอาจจะไม่รู้ว่า error อะไร เราสามารถ handle มันได้ด้วยคำสั่งนี้ครับ เพิ่มลงที่ server.js
หลังจาก mongoose.connect()
Done!
สรุป
ตอนนี้ เราก็ได้ Backend API ง่ายๆ ด้วยการใช้ Express.js + Mongoose สำหรับทำ RESTFul API กันไปแล้ว รวมถึงสามารถใช้ Postman ในการเทส HTTP Method ต่างๆด้วย ที่เหลือผู้อ่านก็ลองไปดู Mongoose เพิ่มเติมนะครับ ว่ามันสามารถ Query อะไรได้บ้าง Query และกำหนด limit หรือ order ยังไง รวมถึง การเทสด้วย Postman ก็นับว่ามีประโยชน์ไม่น้อย ในการทำงานจริง
หวังว่าบทความนี้จะเป็นประโยชน์สำหรับผู้เริ่มต้นที่จะหัด Node.js และหัดทำ Backend API นะครับ หากติดปัญหาตรงส่วนไหน หรือขั้นตอนไหนไม่เคลียร์ สามารถสอบถามได้เลยครับ
สุดท้าย Source Code ของบทความนี้เช่นเคยครับ เผื่อใครติดปัญหาสามารถ Clone ไปลองดูได้ครับ
Happy Coding ❤️
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust