ทำระบบ Login/Register ด้วย React Router และ Firebase Auth (JavaScript)
ตัวอย่าง Workshop วิธีการทำระบบสมัครสมาชิก และเข้าสู่ระบบ โดยใช้ React.js ร่วมด้วย React Router และ Firebase Authentication โดยเนื้อหานี้ออกแบบมาสำหรับเรียนด้วยการลงมือทำ ฝึกฝน ตามตัวอย่างครับ อาจจะไม่ได้อธิบายลงลึกในแต่ละเนื้อหา ไม่จำเป็นต้องเข้าใจ 100% ครับ เน้นลงมือทำ ลงมือฝึกฝน และค่อยๆ เรียนรู้ไปครับ
สำหรับเนื้อหาใน workshop นี้จะแบ่งออกเป็น 2 เวอร์ชั่นคือ
- เนื้อหา React Router + Firebase Authentication (JavaScript) (บทความนี้)
- เนื้อหา React Router + Firebase Authentication (TypeScript)
ระดับความยาก: ⭐️⭐️
เหมาะสำหรับผู้ที่มีพื้นฐาน React มาบ้างแล้ว และอยากลองทำระบบ Login / Register รวมถึงเข้าใจขั้นตอนการจัดการ Client Routing
สิ่งที่จะได้รับ:
- สามารถทำระบบ Login/Register ด้วย Firebase Auth เบื้องต้นได้
- เข้าใจการกำหนด Routing แบบ Client ด้วย React Router
- การ Deploy โปรเจ็คขึ้น Vercel และ Firebase Hosting
- Source Code ของโปรเจ็ค เพื่อนำไปเรียนรู้ ทบทวน และฝึกฝน
ตัวเนื้อหาสำหรับ Pro Member นะครับ
ระบบที่เราจะสร้างวันนี้ คือ
- มีหน้าสมัครสมาชิก (Register)
- มีหน้าเข้าสู่ระบบ (Login)
- หน้า Dashboard ที่จะโชว์ข้อมูลเฉพราะเราเข้าสู่ระบบแล้วเท่านั้น เช่น แสดง Email ของผู้ใช้งาน
ตัวอย่าง




หน้าตาตัวอย่างโปรแกรมแบบง่ายๆ
โดยที่หน้า Dashboard หากไม่ได้เข้าสู่ระบบ จะไม่สามารถดูเนื้อหาได้
แบบ Video Clip สั้นๆ
เทคโนโลยีที่เกี่ยวข้อง
- Package Manager ที่ชอบ ในบทความใช้ pnpm (สามารถใช้ npm, yarn หรือ bun แทนได้เช่นกัน)
- React และ Vite
- React Router เวอร์ชั่น v.6.16.0 ณ ที่เขียนบทความ
- Firebase JavaScript SDK
- Vercel - สำหรับการ Deploy ตัวอย่างโปรเจ็ค
เนื้อหาในบทเรียนนี้
- Step 1 - เริ่มต้นสร้างโปรเจ็ค
- Step 2 - สร้างโปรเจ็คใน Firebase Console
- Step 3 - Setup Firebase SDK
- Step 4 - กำหนด Route ด้วย React Router
- Step 5 - ทำหน้า Register
- Step 6 - ทำหน้า Login
- Step 7 - Authentication และการเช็ค Session
- Step 8 - ทำหน้า Dashboard และ Home
- Step 9 - Build และ Deploy Production บน Vercel
Step 1 - เริ่มต้นสร้างโปรเจ็ค
เริ่มต้น เราจะสร้างโปรเจ็คขึ้นมาด้วย Vite กันนะครับ เริ่มจากเปิด Terminal พิมพ์คำสั่งนี้ลงไป (สามารถใช้ npm, pnpm, yarn หรือ bun ก็ได้)
pnpm create vite@latest
จากนั้นเลือกตั้งชื่อไฟล์, เลือก Library/Framework ตามนี้ (ผมตั้งชื่อว่า react-firebase-auth-js เพื่อนๆ สามารถตั้งชื่อโปรเจ็คได้ตามต้องการ)
✔ Project name: … react-firebase-auth-js
✔ Select a framework: › React
✔ Select a variant: › JavaScript
ทดสอบโปรเจ็คของเรา ว่าสร้างถูกต้องหรือไม่ โดยการไปที่โฟลเดอร์ที่สร้างขึ้นมา ทำการ install dependencies และลอง start dev server
cd react-firebase-auth-js
pnpm install
pnpm run dev
จะได้ผลลัพธ์ และหน้าเว็บ http://localhost:5174
VITE v4.4.5 ready in 188 ms
➜ Local: http://localhost:5174/
➜ Network: use --host to expose
➜ press h to show help
Step 2 - สร้างโปรเจ็คใน Firebase
เข้าหน้าเว็บ Firebase หากยังไม่มีบัญชีก็ต้องทำการสมัครสมาชิกของ Firebase ก่อน จริงๆ แล้วตัว Firebase มี Service ให้บริการมากมายครับ แต่สำหรับบทความนี้ เราจะพูดถึง และใช้งานแค่ Service เดียว นั่นก็คือ Firebase Authentication
จากนั้น เมื่อมีบัญชี Firebase แล้ว ให้เราเข้าไปที่หน้า Firebase Console ทำการสร้างโปรเจ็คขึ้นมาใหม่ (ตั้งชื่อ Project Name)

