連假最後一天!分享了一些關於 Next.js 這個框架,雖然他是 React 的框架,可是你們知道嗎?Next.js 也可以同時實作後端喔!沒錯,前後端全包~ 怎麼做呢?今天來看看 Next.js 的 API Routes,看完應該會有想法怎麼實作,不過看這篇文章之前,可以先看看上一篇關於 Next.js 的 routing 喔!

Next.js 的 API routes 也採用 file-based routing,意思是每個檔案代表一個 route,而檔案名稱 (file name) 會變成 route 的 path name (路徑名稱)。Next.js 會把 pages/api 資料夾 / 目錄裡面的所有檔案看成 route (路徑) 的一部分:
pages/api/index.js 代表 / API routepages/api/user.js 代表 /user API routeAPI routes 裡的所有程式碼是屬於後端的部分,所以只會跑在伺服器端。這代表我們寫的 API 不會讓我們前端的 bundle size 變大,不會影響網頁的下載時間!也是因為是跑在伺服器端,我們可以實作各種後端的功能,例如操作資料庫~
每個 pages/api 裡的檔案需要 export 一個 default function (函式) 作為 API 的 request handler (處理請求的函式),然後每個 handler 會有兩個 parameters:
req:request 物件,是一種 http.IncomingMessage,還有包含一些內建的 middlewares
res:response 物件,是個 http.ServerResponse,加上一些 helper functions
像 Next.js 的 routing 一樣,API routes 也支援 dynamic (動態) routing。如果我們對 pages/api/posts/[pid]/comments/[cid].js 路徑發個 /api/posts/world/comments/hello 的請求:
export default function handler(req, res) {
  const { pid, cid } = req.query
  res.end(`Comment #${cid} for post #${pid}`)
}
上面的 API 會回傳 Comment #hello for post #world。
每個 index 檔案代表根目錄,所以
pages/api/posts.js 代表 /api/posts API routepages/api/posts/index.js 也代表 /api/posts API route注意:
pages/api/posts/[pid].js並不代表/api/posts只代表/api/posts/1,/api/posts/2等
屬於 dynamic API routes 的一種,不過 catch all 的意思是不需要一個一個定義動態的部分,把所有動態的資訊抓起來,讓 route 比較彈性,寫法跟其餘參數(rest parameter)一樣。
pages/api/posts/[...slug].js 可以代表 /api/posts/a 也可以是 /api/posts/a/b,/api/posts/a/b/c 等slug 可以是 param 或是其他東西都可以,這些動態資訊也會出現在 query 物件裡面,例如 /api/posts/a/b/c 的 query 會是:
{ "slug": ["a", "b", "c"] }
跟 pages 的 optional catch all routes 一樣,只要把 catch all 的 [...slug] 改成 [[...slug]] 就會變成 optional catch all。意思是 slug 這個 array (陣列) 可以是不存在的,
pages/posts/[[...slug]].js 可以代表 /posts/a 也可以是 /posts/a/b,/posts/a/b/c 等,不過也可以代表 /posts
所以當我們發請求到 /api/posts,query 物件會是空物件
{ } // GET `/api/post` (空物件)
{ "slug": ["a"] } // `GET /api/post/a`
{ "slug": ["a", "b"] } // `GET /api/post/a/b`
Next.js 有提供內建的 middlewares 塞進去 req 物件裡,除了內建的之外,我們還可以用其他 Connect / Express 相容的 middlewares。
req.cookies:請求的 cookies,預設為 {}
req.query:請求的 query string,預設為 {}
req.body:已經被 parse 的請求 body,會根據 content-type 做 parse,當沒有 body 時會回傳 null。如果不想要 body 被 parse,可以在 API route 檔案裡加 config:
export const config = {
  api: {
    bodyParser: false,
  },
}
除了 body parser 的 config 以外,還有更多 API 相關的 config 可以看這裡喔~
想要在我們的 API routes 增加其他 middlewares 也可以!直接在 handler function 裡執行該 middleware 再繼續跑 API 本身的 code,像:
import Cors from 'cors'
// initialize cors middleware
const cors = Cors({
  methods: ['GET', 'HEAD'],
})
// helper method 去執行 middleware,結束後才繼續跑下去
// 發生錯誤時跳 error
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    // fn 為 middleware
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }
      return resolve(result)
    })
  })
}
async function handler(req, res) {
  // 先跑 middleware
  await runMiddleware(req, res, cors)
  // API 本身的邏輯
  res.json({ message: 'Hello Everyone!' })
}
export default handler
雖然 API routes 不是 Express.js,可是 Next.js 有提供一些類似的 helper functions 來幫助我們開發而提升 DX (開發者體驗):
res.status(code):設定 response 的 status code (要符合 HTTP status code)res.json(body):回傳 JSON 為 response 給瀏覽器 (客戶端)res.send(body):回傳 HTTP response,body 可以是 string,object,或是 Buffer
res.redirect([status,] path):導頁,把瀏覽器導到別 path 或 URL,status 是 HTTP status code,是個非必須的 parameter,預設為 307 (Temporary redirect).我們看完 API routes 之後,可以開始做一些全端專案了(誤
大家會想用 API routes 做哪些事呢?有了這個真的讓前後端開發簡單一些~
祝大家明天上班上課愉快!
晚安 <3