Next.js API Routes: สร้าง API ในโปรเจกต์เดียว
โค้ดเดียวจบนะ
การมาของ Next.js API Routes เนี่ย คือตอบโจทย์สายที่อยากเขียนอะไรจบ ๆ ในโปรเจกต์เดียว ไม่ต้องมานั่งแยก Git repo backend อีกอัน frontend อีกอัน ให้วุ่นวาย
ส่วนตัวชอบมาก เพราะมันทำให้ dev experience ดีขึ้นเยอะ ไม่ต้อง context switch บ่อยๆ ระหว่าง backend framework กับ frontend framework
หลักการง่ายๆ
ไฟล์ทุกอย่างที่อยู่ในโฟลเดอร์ pages/api (หรือ app/api ใน App Router นะ) จะถูกมองว่าเป็น Endpoint ของ API มันรันบนฝั่ง Server-side (เป็น serverless function ตอน deploy บน Vercel หรือ AWS Lambda อะไรพวกนี้แหละ)
ง่ายไหมละ ลองดูตัวอย่างกันเลย
เริ่มต้นโค้ดกัน
สมมติว่าเราอยากสร้าง API ง่ายๆ ที่คืนค่า สวัสดีชาวโลก
- สร้างไฟล์:
pages/api/hello.js(สำหรับ Pages Router) หรือapp/api/hello/route.js(สำหรับ App Router)
ใส่โค้ดนี้เข้าไป (ตัวอย่าง Pages Router):
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'สวัสดีชาวโลก!' });
}
ถ้าเป็น App Router, จะหน้าตาแบบนี้:
// app/api/hello/route.js
export async function GET(request) {
return new Response(JSON.stringify({ name: 'สวัสดีชาวโลก!' }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
แค่นั้นแหละ! ลองเข้า http://localhost:3000/api/hello ตอนรัน dev server ดูดิ ก็จะเห็น JSON ก้อนนั้นเลย
รับ Parameter ด้วยนะ
อยากรับค่าจาก Query string หรือ Body ก็ทำได้ง่ายๆ
ถ้าเป็น Query String ก็ใช้ req.query (Pages Router) หรือ request.nextUrl.searchParams (App Router) ถ้าเป็น Body ก็ใช้ req.body (Pages Router) หรือ request.json() (App Router) (แต่ต้องมี middleware ช่วย parse นะ ถ้าเป็น Next.js มัน handle ให้เราอยู่แล้ว)
ตัวอย่าง รับชื่อคนจาก Query string (Pages Router):
// pages/api/greet.js
export default function handler(req, res) {
const { name } = req.query;
if (name) {
res.status(200).json({ message: `สวัสดีครับ, ${name}!` });
} else {
res.status(400).json({ error: 'ต้องมีชื่อนะจ๊ะ' });
}
}
ลองเรียกดู http://localhost:3000/api/greet?name=วิทย์
การจัดการ Method (GET, POST, etc.)
ถ้าอยากให้ API Route รองรับแค่บาง Method ก็เช็ค req.method ได้เลย (Pages Router) หรือใช้ function ชื่อตรงๆ ได้เลย (App Router)
ตัวอย่าง Pages Router:
// pages/api/products.js
export default function handler(req, res) {
if (req.method === 'GET') {
// Logic สำหรับดึงข้อมูลสินค้า
const products = [
{ id: 1, name: 'เสื้อยืด', price: 299 },
{ id: 2, name: 'กางเกงยีนส์', price: 999 }
];
res.status(200).json(products);
} else if (req.method === 'POST') {
// Logic สำหรับเพิ่มสินค้า
const { name, price } = req.body;
if (!name || !price) {
return res.status(400).json({ error: 'ชื่อกับราคาต้องมีนะ' });
}
// สมมติว่าเพิ่มลง DB สำเร็จ
res.status(201).json({ message: 'เพิ่มสินค้าเรียบร้อย', product: { name, price, id: Date.now() } });
} else {
// ถ้าไม่ใช่ GET หรือ POST
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
ตัวอย่าง App Router (ง่ายกว่าเยอะ):
// app/api/products/route.js
export async function GET() {
const products = [
{ id: 1, name: 'เสื้อยืด', price: 299 },
{ id: 2, name: 'กางเกงยีนส์', price: 999 }
];
return new Response(JSON.stringify(products), { status: 200, headers: { 'Content-Type': 'application/json' } });
}
export async function POST(request) {
const { name, price } = await request.json();
if (!name || !price) {
return new Response(JSON.stringify({ error: 'ชื่อกับราคาต้องมีนะ' }), { status: 400, headers: { 'Content-Type': 'application/json' } });
}
return new Response(JSON.stringify({ message: 'เพิ่มสินค้าเรียบร้อย', product: { name, price, id: Date.now() } }), { status: 201, headers: { 'Content-Type': 'application/json' } });
}
นี่คือตัวอย่างง่ายๆ ที่เอาไว้ทำ CRUD ได้เลย.
เจอ Error แล้วปวดหัวไหม?
เออ ก็มีบ้างนะ โดยเฉพาะตอนแรกๆ ที่งงว่าทำไม req.body (Pages Router) หรือ request.json() (App Router) มันเป็น undefined หรือ Object เปล่าๆ ทั้งที่ก็ POST ไปแล้ว
ส่วนใหญ่จะลืม Content-Type: application/json ใน Header ตอนส่ง request จากฝั่ง client หรือบางทีก็ลืม JSON.stringify() body ก่อนส่ง
// ตัวอย่างเรียกใช้จากฝั่ง Frontend (สมมติเป็น React component)
const addProduct = async () => {
const newProduct = { name: 'รองเท้า', price: 1500 };
try {
const response = await fetch('/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // สำคัญมากนะอันนี้!
},
body: JSON.stringify(newProduct)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Something went wrong');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to add product:', error.message);
}
};
ถ้าลืม JSON.stringify(newProduct) หรือ Content-Type เนี่ย ตัว req.body ที่ฝั่ง API Route มันจะว่างเปล่า หรือเป็น Object เปล่าๆ เลย นั่งหาตั้งนานว่าทำไมมันไม่มา.
ข้อจำกัดที่ควรรู้
- Serverless Function: แต่ละ API Route คือ Function แยกกัน ถ้ามันใหญ่มาก ๆ หรือต้องรันนาน ๆ อาจจะต้องคิดดีๆ เพราะมันมี timeout
- Stateful ไม่ได้: มันเป็น stateless function อ่ะ คือทำงานเสร็จก็ตายไปเลย จะเก็บข้อมูลอะไรไว้ข้าม request ไม่ได้นะ
- Logic ซับซ้อน: ถ้า logic backend เริ่มซับซ้อนมาก ๆ มีหลายส่วนต้องคุยกันเยอะๆ เนี่ย การใช้ API Routes อาจจะเริ่มไม่เหมาะละ ควรจะแยกเป็น Microservice หรือ Backend เต็มตัวไปเลยดีกว่า
สรุปง่ายๆ คือ Next.js API Routes มันเจ๋งมากถ้างานเราไม่ได้ใหญ่โตอลังการ ต้องการความเร็วในการพัฒนา และอยาก deploy ง่าย ๆ แต่ถ้าโปรเจกต์ใหญ่ขึ้นเรื่อย ๆ ก็ต้องพิจารณาอีกที
ลองเอาไปใช้ดูนะ โคตรสะดวกเลย!