Skip to content

Improve Backend Infrastructure: Graceful Shutdown, Testing, and Error Handling#24

Open
DevMuhammed3 wants to merge 2 commits intomainfrom
improve-backend-infrastructure-15727824381294086827
Open

Improve Backend Infrastructure: Graceful Shutdown, Testing, and Error Handling#24
DevMuhammed3 wants to merge 2 commits intomainfrom
improve-backend-infrastructure-15727824381294086827

Conversation

@DevMuhammed3
Copy link
Copy Markdown
Owner

I have implemented several key improvements to the backend codebase:

  1. Graceful Shutdown: Added logic to apps/backend/src/index.ts to handle SIGINT and SIGTERM signals. This ensures the HTTP server and Prisma client are closed cleanly, and any active intervals are cleared before the process exits.
  2. Testing Infrastructure: Integrated Vitest into the backend. I've added a test script to package.json and a comprehensive test suite for the resolveAssetUrl utility to establish a testing pattern for the project.
  3. Improved Validation Errors: Updated the global error handler in apps/backend/src/app.ts to explicitly handle Zod validation errors. This provides API consumers with clearer, field-specific feedback when a request fails validation.

These changes enhance the reliability, maintainability, and developer experience of the OpenChat platform.


PR created automatically by Jules for task 15727824381294086827 started by @DevMuhammed3

- Added graceful shutdown handling for SIGINT and SIGTERM
- Set up Vitest testing infrastructure and added sample unit tests
- Enhanced global error handling for Zod validation errors

Co-authored-by: DevMuhammed3 <[email protected]>
@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
open-chat-frontend Ready Ready Preview, Comment Apr 15, 2026 7:29pm

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add graceful shutdown, testing infrastructure, and validation error handling

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Added graceful shutdown handling for SIGINT and SIGTERM signals
• Integrated Vitest testing framework with sample unit tests
• Enhanced global error handler for Zod validation errors
• Added vitest dependency to backend package.json
Diagram
flowchart LR
  A["Backend Infrastructure"] --> B["Graceful Shutdown"]
  A --> C["Testing Setup"]
  A --> D["Error Handling"]
  B --> B1["SIGINT/SIGTERM Handlers"]
  B --> B2["Server & DB Cleanup"]
  C --> C1["Vitest Integration"]
  C --> C2["resolveAssetUrl Tests"]
  D --> D1["Zod Validation Errors"]
  D --> D2["Field-specific Feedback"]
Loading

Grey Divider

File Changes

1. apps/backend/src/app.ts Error handling +11/-0

Add Zod validation error handling to global error handler

• Added Zod import for validation error handling
• Implemented dedicated error handler for z.ZodError instances
• Returns 400 status with field-specific error messages
• Preserves existing error handling for other error types

apps/backend/src/app.ts


2. apps/backend/src/index.ts ✨ Enhancement +33/-0

Implement graceful shutdown for SIGINT and SIGTERM signals

• Implemented gracefulShutdown function to handle process termination
• Clears presence cleanup interval before shutdown
• Closes HTTP server and disconnects Prisma database
• Includes 10-second timeout for forced shutdown if graceful shutdown fails
• Registered handlers for SIGINT and SIGTERM signals

apps/backend/src/index.ts


3. apps/backend/src/utils/resolveAssetUrl.test.ts 🧪 Tests +39/-0

Add unit tests for resolveAssetUrl utility function

• Created comprehensive test suite for resolveAssetUrl utility function
• Tests null/undefined input handling
• Tests absolute URL passthrough behavior
• Tests local path resolution with BASE_URL environment variable
• Tests leading slash handling and missing BASE_URL scenarios

apps/backend/src/utils/resolveAssetUrl.test.ts


View more (2)
4. apps/backend/package.json Dependencies +4/-2

Add Vitest testing framework to backend dependencies

• Added test script that runs vitest run
• Added vitest version ^4.1.4 to devDependencies

apps/backend/package.json


5. pnpm-lock.yaml Dependencies +247/-1

Update lock file with Vitest and testing dependencies

• Updated lock file with Vitest 4.1.4 and all related dependencies
• Added Vitest ecosystem packages (@vitest/expect, @vitest/runner, etc.)
• Added testing utilities (chai, tinybench, tinyrainbow)
• Added supporting packages for module analysis and debugging

pnpm-lock.yaml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 15, 2026

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (0)   📎 Requirement gaps (0)
🐞\ ≡ Correctness (1) ☼ Reliability (1)

Grey Divider


Action required

1. Shutdown skips socket.io close 🐞
Description
In apps/backend/src/index.ts, gracefulShutdown() calls server.close() but never closes the Socket.IO
server, so long-lived websocket connections can prevent the close callback from firing and the code
will hit the 10s forced process.exit(1). This defeats the “graceful” shutdown and can terminate
clients/cleanup abruptly even when the only remaining work is open sockets.
Code

apps/backend/src/index.ts[R114-141]

