You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复Express应用中预检请求未通过CORS检查的跨域警告

解决二手书店项目的CORS跨域错误问题

问题背景

开发学校项目二手书店网站,使用Aiven数据库、Vercel部署,Vanilla JS编写前端。调用API获取数据时完全失败,浏览器控制台提示:preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header,尝试多种方法未解决。

错误原因分析

从提供的server.js代码来看,核心问题集中在CORS配置错误和中间件执行顺序混乱:

  • CORS的origin固定为http://localhost:3001,但前端部署在Vercel,线上请求的域名并非localhost,导致跨域校验失败。
  • CORS中间件注册在静态文件路由之后,部分请求可能未经过CORS处理。
  • app.listen()之后注册的请求日志中间件永远不会执行,无法排查请求头问题。
  • 重复调用connection.connect(),可能导致数据库连接异常。

具体修复步骤

1. 动态配置CORS允许的域名

针对本地开发和线上Vercel部署两种场景,动态允许请求来源:

const corsOptions = {
  origin: function (origin, callback) {
    // 允许本地开发和Vercel线上域名,可根据实际情况添加其他域名
    const allowedOrigins = [
      'http://localhost:3001',
      'https://bookback-i1o4juwqu-mohammed-aayan-pathans-projects.vercel.app'
    ];
    // 允许无origin的请求(比如Postman测试)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true // 如果前端使用credentials: 'include',必须开启这个
};

2. 调整中间件顺序

将CORS中间件、dotenv配置放在最前面,确保所有请求都经过CORS处理:

require('dotenv').config();
const express = require('express');
const mysql = require('mysql2');
const path = require('path');
const cors = require('cors');
const fs = require('fs');

const fetch = (...args) =>
    import("node-fetch").then(({ default: fetch }) => fetch(...args));

const app = express();
const PORT = process.env.PORT || 3001;

// 先注册CORS中间件
const corsOptions = { /* 上面的配置 */ };
app.use(cors(corsOptions));

// 再注册静态文件和路由
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

3. 移除重复的数据库连接

删除多余的connection.connect()调用,只保留一次:

// 只保留这一次连接
connection.connect(err => {
    if (err) {
        console.error('Database connection error:', err);
        return;
    }
    console.log('Connected to the database');
});

4. 移动日志中间件到listen之前

将请求日志中间件放在所有路由之前、CORS之后:

// 请求日志中间件放在CORS之后,路由之前
app.use((req, res, next) => {
    console.log(`Incoming Request: ${req.method} ${req.url}`);
    console.log('Request Headers:', req.headers);
    next();
});

// 最后启动服务器
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

5. 前端代码调整(可选)

如果后端配置了credentials: true,确认前端的fetch请求credentials: 'include'是必要的;如果不需要携带Cookie,可以去掉这个配置,简化CORS设置。

修改后的完整server.js代码

require('dotenv').config();
const express = require('express');
const mysql = require('mysql2');
const path = require('path');
const cors = require('cors');
const fs = require('fs');

const fetch = (...args) =>
    import("node-fetch").then(({ default: fetch }) => fetch(...args));

const app = express();
const PORT = process.env.PORT || 3001;

// CORS配置
const corsOptions = {
    origin: function (origin, callback) {
        const allowedOrigins = [
            'http://localhost:3001',
            'https://bookback-i1o4juwqu-mohammed-aayan-pathans-projects.vercel.app'
        ];
        if (!origin || allowedOrigins.includes(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true
};
app.use(cors(corsOptions));

// 请求日志中间件
app.use((req, res, next) => {
    console.log(`Incoming Request: ${req.method} ${req.url}`);
    console.log('Request Headers:', req.headers);
    next();
});

// 静态文件和主页路由
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 数据库连接
const connection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
    ssl: {
        rejectUnauthorized: false,
        ca: fs.readFileSync(process.env.SSL_CA),
    },
});

console.log('Connecting to database:', {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
});

connection.connect(err => {
    if (err) {
        console.error('Database connection error:', err);
        return;
    }
    console.log('Connected to the database');
});

// API路由
app.get('/test-db', (req, res) => {
    connection.query('SELECT 1', (err, results) => {
        if (err) {
            return res.status(500).json({ error: 'Database connection error' });
        }
        res.json({ message: 'Database is connected', results });
    });
});

app.get('/books', (req, res) => {
    const query = 'SELECT * FROM books';

    connection.query(query, (err, results) => {
        if (err) {
            return res.status(500).json({ error: 'Error fetching books' });
        }
        res.json(results);
    });
});

// 启动服务器
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

console.log('Using SSL CA file:', process.env.SSL_CA);
console.log('Reading CA:', fs.readFileSync(process.env.SSL_CA).toString());

验证修复

  1. 本地启动服务器,测试http://localhost:3001/books是否能正常返回数据。
  2. 部署到Vercel后,访问线上前端页面,检查控制台是否还有CORS错误。

内容的提问来源于stack exchange,提问作者Mohammed Aayan Pathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:45:53