
本文旨在解决Node.js Express应用中集成PostgreSQL时常见的参数传递错误。当数据库操作函数期望接收`req`和`res`对象,但在Express路由中以不正确的方式调用时,会导致`TypeError: Cannot read properties of undefined (reading 'json')`。我们将详细解析问题根源,并提供正确的函数引用传递方法,以及更优化的代码结构,确保前后端数据交互顺畅。
在构建基于Node.js和Express的后端应用时,通常会将路由处理逻辑与数据库操作逻辑进行分离。例如,index.js文件负责定义API路由、中间件和服务器启动,而expense_model.js则专注于与PostgreSQL数据库进行交互,执行CRUD(创建、读取、更新、删除)操作。
index.js (Express应用入口):
const express = require('express');
const app = express();
const port = 8000;
const expense_model = require('./expense_model'); // 引入数据库模型
app.use(express.json()); // 解析JSON请求体
// CORS配置
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers');
next();
});
// 错误的GET路由示例
app.get('/', (req, res) => {
expense_model.getExpenses() // ⚠️ 问题所在:这里直接调用了函数
.then(expenses => {
res.status(200).json(expenses);
})
.catch(error => {
console.error('Failed to retrieve expenses:', error);
res.status(500).json({ error: 'Failed to retrieve expenses' });
});
});
// 其他路由...
app.post('/expenses', (req, res) => {
expense_model.createExpense(req.body) // 这里也存在类似问题,但因为createExpense直接接收req.body,暂时未暴露
.then(response => {
res.status(200).send(response);
})
.catch(error => {
res.status(500).send(error);
})
})
app.delete('/expenses/:id', (req, res) => {
expense_model.deleteExpense(req.params.id) // 同样,这里也存在类似问题
.then(response => {
res.status(200).send(response);
})
.catch(error => {
res.status(500).send(error);
})
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`));expense_model.js (数据库操作模块):
const Pool = require('pg').Pool;
const pool = new Pool({
user: 'my_user',
host: 'localhost',
database: 'my_database',
password: 'root',
port: 5432,
});
// 期望接收req和res的函数
async function getExpenses(req, res) {
try {
const query = 'SELECT * FROM expenses';
const result = await pool.query(query);
const expenses = result.rows;
res.status(200).json(expenses); // 在这里直接发送响应
console.log('Response:', res);
} catch (err) {
console.error('Failed to retrieve expenses:', err);
res.status(500).json({ error: 'Failed to retrieve expenses' });
}
}
async function createExpense(req, res) {
const { expense, amount, description, tag } = req.body;
const query = 'INSERT INTO expenses (expense, amount, description, tag) VALUES ($1, $2, $3, $4) RETURNING *';
const values = [expense, amount, description, tag];
try {
const result = await pool.query(query, values);
res.status(201).json(result.rows[0]);
} catch (err) {
console.error('Failed to create an expense:', err);
res.status(500).json({ error: 'Failed to create an expense' });
}
}
async function deleteExpense(req, res) {
const id = parseInt(req.params.id);
const query = 'DELETE FROM expenses WHERE id = $1';
const values = [id];
try {
const result = await pool.query(query, values);
if (result.rowCount === 0) {
res.status(404).json({ error: 'Expense not found' });
} else {
res.json({ message: 'Expense deleted successfully' });
}
} catch (err) {
console.error('Failed to delete an expense:', err);
res.status(500).json({ error: 'Failed to delete an expense' });
}
}
// 导出所有函数
module.exports = {
getExpenses,
createExpense,
deleteExpense,
// updateExpense 函数在原问题中未被使用,但通常也在此模块中
};在上述代码中,当访问根路径 / 时,index.js中的路由处理函数尝试调用expense_model.getExpenses()。
错误信息 TypeError: Cannot read properties of undefined (reading 'json') at Object.getExpenses 明确指出,在expense_model.getExpenses函数内部,尝试访问一个undefined对象的json属性。这通常发生在res对象为undefined的情况下,因为res.status(200).json(expenses)这一行需要res对象是有效的Express响应对象。
根本原因: Express的路由处理函数通常接收req (请求对象) 和 res (响应对象) 作为参数。当我们将一个函数作为路由处理程序传递给app.get()、app.post()等方法时,Express会自动将req和res对象作为参数传递给该函数。
在原始的index.js中,app.get('/', (req, res) => { expense_model.getExpenses().then(...) }) 这段代码中,expense_model.getExpenses() 被立即调用了。这意味着它在被传递给then()方法之前就已经执行。然而,expense_model.getExpenses函数定义时期望接收req和res参数:async function getExpenses(req, res)。由于在index.js中调用时没有传入任何参数,expense_model.getExpenses内部的req和res变量就变成了undefined。当它尝试执行res.status(200).json(expenses)时,由于res是undefined,便会抛出TypeError。
解决此问题的关键在于理解如何将函数作为回调或引用传递。Express期望路由处理程序是一个函数引用,而不是函数调用的结果。
修正后的 index.js (GET路由):
// ... (其他代码保持不变)
// 正确的GET路由示例
app.get('/', expense_model.getExpenses); // ⚠️ 直接传递函数引用
// ... (其他路由)
app.post('/expenses', expense_model.createExpense); // 同样,直接传递函数引用
app.delete('/expenses/:id', expense_model.deleteExpense); // 同样,直接传递函数引用
// ... (其他代码保持不变)通过将expense_model.getExpenses(不带括号)直接作为app.get的第二个参数,我们实际上是向Express提供了一个函数引用。当有请求到达/路径时,Express会自动调用expense_model.getExpenses函数,并将当前的req和res对象作为参数传递给它。这样,expense_model.getExpenses内部就能正确地访问和使用res对象来发送响应。
虽然上述解决方案可以修复错误,但将res对象直接传递给数据库模型函数(如getExpenses)并不是最佳实践。更好的做法是让数据库模型函数专注于数据查询和返回结果,而让Express路由处理函数负责接收这些结果并构造HTTP响应。这增强了模块的独立性和可测试性。
优化后的 expense_model.js:
const Pool = require('pg').Pool;
const pool = new Pool({
user: 'my_user',
host: 'localhost',
database: 'my_database',
password: 'root',
port: 5432,
});
// 优化后的getExpenses:只负责查询并返回数据
async function getExpensesFromDB() {
try {
const query = 'SELECT * FROM expenses';
const result = await pool.query(query);
return result.rows; // 返回查询到的数据
} catch (err) {
console.error('Failed to retrieve expenses from DB:', err);
throw new Error('Database query failed'); // 抛出错误,由调用者处理
}
}
// 优化后的createExpense:只负责创建并返回新数据
async function createExpenseInDB({ expense, amount, description, tag }) {
const query = 'INSERT INTO expenses (expense, amount, description, tag) VALUES ($1, $2, $3, $4) RETURNING *';
const values = [expense, amount, description, tag];
try {
const result = await pool.query(query, values);
return result.rows[0];
} catch (err) {
console.error('Failed to create an expense in DB:', err);
throw new Error('Database insert failed');
}
}
async function deleteExpenseInDB(id) {
const query = 'DELETE FROM expenses WHERE id = $1';
const values = [id];
try {
const result = await pool.query(query, values);
if (result.rowCount === 0) {
return false; // 表示未找到要删除的记录
}
return true; // 表示删除成功
} catch (err) {
console.error('Failed to delete an expense from DB:', err);
throw new Error('Database delete failed');
}
}
module.exports = {
getExpensesFromDB,
createExpenseInDB,
deleteExpenseInDB,
// updateExpenseInDB
};相应优化后的 index.js:
const express = require('express');
const app = express();
const port = 8000;
const expense_model = require('./expense_model');
app.use(express.json());
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers');
next();
});
// 优化后的GET路由
app.get('/', async (req, res) => {
try {
const expenses = await expense_model.getExpensesFromDB(); // 调用数据库模型函数获取数据
res.status(200).json(expenses); // 在路由处理函数中发送响应
} catch (error) {
console.error('Failed to retrieve expenses:', error);
res.status(500).json({ error: 'Failed to retrieve expenses' });
}
});
// 优化后的POST路由
app.post('/expenses', async (req, res) => {
try {
const newExpense = await expense_model.createExpenseInDB(req.body);
res.status(201).json(newExpense);
} catch (error) {
console.error('Failed to create expense:', error);
res.status(500).json({ error: 'Failed to create expense' });
}
});
// 优化后的DELETE路由
app.delete('/expenses/:id', async (req, res) => {
const id = parseInt(req.params.id);
try {
const deleted = await expense_model.deleteExpenseInDB(id);
if (deleted) {
res.status(200).json({ message: 'Expense deleted successfully' });
} else {
res.status(404).json({ error: 'Expense not found' });
}
} catch (error) {
console.error('Failed to delete expense:', error);
res.status(500).json({ error: 'Failed to delete expense' });
}
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));通过遵循这些原则,可以有效地避免常见的集成问题,构建出更健壮、可维护的Node.js与PostgreSQL应用。
以上就是Node.js与PostgreSQL集成:解决路由处理函数参数传递错误的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号