ตรง Google Analytics เราสามารถ turn off ได้ (แต่ถ้าใครต้องการ ก็กดเลือก Turn On แล้วก็ผูกกับบัญชี Google Analytics ของตัวเองได้ครับ)

ทำการกด Create a Project
เมื่อโปรเจ็คสร้างเสร็จแล้ว ให้กดเข้าไปหน้า Project จากนั้นเลือก ตรง Web ทำการ Register App

ต่อมา เราจะทำการเพิ่ม Firebase SDK ตามที่หน้า Firebase Console แนะนำ เลยครับ ก็คือติดตั้ง dependencies และสร้างไฟล์ config แบบภาพด้านล่าง

แหล่งอ้างอิงสำหรับ Firebase และ Firebase Authentication

Step 3 - Setup Firebase SDK
กลับมาที่ตัวโค๊ดของเรา หลังจากที่เราได้ config จากหน้า Firebase Console แล้ว ให้เรามาสร้างไฟล์ ชื่อ libs/firebase.js
ขึ้นมา จากนั้นเซฟ config ของเราลงไป (เพิ่ม export
เอาไว้ใช้ด้วย)
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app"
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "AI....",
authDomain: "....",
projectId: "....",
storageBucket: "....",
messagingSenderId: "....",
appId: "1:...."
}
// Initialize Firebase
const app = initializeApp(firebaseConfig)
export default app
ต่อมา เราจะย้าย พวกไฟล์ config ไปไว้เป็น .env
ไม่อยากใส่พวก apiKey ไว้ที่ตรงนี้ครับ เราสามารถย้ายไปไว้ที่ไฟลื .env
ได้ และตัว Vite ก็ซัพพอร์ตโดยที่เราไม่ต้องทำอะไรเพิ่มเลย เพียงแค่ต้องตั้งชื่อขึ้นต้นด้วย VITE_*
ในไฟล์ .env
ผมจะมีเป็นแบบนี้
VITE_FIREBASE_API_KEY=xxx
VITE_FIREBASE_AUTH_DOMAIN=xxx
VITE_FIREBASE_PROJECT_ID=xxx
VITE_FIREBASE_APP_ID=xxx
จากนั้น วิธีการโหลด env ของ vite จะใช้เป็น import.meta.env.<NAME>
ก็แก้ไขไฟล์ firebase.js
เป็นแบบนี้
// Import the functions you need from the SDKs you need
import { initializeApp } from 'firebase/app'
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
}
// Initialize Firebase
const app = initializeApp(firebaseConfig)
export default app
สังเกตว่าผมเอา storageBucket
และ messagingSenderId
เพราะว่ายังไม่ได้ใช้ในการทำ Firebase Auth นั่นเอง
ต่อมา ทำการติดตั้ง Firebase SDK เพื่อใช้ firebase ในโปรเจ็คของเรา:
pnpm install firebase
จากนั้น กลับไปหน้า Firebase Console อีกครั้ง เลือกตรง Authentication และทำการเลือก sign-in method ตัว Firebase Authentication รองรับการ sign-in หลายแบบครับ ทั้ง Facebook, Google, Twitter หรืออื่นๆ แต่ตัวอย่างนี้ เราจะใช้แค่แบบพื้นฐาน คือ Email/Password

