iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Modern Web

用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄系列 第 19

跨域的絆腳石:CORS 問題與後台 API 串接

  • 分享至 

  • xImage
  •  

Day 19:跨域的絆腳石 —— CORS 問題與後台 API 串接

昨天我們在後端導入了 JWT,讓 Admin 後台登入流程更安全。

但當前端開始呼叫後端 API(如 /orders)時,很多人會遇到 CORS 錯誤

今天先解釋什麼是 CORS,為什麼會出錯,並提供三種常見解法。

最後,我們會示範如何在後台串接 API,顯示訂單列表。


什麼是 CORS?

  • 同源政策 (Same-Origin Policy):瀏覽器限制不同來源(協定 + 網域 + 埠號)的請求。

  • 當前端 (http://localhost:5173) 要打後端 (http://localhost:3000) → 這就是跨來源。

  • 瀏覽器會先送 預檢請求 (OPTIONS),伺服器必須回應允許的標頭,否則正式請求會被擋。


CORS 錯誤長什麼樣?

  • Console 紅字:

    Access to fetch ... has been blocked by CORS policy
    
  • 意思:伺服器沒有回 Access-Control-Allow-Origin 標頭。


解決 CORS 的三種方式

方式 A:後端設定 cors middleware

const cors = require("cors");
app.use(cors({
  origin: ["http://localhost:5173"],
  methods: ["GET","POST","PUT","DELETE"],
  allowedHeaders: ["Content-Type","Authorization"]
}));

方式 B:Vite 開發代理 (proxy)

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/api/, "")
      }
    }
  }
})
  • 前端呼叫:
axios.get("/api/orders")

方式 C:同源托管

  • 用 Express express.static 提供前端頁面 + API。

  • 前端直接呼叫相對路徑 /orders,不存在跨域問題。


我們選擇哪個?

  • 建議先用 方式 A (cors middleware),最直觀。

  • 進階讀者可用 B 或 C。


API 串接:訂單列表

後端 (routes/order.js)

const express = require("express");
const router = express.Router();
const { authAdmin } = require("./auth");
const Order = require("../models/order.model");

router.get("/", authAdmin, async (req, res) => {
  const orders = await Order.find().sort({ createdAt: -1 });
  res.json({ success: true, orders });
});

module.exports = router;

前端 (pages/Dashboard.jsx)

import { useEffect, useState } from "react";
import axios from "axios";

export default function Dashboard() {
  const [orders, setOrders] = useState([]);

  useEffect(() => {
    const token = localStorage.getItem("jwt");
    axios.get("http://localhost:3000/orders", {
      headers: { Authorization: `Bearer ${token}` }
    })
    .then(res => setOrders(res.data.orders))
    .catch(err => console.error(err));
  }, []);

  return (
    <div>
      <h1>訂單管理</h1>
      <ul>
        {orders.map(o => (
          <li key={o._id}>
            {o.items.map(i => i.productName).join(", ")} - {o.status}
          </li>
        ))}
      </ul>
    </div>
  );
}

介面成果

成功登入之後就會被導向到 dashboard 的 router,再來就能看到目前訂單的狀況啦!

https://ithelp.ithome.com.tw/upload/images/20251003/20178868NCfUAZGO0t.png


補充:什麼是預檢請求 (OPTIONS)

當瀏覽器發送跨域、且不屬於「簡單請求」的情況(例如 Content-Type: application/json 或帶 Authorization 標頭)時,會先送一個 OPTIONS 請求到伺服器,詢問:

  • 允許哪些來源 (Access-Control-Allow-Origin)

  • 允許哪些方法 (Access-Control-Allow-Methods)

  • 允許哪些自訂標頭 (Access-Control-Allow-Headers)

如果伺服器沒有正確回應這些標頭,瀏覽器就會擋下真正的 POST/PUT/DELETE 請求。這就是為什麼我們需要在 Express 裡加上:

app.options("*", cors());

來處理所有的預檢請求。


重點回顧

  • CORS 是開發中最常遇到的坑,記得從伺服器或代理處理。

  • 後台 Dashboard 現在能顯示訂單列表,之後可以加上「修改訂單狀態」功能。

明天(Day 20)我們將實作 訂單狀態修改,讓後台可以直接改 Pending → In Progress → Completed。


上一篇
讓登入更安全:JWT 與 Session 的導入
下一篇
訂單狀態修改:後台操作訂單流程
系列文
用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言