ตอนที่ 7 - API Routes การทำ API ด้วย Next.js

Published on

เขียนวันที่ : July 16, 2022

Discord

API Routes

ใน Next.js เราสามารถทำตัวเป็น Backend APIs ได้เลย โดยที่ไม่ต้องมี Server เพิ่ม (ปกติอาจจะต้องมี Node.js server อีกตัว) โดยเพียงแค่สร้างโฟลเดอร์ api ไว้ภายใน pages ก็พอครับ โดยไฟล์ /pages/api จะ map กับ /api/* ตามชื่อไฟล์ที่เราตั้ง เช่น

  • pages/api/posts.js - เพื่อเอาไว้ get posts ทั้งหมด โดยเรียกด้วย GET /api/posts
  • pages/api/posts/[id].js - เพื่อดึง post ตาม id มาแสดง โดยเรียกด้วย GET /api/posts/:id

ซึ่งจริงๆแล้ว ไม่ได้เฉพาะแค่ GET สามารถรับ HTTP Methodds อื่นๆ ได้ด้วยเช่นกัน

ตัวอย่าง ผมสร้างไฟล์ api/user.js ขึ้นมา ให้มัน return JSON กลับไป ปกติ

pages/api/user.js
export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}

ซึ่งถ้าเราดูมันก็จะคล้ายๆการเขียนด้วย Express.js ครับ

const express = require('express')
const app = express()

const handler = (req, res) => {
  res.status(200).json({ name: 'John Doe'})
}

app.get('/api/user', handler)

โดยที่ req และ res ก็จะเป็น Client Request และ Server Response

เช่น เราจะเช็คว่า request ที่ส่งมาที่ API ของ Next.js เราเนี่ย เป็น HTTP Method อะไร ก็เช็คแบบนี้

export default function handler(req, res) {
  if (req.method === 'POST') {
    // post
  } else if (req.method === 'PATCH') {
    // patch
  } else {
    // others
  }
}

หรือจะเช็ค POST body/payload ก็ได้เช่นกัน

export default function handler(req, res) {
  const { username, email } = req.body;
  // your logic
}
  • API โดย default แล้ว จะเป็นแบบ same-origin คือเฉพาะ domain เดียวกันที่เรียก API ได้ ไม่สามารถทำ API และให้ domain อื่นๆ มาเรียกใช้ได้ (แต่สามารถ custom ได้ผ่าน CORS Middleware)
  • API Routes ไม่สามารถใช้กับ next export ได้
  • ข้อดีของ API Route คือ เอาไว้ทำ API เวลาที่ต้องใช้ token ต่อกับ 3rd Party (ถ้าเรียกจาก Client ก็จะเห็น token ใช่มั้ยครับ)

Dynamic API Routes

ตัว API Routes ก็รองรับ Dynamic Routes แบบเดียวกับ Page component เลย คือตั้งชื่อไฟล์เช่น pages/api/posts/[id].js

pages/api/posts/[id].js
export default function handler(req, res) {
  const { id } = req.query
  res.json({
    ok: true,
    id,
  })
}

ทีนี้ เวลาเราเรียก /api/posts/* มันก็จะได้ response เป็น id ที่แตกต่างกัน

นอกจากนี้ Dynamic API Routes ก็สามารถทำ catch all api routes ได้เหมือนกับ Page Component เช่นกัน คือกำหนดไฟล์ไว้แบบนี้ [...id].js มันก็จะ match ทั้ง /api/post/a, /api/post/a/b, /api/post/a/b/c ไปเรื่อยๆ

API Middlewares

Next API Route สามารถใช้ middleware ได้เหมือนกับ Express.js ทุกๆ API Route เราสามารถ export config object ได้ ตัวอย่าง

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
}

ตัวอย่าง เช่น การใช้ CORS Middleware

ติดตั้ง CORS

yarn add cors

ตัวอย่าง allow method GET และ HEAD

import Cors from 'cors'

// Initializing the cors middleware
const cors = Cors({
  methods: ['GET', 'HEAD'],
})

// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }

      return resolve(result)
    })
  })
}

async function handler(req, res) {
  // Run the middleware
  await runMiddleware(req, res, cors)

  // Rest of the API logic
  res.json({ message: 'Hello Everyone!' })
}

export default handler

อ่านเพิ่มเติม - Next.js - API Middlewares

ลองสร้าง API Posts

ลองสร้าง API สำหรับ provide ข้อมูล blog posts สร้างไฟล์ชื่อ pages/api/posts.js และ pages/api/posts/[id].js

ซึ่งจริงๆแล้ว การที่สร้าง index กับ id ที่เป็น dynamic id เราสามารถสร้างได้ 2 แบบครับคือ

/api/posts.js และ /api/posts/[id].js

หรือ

/api/posts/index.js และ /api/posts/[id].js

โดยไฟล์ pages/api/posts.js มีข้อมูลแบบนี้

pages/api/posts.js
const posts = [
  {
    id: 1,
    title: 'Post #1',
    content: 'lorem ipsum 1',
  },
  {
    id: 2,
    title: 'Post #2',
    content: 'lorem ipsum 2',
  },
]

export default function handler(req, res) {
  res.status(200).json(posts)
}

ส่วนไฟล์ pages/api/posts/[id].js เป็นแบบนี้

pages/api/posts/[id].js
export default function handler(req, res) {
  const {
    query: { id },
    method,
  } = req

  const { title } = req.body

  switch (method) {
    case 'GET':
      res.status(200).json({ id, title: `Post #${id}` })
      break
    case 'PUT':
      res.status(200).json({ id, title: title || `Post #${id}` })
      break
    default:
      res.setHeader('Allow', ['GET', 'PUT'])
      res.status(405).end(`Method ${method} Not Allowed`)
  }
}

โดยที่เรามี logic เช็คว่า รองรับแค่ GET และ PUT ถ้ามีการเรียก POST มา ก็จะ return 405 กลับไป พร้อมข้อความ Method POST Not Allowed

ทดสอบเรียก http://localhost:3000/api/posts และ http://localhost:3000/api/posts/1 ดูครับ

จบแล้วสำหรับเรื่อง API Routes ซึ่งหลักๆ การเรียนรู้ Next.js เบื้องต้น ก็มีประมาณเท่านี้ครับ ตอนต่อไป จะเป็นหัวข้อเสริม คือเรื่องของการใช้ Next.js ร่วมกับ TypeScript ครับ

คำถาม

  • ถ้าเราจะ fetch api จาก 3rd party ระหว่างใช้ API Routes กับ getServerSideProps ต่างกันมั้ย?
  • ข้อดีของการใช้ API Routes คืออะไร?
  • มี use cases ไหนบ้าง ที่น่าใช้ API Routes แทนที่จะใช้ getStaticProps หรือ getServerSideProps
Discord