เลือกเป็น Email/Password และ กด Enable และเซฟ

ทีนี้ เราเลือก service Firebase Authentication ฉะนั้น เราจะทำการเพิ่ม product ที่ไฟล์ libs/firebase.js
โดยเพิ่ม auth ลงไปแบบนี้
import { initializeApp } from "firebase/app"
// 1. เรียกใช้ service firebase/auth
import { getAuth } from "firebase/auth"
const app = initializeApp(firebaseConfig)
// 2. ใช้ service auth ผ่าน named import
export const auth = getAuth(app)
export default app
ตัว auth
นี้ ต่อไป เราจะใช้ในการเรียก function ของ Firebase ต่อไปครับ
Step 4 - กำหนด Route ด้วย React Router
ขั้นตอนนี้ คือการกำหนด Client Route เพื่อให้เว็บเราสามารถแสดงหน้า แต่ละหน้า แตกต่างกันได้ เช่นจะแบ่งเป็นหน้าแรก หน้าสมัครสมาชิก หน้าเข้าสู่ระบบ และหน้า Dashboard หลังจากเข้าสู่ระบบเรียบร้อยแล้ว
ทำการติดตั้ง React Router ด้วยคำสั่ง
pnpm install react-router-dom
ต่อมา จะเป็นการเพิ่มหน้าเพจ โดยสร้างเป็น component แยก ไว้ที่โฟลเดอร์ pages
(เป็นแค่ convention นะครับ ไม่ได้มีความหมายอะไร จะไว้ใน app
ใน components
ก็ได้)
โดยเราจะสร้างหน้าสมัครสมาชิกขึ้นมา เป็น url /register
โดยสร้างไฟล์ /src/pages/register.jsx
ขึ้นมา จากนั้นก็เป็นหน้าธรรมดาๆ ก่อน
import React from 'react'
const Register = () => {
return <p>Register</p>
}
export default Register
สร้างไฟล์สำหรับหน้า login เผื่อเอาไว้เช่นกัน ข้างในก็ยังเป็นแค่ skeleton ชื่อ src/pages/login.jsx
import React from 'react'
const Login = () => {
return <p>Login</p>
}
export default Login
สังเกตว่าทำไมผมตั้งชื่อ component เป็นตัวพิมพ์เล็ก? จริงๆ แล้วไม่มีกฎตายตัว สามารถตั้งregister.jsx
หรือRegister.jsx
ก็ได้ แต่ผลตั้งชื่อไฟล์ ตัวเล็กหมด เนื่องจาก บาง OS หรือ git มันจะเป็น case-insensitive (ค่า default) ตัวเล็ก ตัวใหญ่ มองเป็นไฟล์เดียวกัน ฉะนั้นก็เลยตั้งเป็นตัวเล็กหมด เพื่อตัดปัญหา เฉยๆ
กำหนด router ด้วย createBrowserRouter()
เพื่อระบุว่า path นั้นๆ จะทำการ render component อะไร
สามารถอ่านเรื่อง วิธีการเลือกใช้ Router ได้จากบทความนี้ได้ครับ

