Skip to content

Improve error handling: fix swallowed errors and add proper propagation#1

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1780488322-improve-error-handling
Open

Improve error handling: fix swallowed errors and add proper propagation#1
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1780488322-improve-error-handling

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Audit of backend error handling found several places where errors were silently swallowed or not properly propagated. This PR fixes all of them:

server.js — Removed console.log that leaked JWT_SECRET to stdout. startServer() catch block now calls process.exit(1) instead of silently continuing with a broken DB connection. Added unhandledRejection and uncaughtException handlers so no async error goes unnoticed.

config/database.js — Validates DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT are set at startup and throws a clear error listing any that are missing, instead of passing undefined into new Sequelize() and getting a cryptic failure later.

app.js — Added 404 handler for unmatched routes (was returning Express's default HTML). Added global error-handling middleware that catches malformed JSON from express.json() (returns 400) and any other unhandled errors (returns 500 with the error message).

auth.controller.js — Register catch block now distinguishes SequelizeValidationError → 400 and SequelizeUniqueConstraintError → 409, instead of treating all errors as 500. Login now validates JWT_SECRET is defined before calling jwt.sign() — previously it would silently sign with undefined.

models/index.js — Fixed require('./User')require('./user') to match the actual filename (user.js), preventing a silent crash on case-sensitive file systems (Linux).

Test update — Updated auth.controller.test.js expectation for the register 500 error message to match the new, more descriptive 'An unexpected error occurred during registration'.

Link to Devin session: https://app.devin.ai/sessions/266a6d93aa384d3f838d74c3d955d5a8
Requested by: @0xpritam

Summary by CodeRabbit

  • Bug Fixes

    • More specific HTTP status codes and clearer error messages for request and auth failures
    • Improved server stability: startup now fails fast on critical errors and global crash handlers terminate safely
    • Startup now validates required configuration and reports missing settings
    • 404 responses added for unmatched routes with request details
  • Tests

    • Updated and added tests to cover new error messages and missing configuration behavior

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d48ef16-f06e-4541-8ace-b416aac2e456

📥 Commits

Reviewing files that changed from the base of the PR and between 0804633 and 444f615.

📒 Files selected for processing (1)
  • backend/src/__tests__/auth.controller.test.js

📝 Walkthrough

Walkthrough

The PR hardens the Node.js backend by validating DB env vars at startup, removing a JWT_SECRET debug log, adding process-level handlers that log and exit on fatal errors, adding Express 404 and global error middleware (including malformed JSON handling), and improving auth error responses with matching test updates.

Changes

Error Handling & Security Hardening

Layer / File(s) Summary
Database Configuration & Validation
backend/src/config/database.js
Required DB environment variables (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT) are validated at startup; missing keys throw an error before Sequelize initialization.
Server Startup Security & Error Handling
backend/server.js
Removed debug console.log of JWT_SECRET, added process.exit(1) on startup failure, and registered process.on('unhandledRejection') and process.on('uncaughtException') handlers that log and exit with code 1.
Express Error & 404 Middleware
backend/src/app.js
Added catch-all unmatched-route middleware returning JSON 404 with method and URL, and a global error handler that returns 400 for malformed JSON parse errors and logs/returns appropriate status and message for other errors.
Authentication Error Handling & Tests
backend/src/controllers/auth.controller.js, backend/src/__tests__/auth.controller.test.js
register now maps SequelizeValidationError → 400 (errors array) and SequelizeUniqueConstraintError → 409; login checks JWT_SECRET presence and returns 500 Server configuration error if missing; tests updated/added to assert new messages and that jwt.sign is not called when secret is absent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • 0xpritam/SmartQueue#4: Updates Jest tests for auth.controller that overlap the test changes in this PR.

Poem

🐰 I nibble logs and guard the gate,

Secrets hidden, errors weight;
I hop and catch the JSON cries,
Seal the startup, close the skies;
A tidy backend—hop, rejoice!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main focus of the changeset: improving error handling by fixing swallowed errors and adding proper error propagation throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1780488322-improve-error-handling

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/src/controllers/auth.controller.js (1)

24-29: ⚡ Quick win

Inconsistent status code for duplicate email (400 vs 409).

The pre-check at Lines 24-29 returns 400 for an existing email, while the unique-constraint fallback returns 409. The same logical condition should map to one status. Recommend 409 in both for a consistent client contract (the catch block already serves as the race-condition fallback).

♻️ Align pre-check with 409
     if (existingUser) {
-      return res.status(400).json({
+      return res.status(409).json({
         success: false,
         message: 'Email already registered',
       });
     }

Also applies to: 61-66

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/controllers/auth.controller.js` around lines 24 - 29, The
pre-check that returns a 400 when an existingUser is found should return 409 to
match the unique-constraint fallback; update the response in the existingUser
branch (the duplicate-email pre-check) to use res.status(409).json(...) and
likewise change the other duplicate-email pre-check branch (the similar block
later in the same controller) so both use 409 for consistency with the catch
fallback that already returns 409.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/app.js`:
- Around line 41-45: The current error handler logs the full error and returns
err.message to clients even on 500s; change the logic in the error-handling
block that uses console.error, res.status(err.status || 500).json(...) so that
only explicit client errors (status >=400 && <500) surface err.message to the
JSON response, while unexpected server errors (no err.status or status >=500)
return a generic message like "Internal server error"; continue to console.error
the full err (and stack) for server-side diagnostics but do not include those
details in the response JSON.

---

Nitpick comments:
In `@backend/src/controllers/auth.controller.js`:
- Around line 24-29: The pre-check that returns a 400 when an existingUser is
found should return 409 to match the unique-constraint fallback; update the
response in the existingUser branch (the duplicate-email pre-check) to use
res.status(409).json(...) and likewise change the other duplicate-email
pre-check branch (the similar block later in the same controller) so both use
409 for consistency with the catch fallback that already returns 409.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 939923ac-7260-4172-b8d5-aee0336f03e4

📥 Commits

Reviewing files that changed from the base of the PR and between 44f8626 and 6673260.

📒 Files selected for processing (5)
  • backend/server.js
  • backend/src/app.js
  • backend/src/config/database.js
  • backend/src/controllers/auth.controller.js
  • backend/src/models/index.js

Comment thread backend/src/app.js
Comment on lines +41 to +45
console.error('Unhandled error:', err);
res.status(err.status || 500).json({
success: false,
message: err.message || 'Internal server error',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid leaking internal error details to clients on 500s.

For unexpected errors (no intentional err.status), err.message can expose internals such as driver/SQL/stack text to clients. Surface err.message only for explicit client errors (4xx) and return a generic message otherwise.

🛡️ Proposed fix
     console.error('Unhandled error:', err);
-    res.status(err.status || 500).json({
-        success: false,
-        message: err.message || 'Internal server error',
-    });
+    const status = err.status || 500;
+    res.status(status).json({
+        success: false,
+        message: status < 500 ? (err.message || 'Request error') : 'Internal server error',
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error('Unhandled error:', err);
res.status(err.status || 500).json({
success: false,
message: err.message || 'Internal server error',
});
console.error('Unhandled error:', err);
const status = err.status || 500;
res.status(status).json({
success: false,
message: status < 500 ? (err.message || 'Request error') : 'Internal server error',
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/app.js` around lines 41 - 45, The current error handler logs the
full error and returns err.message to clients even on 500s; change the logic in
the error-handling block that uses console.error, res.status(err.status ||
500).json(...) so that only explicit client errors (status >=400 && <500)
surface err.message to the JSON response, while unexpected server errors (no
err.status or status >=500) return a generic message like "Internal server
error"; continue to console.error the full err (and stack) for server-side
diagnostics but do not include those details in the response JSON.

0xpritam and others added 2 commits June 4, 2026 16:44
- server.js: Remove debug log leaking JWT_SECRET, exit process on startup
  failure, add unhandledRejection and uncaughtException handlers
- config/database.js: Validate required DB env vars on startup with
  clear error message listing which vars are missing
- app.js: Add 404 handler for unmatched routes, add global error-handling
  middleware with malformed JSON detection
- auth.controller.js: Distinguish SequelizeValidationError (400) and
  SequelizeUniqueConstraintError (409) from generic 500s in register;
  validate JWT_SECRET is set before signing tokens in login
- models/index.js: Fix require path case (User -> user) for Linux compat

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1780488322-improve-error-handling branch from 6673260 to 0804633 Compare June 4, 2026 16:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
backend/src/__tests__/auth.controller.test.js (2)

101-192: ⚡ Quick win

Add coverage for the missing JWT_SECRET branch.

Line 101-192 does not currently assert the new login guard that returns 500 with Server configuration error when process.env.JWT_SECRET is missing. This is a key contract added in the controller and should be locked with a dedicated test.

Proposed test addition
 describe('login', () => {
   beforeEach(() => {
     jest.clearAllMocks();
     process.env.JWT_SECRET = 'test-secret';
     process.env.JWT_EXPIRES_IN = '1h';
   });

+  it('returns 500 when JWT_SECRET is not configured', async () => {
+    delete process.env.JWT_SECRET;
+    const fakeUser = { id: '1', name: 'Test', email: 'a@b.com', password: 'hashed' };
+    User.findOne.mockResolvedValue(fakeUser);
+    bcrypt.compare.mockResolvedValue(true);
+
+    const { req, res } = mockReqRes({ email: 'a@b.com', password: '123456' });
+    await login(req, res);
+
+    expect(res.status).toHaveBeenCalledWith(500);
+    expect(res.json).toHaveBeenCalledWith(
+      expect.objectContaining({ success: false, message: 'Server configuration error' })
+    );
+    expect(jwt.sign).not.toHaveBeenCalled();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/__tests__/auth.controller.test.js` around lines 101 - 192, Add a
new test in backend/src/__tests__/auth.controller.test.js that verifies the
login guard when process.env.JWT_SECRET is missing: in the test clear mocks,
delete process.env.JWT_SECRET, create a mock req/res (e.g., mockReqRes with
valid email/password), call the login function, and assert res.status was called
with 500 and res.json was called with an object containing the message 'Server
configuration error'; ensure the test does not rely on User.findOne or jwt.sign
being called since the guard should return early.

102-106: ⚡ Quick win

Restore mutated environment variables after each test.

Line 102-106 mutates process.env but does not restore previous values, which can leak state into other suites.

Proposed isolation fix
 describe('login', () => {
+  let originalJwtSecret;
+  let originalJwtExpiresIn;
+
+  beforeAll(() => {
+    originalJwtSecret = process.env.JWT_SECRET;
+    originalJwtExpiresIn = process.env.JWT_EXPIRES_IN;
+  });
+
   beforeEach(() => {
     jest.clearAllMocks();
     process.env.JWT_SECRET = 'test-secret';
     process.env.JWT_EXPIRES_IN = '1h';
   });
+
+  afterAll(() => {
+    if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
+    else process.env.JWT_SECRET = originalJwtSecret;
+
+    if (originalJwtExpiresIn === undefined) delete process.env.JWT_EXPIRES_IN;
+    else process.env.JWT_EXPIRES_IN = originalJwtExpiresIn;
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/__tests__/auth.controller.test.js` around lines 102 - 106, The
tests mutate process.env in the beforeEach block (setting process.env.JWT_SECRET
and process.env.JWT_EXPIRES_IN) but never restore originals; update the test
file to save the original values (e.g., capture originalJwtSecret and
originalJwtExpiresIn) before overwriting them in beforeEach, and add an
afterEach that restores those originals (or deletes the keys if they were
undefined) so process.env is returned to its prior state after each test;
reference the existing beforeEach and add a corresponding afterEach near it to
perform the restore and keep jest.clearAllMocks() as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/src/__tests__/auth.controller.test.js`:
- Around line 101-192: Add a new test in
backend/src/__tests__/auth.controller.test.js that verifies the login guard when
process.env.JWT_SECRET is missing: in the test clear mocks, delete
process.env.JWT_SECRET, create a mock req/res (e.g., mockReqRes with valid
email/password), call the login function, and assert res.status was called with
500 and res.json was called with an object containing the message 'Server
configuration error'; ensure the test does not rely on User.findOne or jwt.sign
being called since the guard should return early.
- Around line 102-106: The tests mutate process.env in the beforeEach block
(setting process.env.JWT_SECRET and process.env.JWT_EXPIRES_IN) but never
restore originals; update the test file to save the original values (e.g.,
capture originalJwtSecret and originalJwtExpiresIn) before overwriting them in
beforeEach, and add an afterEach that restores those originals (or deletes the
keys if they were undefined) so process.env is returned to its prior state after
each test; reference the existing beforeEach and add a corresponding afterEach
near it to perform the restore and keep jest.clearAllMocks() as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dce7900-b853-4f81-aa37-0dc5c3336c5f

📥 Commits

Reviewing files that changed from the base of the PR and between 6673260 and 0804633.

📒 Files selected for processing (5)
  • backend/server.js
  • backend/src/__tests__/auth.controller.test.js
  • backend/src/app.js
  • backend/src/config/database.js
  • backend/src/controllers/auth.controller.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/src/app.js
  • backend/src/config/database.js
  • backend/server.js
  • backend/src/controllers/auth.controller.js

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant