Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
Hello! I'm requesting a few changes before this can be approved. You've built a solid application structure, which is great! However, there are several critical issues that conflict with the project's core requirements and cause incorrect behavior.
Here are the main points to address:
- Initial Data: In
src/users.controller.jsandsrc/expenses.controller.js, the data arrays must be initialized as empty (let users = []) as required by the task description. - Server Start: The
app.listen()call insrc/createServer.jsmust be removed. The task specifies that the server is started by the test environment, not this file. - User Deletion Logic: In
src/users.routes.js, theDELETE /:idendpoint incorrectly sends a204status even when a user isn't found. You need to ensure the function exits after setting the404status, for example, with areturn. - Expense Filtering: The logic in
GET /expensesinsrc/expenses.routes.jsis flawed. The check for query parameters is always true, making the code to get all expenses unreachable. Also, a search that yields no results should return a200 OKwith an empty array, not a404.
Please fix these issues, and your project will be ready for approval. Keep up the great work!
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
| router.get('/', (req, res) => { | ||
| const searchParams = new URLSearchParams(req.url.slice(2)); | ||
|
|
||
| if (searchParams) { |
There was a problem hiding this comment.
The condition if (searchParams) will always be true because new URLSearchParams() always returns an object, even for an empty query string. This makes the logic to get all expenses (lines 34-35) unreachable. You should check if any query parameters were actually provided, for example by checking the number of keys in req.query.
| const searchParams = new URLSearchParams(req.url.slice(2)); | ||
|
|
||
| if (searchParams) { | ||
| const userId = +searchParams.get('userId') || null; |
There was a problem hiding this comment.
There's a bug in how the userId is parsed. If the userId query parameter is not provided, searchParams.get('userId') is null, and +null evaluates to 0. This causes the code to incorrectly filter for expenses of user 0 instead of not filtering by user at all.
|
|
||
| const expenses = expensesController.getByParams(userId, category, from, to); | ||
|
|
||
| if (!expenses.length) { |
There was a problem hiding this comment.
Returning a 404 status code when a filtered search returns no results is not standard REST API practice. A successful search that finds no matching items should return a 200 OK status with an empty array []. A 404 Not Found should be used when the requested resource itself (like /expenses/some-invalid-id) doesn't exist.
| if (searchParams) { | ||
| const userId = +searchParams.get('userId') || null; | ||
| const category = searchParams.get('category')?.trim(); | ||
| const from = searchParams.get('from') || null; | ||
| const to = searchParams.get('to') || null; | ||
|
|
||
| const expenses = expensesController.getByParams(userId, category, from, to); | ||
|
|
||
| if (!expenses.length) { | ||
| res.statusCode = 404; | ||
| res.end(); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| res.statusCode = 200; | ||
| res.send(expenses); | ||
|
|
||
| return; | ||
| } |
There was a problem hiding this comment.
This if block is always true because searchParams is always an object, making the else block unreachable. The logic for handling requests with and without query parameters needs to be reconsidered. A simple check like Object.keys(req.query).length > 0 would be more appropriate for distinguishing between these cases.
| module.exports = { | ||
| createServer, | ||
| }; | ||
| createServer().listen(3005); |
There was a problem hiding this comment.
The task requirements explicitly state that you should not call app.listen() within this file. The server is started by the test runner or the main application file (main.js). Including this line will interfere with the automated tests and proper application startup.
| let users = [ | ||
| { | ||
| id: 0, | ||
| name: 'Vovan', | ||
| }, | ||
| { | ||
| id: 1, | ||
| name: 'Andrew', | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
According to the task requirements, the data should be empty when the server starts. This array should be initialized as empty, like let users = [];.
| let expenses = [ | ||
| { | ||
| id: 0, | ||
| userId: 2, | ||
| spentAt: '2020-07-20T15:49:04-07:00', | ||
| title: 'string', | ||
| amount: 0, | ||
| category: 'string', | ||
| note: 'string', | ||
| }, | ||
| { | ||
| id: 1, | ||
| userId: 2, | ||
| spentAt: '2020-07-20T15:49:04-07:00', | ||
| title: 'string', | ||
| amount: 0, | ||
| category: 'string', | ||
| note: 'string', | ||
| }, | ||
| { | ||
| id: 2, | ||
| userId: 1, | ||
| spentAt: '2015-07-20T15:49:04-07:00', | ||
| title: 'string', | ||
| amount: 0, | ||
| category: 'go', | ||
| note: 'string', | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
According to the task requirements, the data should be empty initially when the server starts. This array should be initialized as an empty array, like in the commented-out line below.
| if (!deleteStatus) { | ||
| res.statusCode = 404; | ||
|
|
||
| res.end(); | ||
| } | ||
|
|
||
| res.statusCode = 204; | ||
| res.end(); |
There was a problem hiding this comment.
There's a logic error in this DELETE handler. When deleteStatus is false (meaning the user was not found), you correctly set the status code to 404. However, the function continues to execute, and the status code is immediately overwritten to 204 on line 54. You need to ensure the function exits after handling the 404 case, for example, by adding a return statement inside the if block.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am requesting changes because there are still a few critical bugs in src/expenses.routes.js that will break required functionality and test cases. The GET /expenses query filtering branch never runs because URLSearchParams is used incorrectly (no .length property), and the request URL is parsed manually instead of using req.query, so filtered queries (e.g. by userId, category, from, to) won’t work at all. Also in that same route, parsing userId with +searchParams.get('userId') || null mistakenly turns a valid id of 0 into null, which conflicts with the controller’s expectation that userId === 0 is a valid user id. In the POST /expenses route, when the referenced user doesn’t exist, you currently return status 400, but the requirements specify returning 404 when an expected entity (like a user or expense) is missing.
The main fixes you need to make are:
- Replace the manual
new URLSearchParams(req.url.slice(2))logic with Express’req.query, and check for filters with something likeif (Object.keys(req.query).length > 0) { ... }(this will make the filtering branch reachable and safer). - When deriving
userId(and other params) fromreq.query, preserve0by checking presence explicitly, e.g.if ('userId' in req.query) { userId = Number(req.query.userId); }instead of using+value || null. - In POST /expenses, when
userController.getById(userId)returns nothing, respond withres.status(404)instead of400to match the “404 for missing entity” rule.
Everything else (users routes/controller, expenses controller, createServer) looks solid and aligned with the requirements; once you fix the expenses query parsing and the 404 status, your solution should meet the task expectations. Keep going—you’re very close, and these changes will make your expenses endpoints both correct and easier to maintain.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
| const keys = ['spentAt', 'title', 'amount', 'category', 'note']; | ||
|
|
||
| router.get('/', (req, res) => { | ||
| const searchParams = new URLSearchParams(req.url.slice(2)); |
There was a problem hiding this comment.
Avoid manually slicing req.url and constructing URLSearchParams here. Prefer using Express-provided req.query which is already parsed and avoids fragile slicing logic. Using req.query will simplify this route and remove edge-case bugs. Reference: this file's query parsing on the next line.
| router.get('/', (req, res) => { | ||
| const searchParams = new URLSearchParams(req.url.slice(2)); | ||
|
|
||
| if (searchParams.length) { |
There was a problem hiding this comment.
if (searchParams.length) { ... } is incorrect: URLSearchParams doesn't have a length property, so this condition will always be falsy and the filtering branch will never run. Use Object.keys(req.query).length, searchParams.toString() !== '', or, better, switch to req.query and check Object.keys(req.query).length instead.
| const searchParams = new URLSearchParams(req.url.slice(2)); | ||
|
|
||
| if (searchParams.length) { | ||
| const userId = +searchParams.get('userId') || null; |
There was a problem hiding this comment.
Parsing userId like +searchParams.get('userId') || null will convert a valid 0 to null (because 0 || null is null). Preserve zeros by checking presence explicitly, e.g. const userId = searchParams.has('userId') ? +searchParams.get('userId') : null or use if ('userId' in req.query) ... when switching to req.query. The controller expects userId === 0 to be valid.
| if (!userController.getById(userId)) { | ||
| res.statusCode = 400; |
There was a problem hiding this comment.
When the referenced user doesn't exist you currently respond with status 400. Per the project/tests requirements, missing expected entities should return 404 (not 400). Change this status to 404 for the user-not-found case in POST / (create expense).
| const searchParams = new URLSearchParams(req.url.slice(2)); | ||
|
|
||
| if (searchParams.length) { | ||
| const userId = +searchParams.get('userId') || null; | ||
| const category = searchParams.get('category')?.trim(); | ||
| const from = searchParams.get('from') || null; |
There was a problem hiding this comment.
Small suggestion: after switching to req.query you can remove the whole new URLSearchParams(req.url.slice(2)) approach and directly derive userId, category, from, and to from req.query. This will fix presence checks, preserve falsy numeric ids like 0, and make the code much easier to read and test.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving your solution as an educational override so you can keep progressing while you refine a few remaining details.
You did a solid job implementing the in-memory controllers for both users and expenses, wiring up all required endpoints, returning 404s correctly when entities are missing, and structuring createServer so it mounts the routers and returns the Express app without calling listen.
There are, however, some important fixes to make next:
- In
src/expenses.routes.js, theGET /expenseshandler currently usesif (req.query)which is always truthy; this makes the filtered branch always run and the unfilteredgetAll()path unreachable. Update it to something likeif (Object.keys(req.query).length > 0)and, foruserId, explicitly check key presence and convert withNumber(...)so0is preserved as a valid id. - Also in
expenses.routes.js, consider applying the same explicit presence and type handling tofromandtoso they are consistently parsed and easier to reason about. - In
src/users.routes.js, both POST and PATCH endpoints callname?.trim()before checking the type; this can throw ifnameis not a string. Swap the order toif (typeof name !== 'string' || !name.trim()) { ... }so invalid names yield a 400 instead of an exception.
Overall, your structure and route/controller separation are strong; with these validation and query-handling fixes, your implementation will be much more robust and aligned with typical Express best practices. Keep going—you’re very close to a polished solution.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
| const keys = ['spentAt', 'title', 'amount', 'category', 'note']; | ||
|
|
||
| router.get('/', (req, res) => { | ||
| if (req.query) { |
There was a problem hiding this comment.
if (req.query) is always truthy in Express (it will be an object even when empty). That makes the filtered branch execute for every request and prevents the fallback getAll() path from being used. Change this to an explicit presence check such as if (Object.keys(req.query).length > 0) { ... } so you only apply filters when query params exist.
|
|
||
| router.get('/', (req, res) => { | ||
| if (req.query) { | ||
| const userId = req.query['userId'] || null; |
There was a problem hiding this comment.
Parsing userId with req.query['userId'] || null mixes types and relies on truthiness. Explicitly check for the presence of the key and convert to Number to preserve 0 as a valid id, e.g. let userId = null; if ('userId' in req.query) { userId = Number(req.query.userId); }. This keeps types consistent and avoids edge-cases.
| res.statusCode = 200; | ||
| res.send(expensesController.getAll()); |
There was a problem hiding this comment.
Because the filtered branch currently always runs, the unfiltered fallback that calls expensesController.getAll() (below) is effectively unreachable. After fixing the presence check on req.query, the fallback will work as intended.
| const from = req.query['from'] || null; | ||
| const to = req.query['to'] || null; |
There was a problem hiding this comment.
Consider applying the same explicit-presence parsing approach to from and to if you rely on their presence vs absence. Right now they are left as strings or null, and although getByParams handles them, making presence and types explicit improves clarity and avoids subtle bugs.
| router.post('/', express.json(), (req, res) => { | ||
| const { name } = req.body; | ||
|
|
||
| if (!name?.trim() || typeof name !== 'string') { |
There was a problem hiding this comment.
The POST validation calls name?.trim() before checking type. If name is a non-string (for example a number), calling .trim() may throw. Swap the checks and verify the type first, e.g. if (typeof name !== 'string' || !name.trim()) { ... } so you return 400 safely for invalid/missing name.
| const { id } = req.params; | ||
| const { name } = req.body; | ||
|
|
||
| if (!name?.trim()) { |
There was a problem hiding this comment.
The PATCH validation only checks !name?.trim() and does not verify name is a string. If name is present but not a string this may throw. Add an explicit type check like if (typeof name !== 'string' || !name.trim()) { ... } to ensure you return 400 rather than causing an exception.
No description provided.