+const gracefulShutdown = async (signal: string) => {
+  console.log(`\n${signal} received. Shutting down gracefully...`)
+
+  clearInterval(presenceCleanup)
+
+  server.close(async (err) => {
+    if (err) {
+      console.error('Error closing server:', err)
+      process.exit(1)
+    }
+
+    console.log('HTTP server closed.')
+
+    try {
+      await prisma.$disconnect()
+      console.log('Database connection closed.')
+      process.exit(0)
+    } catch (dbErr) {
+      console.error('Error during database disconnection:', dbErr)
+      process.exit(1)
+    }
+  })
+
+  // Force close after 10 seconds if graceful shutdown fails
+  setTimeout(() => {
+    console.error('Could not close connections in time, forcefully shutting down')
+    process.exit(1)
+  }, 10000)
Evidence
The backend creates a Socket.IO server and accepts persistent connections, but the shutdown path
only closes the Node HTTP server and Prisma; there is no io.close()/disconnectSockets call, so
active sockets can keep the server from reaching the close callback before the forced-exit timer
triggers.

apps/backend/src/index.ts[25-45]
apps/backend/src/index.ts[112-145]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`gracefulShutdown()` closes the HTTP server and Prisma but leaves Socket.IO running. With active websocket connections, `server.close()` may never invoke its callback, causing the 10s timeout to force `process.exit(1)`.

### Issue Context
The backend uses Socket.IO for real-time features; sockets are long-lived and need an explicit shutdown to let the HTTP server fully close.

### Fix Focus Areas
- apps/backend/src/index.ts[114-141]

### Suggested fix
- Add an idempotency guard (e.g., `let isShuttingDown = false`) to avoid double shutdown on repeated signals.
- Explicitly stop Socket.IO before/alongside `server.close()`, e.g.:
 - `io.disconnectSockets(true)` (optional, if you want to actively drop clients)
 - `io.close()` to close the engine and release handles
- Store the force-timeout handle and `clearTimeout()` it when shutdown completes.
- Consider calling `await prisma.$disconnect()` in the force-timeout path (best-effort) before exiting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Zod error response mismatch 🐞
Description
The new global ZodError handling in apps/backend/src/app.ts returns a different JSON shape than the
existing respondWithZodError() utility used by controllers, so clients can receive different field
names/messages for the same validation failure. This inconsistency makes client-side error handling
brittle across endpoints and code paths.
Code

apps/backend/src/app.ts[R68-76]

+  if (err instanceof z.ZodError) {
+    return res.status(400).json({
+      message: "Validation Error",
+      errors: err.issues.map((issue) => ({
+        field: issue.path.join("."),
+        message: issue.message,
+      })),
+    })
+  }
Evidence
The global error handler returns { message: "Validation Error", errors: [...] }, while the shared
zodError utility returns { message: "Invalid request", issues: [...] } and is already used by
controllers; therefore Zod errors are not normalized across the API.

apps/backend/src/app.ts[66-83]
apps/backend/src/utils/zodError.ts[4-12]
apps/backend/src/controllers/chat.controller.ts[112-115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Zod validation errors are formatted differently depending on whether they are handled in a controller via `respondWithZodError()` or fall through to the new global handler in `app.ts`.

### Issue Context
A consistent validation error schema is important for API consumers; today the project already has a shared `respondWithZodError()` helper.

### Fix Focus Areas
- apps/backend/src/app.ts[68-76]
- apps/backend/src/utils/zodError.ts[4-12]

### Suggested fix
- Reuse the existing helper in the global error handler, e.g.:
 - `import { respondWithZodError } from "./utils/zodError.js"`
 - `if (err instanceof z.ZodError) return respondWithZodError(res, err)`
- Alternatively, update `respondWithZodError()` to match the new schema, and update controller usages accordingly (pick one canonical schema).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread apps/backend/src/index.ts
Comment on lines +114 to +141
const gracefulShutdown = async (signal: string) => {
console.log(`\n${signal} received. Shutting down gracefully...`)

clearInterval(presenceCleanup)

server.close(async (err) => {
if (err) {
console.error('Error closing server:', err)
process.exit(1)
}

console.log('HTTP server closed.')

try {
await prisma.$disconnect()
console.log('Database connection closed.')
process.exit(0)
} catch (dbErr) {
console.error('Error during database disconnection:', dbErr)
process.exit(1)
}
})

// Force close after 10 seconds if graceful shutdown fails
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down')
process.exit(1)
}, 10000)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Shutdown skips socket.io close 🐞 Bug ☼ Reliability

In apps/backend/src/index.ts, gracefulShutdown() calls server.close() but never closes the Socket.IO
server, so long-lived websocket connections can prevent the close callback from firing and the code
will hit the 10s forced process.exit(1). This defeats the “graceful” shutdown and can terminate
clients/cleanup abruptly even when the only remaining work is open sockets.
Agent Prompt
### Issue description
`gracefulShutdown()` closes the HTTP server and Prisma but leaves Socket.IO running. With active websocket connections, `server.close()` may never invoke its callback, causing the 10s timeout to force `process.exit(1)`.

### Issue Context
The backend uses Socket.IO for real-time features; sockets are long-lived and need an explicit shutdown to let the HTTP server fully close.

### Fix Focus Areas
- apps/backend/src/index.ts[114-141]

### Suggested fix
- Add an idempotency guard (e.g., `let isShuttingDown = false`) to avoid double shutdown on repeated signals.
- Explicitly stop Socket.IO before/alongside `server.close()`, e.g.:
  - `io.disconnectSockets(true)` (optional, if you want to actively drop clients)
  - `io.close()` to close the engine and release handles
- Store the force-timeout handle and `clearTimeout()` it when shutdown completes.
- Consider calling `await prisma.$disconnect()` in the force-timeout path (best-effort) before exiting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

- Replaced Vitest with Jest and ts-jest in apps/backend
- Configured Jest for ESM support using NODE_OPTIONS
- Added GitHub Actions workflow (.github/workflows/ci.yml) for automated testing and linting
- Migrated resolveAssetUrl tests to Jest format
- Enhanced backend error handling for Zod validation errors
- Implemented graceful shutdown in backend index.ts

Co-authored-by: DevMuhammed3 <[email protected]>
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