Nuxt.js Fundamental ตอนที่ 12 - ทำ Workshop เว็บ Portfolio
เนื้อหาของบทเรียน Nuxt.js
- ตอนที่ 1 - Getting Started with Nuxt.js
- ตอนที่ 2 - สร้าง Nuxt.js ด้วย create-nuxt-app
- ตอนที่ 3 - การกำหนด Routing
- ตอนที่ 4 - Nuxt.js Concept
- ตอนที่ 5 - Nuxt Content และ Async Data
- ตอนที่ 6 - การดึงข้อมูลจาก APIs
- ตอนที่ 7 - การใช้งานร่วมกับ Vuex Store
- ตอนที่ 8 - ทำระบบ Authentication ด้วย Nuxt.js
- ตอนที่ 9 - การ Deploy Nuxt.js
- ตอนที่ 10 - การทำ Internal API และ Middleware
- ตอนที่ 11 - ทำ SEO และ Meta tags
- ตอนที่ 12 - Workshop
มาเริ่มลงมือทำ Workshop กันดีกว่าครับ หลังจากได้เรียนรู้ Nuxtjs มา 11 ตอนแล้ว สำหรับตอนนี้จะเป็นการนำเอาทุกๆอย่างที่เรียนมาทั้งหมด มาทำ Workshop กันนะครับ โดยอาจจะมีสอดแทรกเนื้อหาบางส่วนลงไปเพิ่มเติมบ้าง และส่วนไหนอ่านไม่เข้าใจ ก็ย้อนกลับไปอ่านตอนเก่าๆ หรือทำความเข้าใจเพิ่มเติมนะครับ
จุดประสงค์ของ Workshop นี้คือ
- ประยุกต์ใช้ Nuxt Routing
- ประยุกต์ใช้ Nuxt Content
- ประยุกต์ใช้
asyncData
และการ fetch API - ประยุกต์ใช้การทำ Nuxt Authentication
- ประยุกต์ใช้การทำ SEO และ Meta Tags
- ประยุกต์ใช้การกำหนด
nuxt.config.js
เพื่อกำหนด mode ต่างๆ - สามารถ deploy Nuxt และเผยแพร่เว็บไซต์ของเราได้
ตัวอย่างหน้าตา Project Workshop ที่เราจะทำวันนี้
Step 1 - สร้างโปรเจ็คด้วย Create Nuxt App
เริ่มสร้างโปรเจ็คใหม่เลยครับ
- ผมตั้งชื่อว่า
nuxt-portfolio
- ใช้
axios
,content
และauth
modules - CSS ใช้เป็น bulma ครับ (จริงๆ ให้เห็นภาพการใช้ css เฉยๆ)
ก็จะได้แบบนี้ครับ
เมื่อสร้างโปรเจ็คเสร็จเรียบร้อยแล้ว ก็ทำการ Start development mode แล้วเริ่ม dev กันเลย
Step 2 - กำหนด Routing ต่างๆ
สร้างหน้า Blog index และ Blog Post ครับ
pages/blog/index.vue
- หน้า Blog Listpages/blog/_slug.vue
- หน้า Blog Post
ตอนนี้ก็สร้างไฟล์เปล่าๆ ขึ้นมาไว้ก่อน นอกจากนั้น ก็จะมีไฟล์อื่นๆ อีกคือ
pages/login.vue
- เอาไว้สำหรับหน้า Loginpages/profile.vue
- เป็นหน้า Profile หน้านี้จะเอาไว้ fetch Github API เพื่อแสดงรายละเอียดของเราpages/about.vue
- หรือหน้าอื่นๆ ที่อยากเพิ่ม (อันนี้แล้วแต่ free style เลยครับ)
เมื่อเรามีหน้าพร้อมแล้ว ต่อไปก็เริ่ม implement ทีละส่วน เริ่มจากหน้า Blog ก่อนเลย
ซึ่ง ทั้ง 2 ไฟล์นี้ เราจะให้มันอ่าน Content โดยใช้ @nuxt/content
ครับ
Step 3 - Nuxt Content
เราใช้ Nuxt Content เพื่อทำ blog post โดยใช้ markdown ในการเขียนบทความ (Nuxt Content ติดตั้งมาพร้อมตอน create-nuxt-app
เรียบร้อยแล้ว หากใครไม่ได้เลือกตอนสร้าง ก็ install และเพิ่มใน nuxt.config.js
ด้วยนะครับ)
ใน folder content
จะเห็นว่ามี hello.md
อยู่แล้ว เพราะมัน generate มาใหม่
ผมทำการย้ายไป folder content/blog
ที่สร้างมาใหม่ จากนั้นก็ทำการเพิ่มบทความ .md
อีก 3-4 บทความ เช่น
Step 4 - แสดงข้อมูล Content
ต่อมาที่ไฟล์ pages/blog/index.vue
เพิ่มโค๊ดนี้ลงไป
ส่วนสำคัญคือ เราใช้ $content('blog').sortBy('createdAt', 'asc')
เพื่อทำการดึง content ทั้งหมดมาแสดง โดยเรียงจากบทความล่าสุด ผ่าน asyncData
นั่นเอง และใน template เราก็แค่วน loop แสดงค่า posts ครับ v-for="post in posts" :key="post.slug"
นอกจากนี้จะเห็นว่าผมมีใช้ <nuxt-link>
ไปที่ /blog/:slug
โดยใช้ post.slug
ซึ่งเป็น dynamic route ที่เรากำลังจะทำ
ที่ไฟล์ pages/blog/_slug.vue
มีโค๊ดดังนี้
จะเห็นว่าที่ asyncData
ผมรับค่า params
จากนั้น ใช้ params.slug
เป็น args ที่ 2 ส่งไปสำหรับ $content('blog', slug)
ครับ มันจะ query ค่ากลับมาเป็น Object ถ้าเจอ (ต่างกับการใช้ $content('blog').where()
นะครับ แบบนั้นจะส่งกลับมาเป็น array)
และที่ template ก็ใช้ <nuxt-content>
สำหรับแสดงผล data จาก Markdown นั่นเอง
ทดลองเข้าเว็บ http://localhost:3000/blog และลองเลือกคลิ๊กบทความ ก็จะไปหน้ารายละเอียดบทความได้
Step 5 - เพิ่ม Navbar Menu
จะเห็นว่าเรามีเพิ่มหน้าเว็บแล้ว แต่ไม่ได้ link ไปแต่ละหน้า ตอนนี้เข้าโดยพิมพ์ url ก็เลยมาเพิ่ม navbar ให้เว็บดีกว่า โดยผมใช้ default ของ bulma เลยครับ ไม่ได้ปรับอะไรเลย แบบนี้
ผมสร้างไฟล์ components/Navbar.vue
ขึ้นมา
จากนั้น ที่ไฟล์ layouts/default.vue
ผมก็เพิ่ม Navbar
ไป
จะเห็นว่าผมไม่ต้อง import Component เลย เพราะว่าเราตั้ง auto import component ที่ไฟล์ nuxt.config.js
แล้วครับ
Step 6 - ทำหน้า Profile ดึง API
ต่อมาคือ ทำหน้า Profile ครับ ซึ่งหน้านี้ มีการดึง API จาก Github API ครับ คือ
GET /users/{username}
- ข้อมูล User โดยใช้ usernameGET /users/{username}/repos
- ข้อมูล Reponsitories ของ username นั้นๆ
เราใช้ asyncData
เพื่อดึงข้อมูลจาก API มาลองแก้ไขไฟล์ pages/profile.vue
ดังนี้
Step 7 - เพิ่ม Authentication หน้า Profile
เมื่อได้หน้า Profile แล้ว ทีนี้ทุกครั้งที่เข้าหน้านี้ มันก็จะไปดึง API มาแสดงผล ทีนี้ถ้าเราอยากใส่ Auth ให้มัน
เราใช้ nuxtjs/auth
ครับ รายละเอียดเพิ่มเติม ลองไปอ่าน ตอนที่ 8 - ทำระบบ Authentication ด้วย Nuxt.js เพิ่มเติมได้ครับ
ติดตั้ง Nuxtjs Auth
จากนั้นเพิ่มค่าใน nuxt.config.js
เพื่อกำหนด module และ strategy
ต่อมาทำการ enable Vuex ด้วย ถ้าเราไม่ใช้ Vuex ง่ายสุดคือสร้าง store ขึ้นมาตัวนึง (เพราะตัว Nuxt Auth จะใช้ store.state.auth
ในการเก็บข้อมูลนั่นเอง)
สร้างไฟล์ store/index.js
ต่อมาสร้างไฟล์ server.js
ขึ้นมา (เราจะใช้เป็น API Server กันครับ)
ทำการติดตั้ง express และ cors
จากนั้น Start server ขึ้นมาเลยครับ (มันจะรันอีก port นึง)
ตัว Server API เป็นแค่ mock นะครับ เพื่อให้เห็น flow การทำงานเฉยๆครับ ไม่มีทั้งการ query database การ compare password hash การ sign JWT Token หรืออะไรทั้งนั้น เราข้าม process นั้นๆ เน้นแค่ request และ response ที่จะได้รับครับ
Step 8 - ทำหน้า Login
เมื่อมี API Server แล้ว ต่อมาเราก็มาทำหน้า Login เพื่อเข้าสู่ระบบกันดีกว่า โดย flow ของมันคือ
- User กรอกข้อมูล Login แล้วกด Submit
- ข้อมูลถูกส่งไป API ด้วย
axios
แบบHTTP POST
ส่งusername
และpassword
ไป - Server รับข้อมูล (จริงๆ ต้อง query db, compare hash) แต่เราจะข้ามขั้น สมมติว่า username และ password ถูก เราก็จะ sign token (อาจจะเป็น JWT Token นะครับ) กลับมา
- เมื่อ response HTTP 200 ตัว Nuxt Auth มันก็จะเซฟ token ที่ได้ไว้ใน localStorage ให้เราเอง (กำหนด name หรือมี prefix ได้)
- ถ้าหน้าไหนที่มัน require auth คือต้อง login ตัว Nuxt Auth มันก็จะ request ไปที่
/me
(ที่เรากำหนดendpoints.user
ไว้ใน strategy) ถ้า response 200 คือปกติ ถ้าเป็นอย่างอื่นคือ unauthorized
Flow คร่าวๆ ก็ประมาณนี้
มาทำหน้า UI กัน ที่ไฟล์ pages/login.vue
เวลาที่ User submit เราจะเรียก auth login ด้วย $auth.loginWith()
นั่นเอง
Step 9 - Middleware
ทีนี้ถ้าเรา Login Success เราจะมีค่า store.state.auth.loggedIn
เป็น true
และมีค่า store.state.auth.user
ที่ได้จากตอน login และ request /me
นั่นเอง
ต่อมาก็ทำการกำหนด ว่าหน้าไหน ที่เราจะให้มัน required auth คือต้องเข้าสู่ระบบก่อนเท่านั้น
/blog/:slug
- หน้ารายละเอียดบล็อก เราจะบังคับให้ต้อง Login ก่อน/profile
- หน้า Profile ก็เช่นกัน ถ้าไม่ได้ login จะเข้าดูไม่ได้
ฉะนั้นก็แค่เพิ่ม middleware: 'auth'
ลงไปใน Page ที่เราต้องการ
ต่อมาเราเพิ่ม Middleware ตัวนึงเพื่อกันไม่ให้มัน render หน้า /login
ถ้าเรา login เข้าระบบแล้ว สร้างไฟล์ middlware/isLoggedIn.js
และก็ใส่ไว้ในหน้า pages/login
ซะ
ต่อมาปัญหาเรื่องหน้า /profile
นิดนึง เราเพิ่มส่วนนี้ลงไปใน asyncData
ซะ ที่ไฟล์ pages/profile.vue
เนื่องจากใช้ร่วมกับ Nuxt Auth ทุกๆ ครั้งที่เรียก
$axios
มันจะส่งheaders.authorization
ไปด้วย ทำให้เราต้อง ลบ header ก่อน เวลา request ไป Github API ครับ
Step 10 - เพิ่มหน้า Photos
เราลองเพิ่มหน้าเพิ่มดีกว่า ในการดึงข้อมูล API มี 2 หน้าคือ
/photos
- แสดงรูปทั้งหมด/photos/:id
- แสดงรูปตาม id ของรูป
โดย API เราก็ใช้แบบเดียวกันกับ ตอนที่ 6 - การดึงข้อมูลจาก APIs คือเว็บ https://picsum.photos/
รวมถึงไฟล์ pages/photos/index.vue
และ pages/photos/_id.vue
ก็แบบเดียวกันเลย
ขั้นตอนนี้ ผมอยากให้ผู้อ่าน ลองเพิ่มเองกันดูนะครับ หลักการเหมือนกับการใช้
asyncData
ในหน้าpages/profile.vue
เลย
เมื่อมีหน้าเพิ่ม เราก็ต้องไปเพิ่ม link ใน Navbar ให้มันด้วย ก็แก้ไข components/Navbar.vue
ซะหน่อย
Step 11 - ปรับ Navbar Toggle บน Mobile
เมื่อใช้ Bulma มันก็จะรองรับ Responsive Design อยู่แล้ว เมื่อเราเปิดบนมือถือ หรือหน้าจอขนาดเล็ก ตรง Navbar menu เรามันก็จะเปลี่ยนเป็น hamburger menu ให้เราเอง ทีนี้ เวลาใช้ Bulma เวลากด มันจะ trigger class is-active
โดยใช้ JavaScript แต่ใน Nuxt.js เราจะทำยังไงนะ?
วิธีก็ไม่ยาก เรากำหนด state ให้มันก่อน ที่ไฟล์ components/Navbar.vue
ดังนี้
ใน data
มี showNav
เป็น false
ไว้ก่อน แล้วเวลา menu ถูกคลิก เราก็ค่อยเปลี่ยนเป็น true
ก็แก้ไข template เป็นแบบนี้
ทีนี้ทุกครั้งที่ click มันก็จะ toggle true/false
ละ เราก็แค่เอามาเป็นเงื่อนไขในการ show class is-active
แบบนี้
Step 12 - ปรับ SEO และ Meta Tags
ต่อมาเรื่องการปรับ SEO เราจะกำหนด Meta tag global ไว้ที่ไฟล์ nuxt.config.js
และกำหนด Title และ description ในแต่ละ page รวมถึงหน้า Profile อาจจะให้เป็น dynamic title และ description ด้วยคครับ
ไฟล์ nuxt.config.js
เพิ่มไปเลย
ต่อมา ในแต่ละ Pages เราก็กำหนด head()
ให้มันซะ เช่นหน้า pages/index.vue
หรืออย่างหน้า Profile ก็ใช้ response จาก API มาเป็น title และ description แบบนี้
เราก็จะได้ Dynamic meta tag ตามที่เราต้องการแล้ว
Step 13 - เปลี่ยนมาใช้ Interal API
ตอนนี้เราใช้ API ที่รัน Server แยก เราอยากจะเปลี่ยนไปใช้ Internal API จะได้ไม่ต้องรันหลาย Server ครับ วิธีการก็แค่เพิ่ม serverMiddlware
ใน nuxt.config.js
ซะ
ต่อมา สร้างไฟล์ api/auth.js
และเอาโค๊ด server.js
มาใช้ได้เลย
ปรับแก้นิดหน่อย เป็นแบบนี้
ดูเพิ่มเติม ตอนที่ 10 - การทำ Internal API และ Middlware ครับ
สุดท้ายปรับแก้ baseURL
ของ axios
ใน nuxt.config.js
เป็น Server เดียวกัน เช่น http://localhost:3000/api
เรียบร้อยครับ!
Step 14 - Deploy ไป Heroku
สุดท้ายครับ การ Deploy เราจะใช้ Heroku กันเนื่องจากว่ามีการใช้ Internal Middleware ซึ่ง หากใครมี Server หรือ API แยกอยู่แล้ว เราสามารถ Deploy Nuxt.js ด้วย Netlify หรือ Github Pages ได้นะครับ ลองดู ตอนที่ 9 - การ Deploy Nuxt.js ครับ
สร้าง Create new App ในหน้า Heroku Dashboard เรียบร้อย
ให้เรามาที่ folder ของเรา จากนั้นทำการ init และ push ไป Heroku Server
เราจะได้ domain ที่ Heroku generate ให้ ลองเข้าเว็บดู จะไม่สามารถเข้าได้ เพราะ Heroku ไม่เจอ port และ hostname ครับ ต้องไปกำหนด environment variable ใน Setting -> Config Vars ครับ
HOST
ใช้เป็น0.0.0.0
NODE_ENV
ใช้เป็นproduction
เราก็จะได้เว็บที่รันบน Heroku แล้ว แต่ติดปัญหานิดนึงตรง api มันไม่สามารถเข้าถึงได้ด้วย http://localhost:3000/api เราต้องเปลี่ยนเป็น Url ของ Heroku ครับ (ดูจากหน้า Settings -> Domains)
Congratulations! 🎉🎉🎉
สรุป
หวังว่าบทความ Nuxt.js Fundamental ทั้ง 12 ตอน และ Workshop นี้จะเป็นประโยชน์สำหรับเพื่อนๆ พี่ๆ น้องๆ ที่สนใจ หรือกำลังศึกษา Nuxt.js นะครับ จริงๆ ก็รวมถึงคนที่ยังไม่เคยเขียน และอยากลองเขียน Nuxt.js หรือ Vue.js ด้วย เผื่อจะเห็นไอเดีย แนวทางในการเขียน และนำสิ่งที่ได้เรียนรู้ไปประยุกต์ใช้นะครับ
หากเนื้อหาส่วนไหน มีข้อผิดพลาด ตรงไหนอธิบายไม่เคลียร์ สามารถติชมกันได้นะครับ
ขอบคุณที่ติดตามอ่านครับ และหากใครชื่นชอบ ก็อย่าลืมแชร์ อย่าลืมบอกต่อเพื่อนๆ ด้วยนะครับ แล้วพบกัน Tutorial หน้าครับ
Happy Coding ♥️
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust