如何修复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());
验证修复
- 本地启动服务器,测试
http://localhost:3001/books是否能正常返回数据。 - 部署到Vercel后,访问线上前端页面,检查控制台是否还有CORS错误。
内容的提问来源于stack exchange,提问作者Mohammed Aayan Pathan
相关产品推荐
相关产品推荐