สร้างไฟล์ router.jsx
ขึ้นมา ข้างในกำหนด routing ไว้แบบนี้
import React from 'react'
import {
Route,
createBrowserRouter,
createRoutesFromElements,
} from 'react-router-dom'
import Login from './pages/login'
import Register from './pages/register'
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/" element={<p>Home</p>} />
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
</>
)
)
export default router
โดยตอนนี้เรากำหนด routes ไว้ 3 หน้าคือ
/
- หน้า Home/login
- คือหน้า สำหรับเข้าสู่ระบบ/register
- คือหน้าสำหรับสมัครสมาชิก
จากนั้นที่ไฟล์ App.jsx
เราจะลบโค๊ด default ทั้งหมด และเหลือไว้แค่นี้
import { RouterProvider } from 'react-router-dom'
import './App.css'
import router from './router'
function App() {
return <RouterProvider router={router} fallbackElement={null} />
}
export default App
ทดสอบด้วยการเข้าหน้าเว็บ http://localhost:5173/ , http://localhost:5173/login และ http://localhost:5173/register เพื่อดูผลลัพธ์ ต้องเห็นหน้าตาเว็บ ที่เป็น default component ที่เราทำไว้ แสดงว่าเราตั้งค่า Router ถูกต้องแล้ว

Step 5 - ทำหน้า Register
ปรับแต่งหน้า Register เพิ่ม Markup และ Style ลงไป (ขั้นตอนนี้ผมจะไม่ได้อธิบายรายละเอียดนะครับ ว่ากำหนด div กำหนด style ยังไงบ้าง เราจะโฟกัสกันที่ตัว React และ การทำงานของมันเนอะ ส่วนนี้เพื่อนๆ สามารถปรับแต่ง ได้ตามความเหมาะสม หรือปรับตามที่ตัวเองต้องการได้ เช่นกัน)
สำหรับไฟล์ css ในโปรเจ็ค ใช้ที่มากับ default ของ vite นะครับ คือ App.css
และ index.css
ตัวไฟล์ App.css
ลบไปเกือบหมด เหลือไว้แค่นี้
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
และไฟล์ index.css
ครับ มีเพิ่มเติมจาก default ไปนิดหน่อย ตรง .form-container
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 2em;
width: 100%;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
/* Login & Register */
.form-container {
display: flex;
max-width: 840px;
margin: 0 auto;
flex-direction: column;
}
.form-container .container {
display: flex;
flex-direction: column;
width: 240px;
row-gap: 1.5rem;
text-align: left;
}
.form-container input {
padding: 0.5rem;
}
.error-message {
color: #ff4643;
}
ทีนี้ ในส่วนโค๊ด pages/register.jsx
ผมอธิบาย เพิ่มเติม และดูโค๊ดด้านล่างประกอบนะครับ
- มี
<form>
เมื่อกด submit เราจะก็ไปเรียก functionhandleSubmit
เพื่อ ไปเรียก function ของ firebase auth อีกที - ส่วนของ
<input>
มีแค่ 2 ตัว คือ Email และ Password โดยใช้ เป็นแบบ uncontrolled form ด้วยการใช้React.useRef
import { useRef } from 'react'
const Register = () => {
const emailRef = useRef()
const passwordRef = useRef()
const handleSubmit = async (e) => {
e.preventDefault()
}
return (
<form onSubmit={handleSubmit} className="form-container">
<h2>สมัครสมาชิก</h2>
<div className="container">
<label htmlFor="username">Email</label>
<input ref={emailRef} type="email" name="username" required />
<label htmlFor="password">Password</label>
<input ref={passwordRef} type="password" name="password" required />
<button type="submit">Register</button>
</div>
</form>
)
}
export default Register
- ใช้
useRef()
เป็นemailRef
และpasswordRef
กับ<input ref={}
เพื่อเอาไปใช้อ้างอิง ในส่วนhandleSubmit
- ในส่วน
handleSubmit
เนี่ย เราจะเรียก function ของ Firebase Auth สำหรับ register คือ function ชื่อว่าcreateUserWithEmailAndPassword
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { auth } from '../libs/firebase'
// เรียก function ของ firebase โดยส่ง auth, email และ password ไป
await createUserWithEmailAndPassword(auth, email, password)
สังเกตเห็นว่า เราสามารถเรียก function ของ Firebase Auth ได้ 2 แบบคือ
แบบ Modular ส่งค่า auth
เป็น argument ตัวแรก
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
แบบ Namespace API - เรียก firebase.auth()
ได้เลย
firebase.auth().createUserWithEmailAndPassword(email, password)
ข้อดีของ Modular คือตัวโปรเจ็คมีขนาดเล็กกว่าในขณะที่ตัว namespace จะเป็น api แบบเก่า และใช้มานานแล้ว มีขนาดใหญ่ เพราะรวมทุกๆ feature เลย
import { useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { auth } from '../libs/firebase'
const Register = () => {
const emailRef = useRef()
const passwordRef = useRef()
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
const email = emailRef.current.value
const password = passwordRef.current.value
// 1. ทำการสร้าง user จาก email, password
await createUserWithEmailAndPassword(auth, email, password)
// 2. ถ้า success จะ redirect ไปหน้า /dashboard.
navigate('/dashboard')
}
return (
<form onSubmit={handleSubmit} className="form-container">
<h2>สมัครสมาชิก</h2>
<div className="container">
<label htmlFor="username">Email</label>
<input ref={emailRef} type="email" name="username" required />
<label htmlFor="password">Password</label>
<input ref={passwordRef} type="password" name="password" required />
<button type="submit">Register</button>
</div>
{errorMessage && <p className="error-message">{errorMessage}</p>}
</form>
)
}
export default Register
ทดสอบ ลองเข้าหน้า Register และใส่ Email พร้อมตั้งรหัสผ่าน ลองกด Submit ดู Console log จะเห็นผลลัพธ์แบบนี้
_UserCredentialImpl {user: _UserImpl, providerId: null, _tokenResponse: {…}, operationType: 'signIn'}
และลองเข้าไปดูข้อมูลของเราที่ Firebase Console จะเห็นข้อมูลของเราอยู่ในนี้แล้ว

