Node.js开发GET /albums与POST /purchases接口遇到问题求助
我正在学习Node.js,作为练习需要开发两个接口:
- GET /albums:获取数据库中所有专辑的列表
- POST /purchases:创建一条购买记录
我的尝试代码
const mongoose = require('mongoose'); const express = require('express'); const app = express(); const bodyParser = require('body-parser'); // Imports const Album = require("./models/album"); const Purchase = require("./models/purchase"); // TODO code the API // Connect to DB mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true}); var conn = mongoose.connection; conn.on('connected', function() { console.log('database is connected successfully'); }); conn.on('disconnected',function(){ console.log('database is disconnected successfully'); }) conn.on('error', console.error.bind(console, 'connection error:')); // Routes app.get('/albums', function(req, res, next) { Album.find({}, (err, albums) => { if (!err) { res.set({ 'Content-Type': 'application/json', 'Status': 200, }) return res.end(JSON.stringify(albums)); } else { console.log('Failed to retrieve the Course List: ' + err); } }); }); // POST method route app.post('/purchases', (req, res) => { const purchase = new Purchase({ user: req.body.user, album: req.body.album }) purchase.save(function (err, post) { if (err) { return err } res.json(201, purchase); }) }) module.exports = app;
接口开发要求
GET /albums 请求要求
由于这是一个JSON API,需返回JSON格式数据和200状态码(销毁方法除外,需返回204状态码表示无内容)。GET、POST和PUT方法需在data对象中返回Album的title、performer和cost三个字段。响应格式示例如下:
预期格式:
response.body.data = { _id: "the id of the album", title: "Appetite for Destruction", performer: "Guns N' Roses", cost: 20 };
POST /purchases 请求要求
POST /purchases接口需接收请求体中的user和album属性,然后在新创建的购买记录中存储这两条记录的引用。响应需包含购买记录以及关联的user和album的完整数据字段。
数据模型Schema
Album Schema
const albumSchema = mongoose.Schema({ performer: String, title: String, cost: Number });
Purchase Schema
const purchaseSchema = mongoose.Schema({ user: {type: mongoose.Schema.Types.ObjectId, ref: "User"}, album: {type: mongoose.Schema.Types.ObjectId, ref: "Album"} })
测试用例
程序需要通过以下两个接口测试用例:
describe("GET /albums", () => { it("should return an array of all models", async () => { const album = new Album(albumData).save(); const res = await chai .request(app) .get("/albums") ; expect(res.status).to.equal(200); expect(res).to.be.json; expect(res.body.data).to.be.a("array"); expect(res.body.data.length).to.equal(1); expect(res.body.data[0].title).to.equal(albumData.title); expect(res.body.data[0].performer).to.equal(albumData.performer); expect(res.body.data[0].cost).to.equal(albumData.cost); }).timeout(2000); }); describe("POST /purchases", () => { it("should create a new purchase and return its relations", async () => { const otherAlbumData = { title: "Sample", performer: "Unknown", cost: 2, }; const album = await new Album(otherAlbumData).save(); const user = await new User({name: "James"}).save(); const res = await chai .request(app) .post("/purchases") .send({user, album}) ; expect(res.status).to.equal(200); expect(res).to.be.json; expect(res.body.data).to.haveOwnProperty("user"); expect(res.body.data.user).to.haveOwnProperty("name"); expect(res.body.data).to.haveOwnProperty("album"); expect(res.body.data.album).to.haveOwnProperty("title"); expect(res.body.data.user.name).to.equal(user.name); expect(res.body.data.album.title).to.equal(album.title); }).timeout(2000); });
遇到的问题
- GET /albums接口无法正确获取数据,测试报错:
expected undefined to be an array - POST /purchases接口抛出500错误:
Cannot read property 'user' of undefined,但按需求该接口应能从请求体中获取user和album属性。
解决方案
别担心,这两个都是新手开发API时的常见问题,咱们一步步来修复:
1. 修复 POST /purchases 的 Cannot read property 'user' of undefined 错误
这个问题的核心是你没有配置body-parser解析JSON请求体——虽然你引入了body-parser,但没告诉Express要用它处理JSON格式的请求数据。在代码里的// TODO code the API 下方添加这一行:
// 解析JSON格式的请求体 app.use(bodyParser.json());
添加后,Express就能正确解析POST请求中的JSON数据,req.body 就不会再是undefined了。
2. 修复 GET /albums 的测试错误 expected undefined to be an array
你的接口现在直接返回了专辑数组,但测试期望的是把数组包裹在data对象里,同时还有几个小细节需要调整:
- 不需要手动设置
Content-Type和Status,Express的res.json()会自动处理这些响应头 - 错误发生时要给客户端返回错误响应,不能只在控制台打印日志
修改后的GET /albums代码:
app.get('/albums', async (req, res) => { try { // 只返回需要的字段,符合接口要求 const albums = await Album.find({}, '_id title performer cost'); // 把数组包裹在data对象中返回,匹配测试预期格式 res.status(200).json({ data: albums }); } catch (err) { console.error('Failed to retrieve albums:', err); // 给客户端返回错误状态码和信息 res.status(500).json({ error: 'Failed to retrieve albums' }); } });
我把回调改成了async/await写法,代码更易读;同时用find的第二个参数指定返回字段,确保只返回需求中要求的内容;最后把数组放在data属性里返回,完全匹配测试的预期格式。
3. 完善 POST /purchases 接口,使其通过测试
你的POST接口目前有几个问题:
- 保存后没有**填充(populate)**关联的user和album数据,测试期望返回完整的关联对象而非ID
res.json(201, purchase)是Express旧版本写法,现在应该用res.status(201).json(...)- 错误情况下没有返回响应,会导致客户端超时
- 需要把返回的购买记录包裹在
data对象里
修改后的POST /purchases代码:
app.post('/purchases', async (req, res) => { try { // 测试用例传入的是完整对象,我们需要提取_id存入数据库 const purchase = new Purchase({ user: req.body.user._id, album: req.body.album._id }); // 保存后填充关联的user和album数据 const savedPurchase = await purchase.save(); const populatedPurchase = await Purchase.findById(savedPurchase._id) .populate('user', 'name') // 只返回user的name字段 .populate('album', 'title performer cost'); // 只返回album需要的字段 // 把结果包裹在data对象中返回,匹配测试预期 res.status(201).json({ data: populatedPurchase }); } catch (err) { console.error('Failed to create purchase:', err); res.status(500).json({ error: 'Failed to create purchase' }); } });
这里注意测试用例是把完整的user和album对象传过来的,所以我们需要取它们的_id存入Purchase;然后用populate方法把关联的用户和专辑数据填充进来,最后同样把结果放在data对象里返回。
最后几个小建议
- 尽量用async/await代替回调函数,代码更清晰易维护
- 所有接口都要处理错误,给客户端返回合适的错误状态码和信息
- 测试用例里的
new Album(albumData).save()最好加上await,避免出现数据库还没保存完就发请求的情况
这样修改后,你的两个接口应该就能顺利通过测试了!
内容的提问来源于stack exchange,提问作者Ashar