ลองทดสอบ สมัครสมาชิก ด้วย Email เดิม และดูว่าจะมี Error อะไรเกิดขึ้น?
จะได้ Error ว่า
FirebaseError: Firebase: Error (auth/email-already-in-use).
ต่อมา เราจะ handle state หลังจาก register เรียบร้อยแล้ว 2 กรณีคือ
- กรณี มี Error ก็จะแสดง Error ให้ User ได้รู้
- กรณีสมัครเรียบร้อย จะถูก redirect ไปหน้า Dashboard (หน้านี้ยังไม่ได้ทำ)
const [errorMessage, setErrorMessage] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
const email = emailRef.current.value
const password = passwordRef.current.value
try {
await createUserWithEmailAndPassword(auth, email, password)
navigate('/dashboard')
} catch (error) {
console.log(error)
setErrorMessage(error.message)
}
}
เมื่อมีข้อมูลเวลาสมัครสมาชิก ต่อไป ลองไปดูเรื่อง Login กันต่อเลย
สำหรับคนที่อาจจะตามไม่ทัน ตอนนี้ไฟล์ของ pages/register.jsx
เป็นแบบด้านล่างนะครับ
import { useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { auth } from '../libs/firebase'
const Register = () => {
const emailRef = useRef()
const passwordRef = useRef()
const [errorMessage, setErrorMessage] = useState('')
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
const email = emailRef.current.value
const password = passwordRef.current.value
try {
await createUserWithEmailAndPassword(auth, email, password)
navigate('/dashboard')
} catch (error) {
console.log(error)
setErrorMessage(error.message)
}
}
return (
<form onSubmit={handleSubmit} className="form-container">
<h2>สมัครสมาชิก</h2>
<div className="container">
<label htmlFor="username">Email</label>
<input ref={emailRef} type="email" name="username" required />
<label htmlFor="password">Password</label>
<input ref={passwordRef} type="password" name="password" required />
<button type="submit">Register</button>
</div>
{errorMessage && <p className="error-message">{errorMessage}</p>}
</form>
)
}
export default Register
Source Code สำหรับ Part 1
Source Code