Skip to content

Commit 8a64698

Browse files
holmokchrisholmok
andauthored
✨ feat: Added optional production graceful shutdown for bun adapter. (#196)
* added production graceful shutdown as bun option. see: #195 --------- Co-authored-by: chrisholmok <chris@holmok.com>
1 parent 6404c83 commit 8a64698

File tree

17 files changed

+1555
-0
lines changed

17 files changed

+1555
-0
lines changed

.changeset/bold-jars-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router-hono-server": minor
3+
---
4+
5+
✨ feat: Added optional production graceful shutdown for bun adapter.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,20 @@ export interface HonoServerOptions<E extends Env = BlankEnv> extends HonoServerO
664664
* {@link https://bun.sh/docs/api/http#start-a-server-bun-serve}
665665
*/
666666
customBunServer?: Serve.Options<unknown, string>;
667+
/**
668+
* Callback executed after server has closed and all inflight requests completed,
669+
* before process exit. Only applicable in production mode.
670+
*
671+
* @example
672+
* ```ts
673+
* export default createHonoServer({
674+
* onGracefulShutdown: async () => {
675+
* await db.close();
676+
* },
677+
* });
678+
* ```
679+
*/
680+
onGracefulShutdown?: () => Promise<void> | void;
667681
/**
668682
* Customize the serve static options
669683
*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env
6+
.react-router
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Bun Graceful Shutdown Example
2+
3+
This example demonstrates the graceful shutdown feature of the Bun adapter for `react-router-hono-server`.
4+
5+
## Features
6+
7+
- ✅ Graceful shutdown hook (`onGracefulShutdown`)
8+
- ✅ Signal handler setup (SIGTERM, SIGINT)
9+
- ✅ Clean resource cleanup before process exit
10+
- ✅ Production-ready pattern
11+
12+
## How It Works
13+
14+
### 1. Server Configuration
15+
16+
In [app/server.ts](./app/server.ts), simply configure the server with a graceful shutdown callback:
17+
18+
```ts
19+
export default createHonoServer({
20+
onGracefulShutdown: async () => {
21+
console.log('🧹 Running cleanup tasks...');
22+
// Your cleanup logic here (close DB connections, etc.)
23+
await new Promise(resolve => setTimeout(resolve, 1000));
24+
console.log('✅ Cleanup complete!');
25+
},
26+
});
27+
```
28+
29+
**That's it!** The Bun adapter automatically registers SIGTERM and SIGINT signal handlers when you provide an `onGracefulShutdown` callback. No additional setup needed.
30+
31+
### 2. Shutdown Flow
32+
33+
When you send a SIGTERM or SIGINT signal (e.g., Ctrl+C):
34+
35+
1. Automatic signal handler is triggered
36+
2. Server stops accepting new connections
37+
3. Existing inflight requests complete
38+
4. `onGracefulShutdown` callback executes
39+
5. Process exits cleanly
40+
41+
## Running the Example
42+
43+
### Development
44+
45+
```bash
46+
bun install
47+
bun run dev
48+
```
49+
50+
### Production
51+
52+
```bash
53+
bun run build
54+
bun run start
55+
```
56+
57+
Then press `Ctrl+C` to test the graceful shutdown. You should see:
58+
59+
```
60+
📡 Received SIGINT, shutting down gracefully...
61+
Initiating graceful shutdown...
62+
Server stopped, all requests completed
63+
🧹 Running cleanup tasks...
64+
✅ Cleanup complete!
65+
Cleanup callback completed
66+
👋 Shutdown complete
67+
```
68+
69+
## Key Files
70+
71+
- [app/server.ts](./app/server.ts) - Server configuration with graceful shutdown callback
72+
- [app/routes/_index.tsx](./app/routes/_index.tsx) - Demo page explaining the feature
73+
74+
## Notes
75+
76+
- The `onGracefulShutdown` callback only runs in **production mode**
77+
- During development, the callback is ignored
78+
- Signal handlers (SIGTERM, SIGINT) are automatically registered in production
79+
- The server waits indefinitely for inflight connections to complete
80+
81+
## Deployment
82+
83+
When deploying to production environments (Docker, Kubernetes, etc.), ensure:
84+
85+
1. Your container/orchestrator sends SIGTERM for graceful shutdown
86+
2. Allow sufficient time for graceful shutdown before force kill
87+
3. Configure health checks to stop sending traffic during shutdown
88+
89+
### Docker Example
90+
91+
```dockerfile
92+
FROM oven/bun:latest
93+
WORKDIR /app
94+
COPY . .
95+
RUN bun install
96+
RUN bun run build
97+
CMD ["bun", "./scripts/start.ts"]
98+
99+
# Important: Use exec form to handle signals properly
100+
# OR use: ENTRYPOINT ["bun", "./scripts/start.ts"]
101+
```
102+
103+
### Kubernetes Example
104+
105+
```yaml
106+
spec:
107+
containers:
108+
- name: app
109+
lifecycle:
110+
preStop:
111+
exec:
112+
command: ["/bin/sh", "-c", "sleep 5"]
113+
terminationGracePeriodSeconds: 30
114+
```
115+
116+
This gives the app time to finish graceful shutdown before Kubernetes force-kills the pod.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
Links,
3+
Meta,
4+
Outlet,
5+
Scripts,
6+
ScrollRestoration,
7+
} from "react-router";
8+
9+
import "./tailwind.css";
10+
11+
export function Layout({ children }: { children: React.ReactNode }) {
12+
return (
13+
<html lang="en">
14+
<head>
15+
<meta charSet="utf-8" />
16+
<meta name="viewport" content="width=device-width, initial-scale=1" />
17+
<Meta />
18+
<Links />
19+
</head>
20+
<body>
21+
{children}
22+
<ScrollRestoration />
23+
<Scripts />
24+
</body>
25+
</html>
26+
);
27+
}
28+
29+
export default function Root() {
30+
return <Outlet />;
31+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { RouteConfig } from "@react-router/dev/routes";
2+
import { flatRoutes } from "@react-router/fs-routes";
3+
4+
export default flatRoutes() satisfies RouteConfig;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Route } from "./+types/_index";
2+
3+
export function meta({}: Route.MetaArgs) {
4+
return [
5+
{ title: "Graceful Shutdown Demo" },
6+
{ name: "description", content: "Bun adapter graceful shutdown example" },
7+
];
8+
}
9+
10+
export default function Index() {
11+
return (
12+
<div className="flex h-screen items-center justify-center">
13+
<div className="flex flex-col items-center gap-8 text-center">
14+
<h1 className="text-4xl font-bold">Graceful Shutdown for Bun Demo</h1>
15+
<p className="text-lg text-gray-600 max-w-md">
16+
This example demonstrates the graceful shutdown feature of the Bun adapter. Press{" "}
17+
<kbd className="px-2 py-1 bg-gray-100 rounded">Ctrl+C</kbd> in the terminal to test graceful shutdown.
18+
</p>
19+
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 max-w-md">
20+
<h2 className="font-semibold mb-2">What happens on shutdown:</h2>
21+
<ol className="text-left space-y-1 text-sm">
22+
<li>1. Server stops accepting new connections</li>
23+
<li>2. Existing requests complete</li>
24+
<li>3. Cleanup callback executes</li>
25+
<li>4. Process exits cleanly</li>
26+
</ol>
27+
</div>
28+
</div>
29+
</div>
30+
);
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createHonoServer } from "react-router-hono-server/bun";
2+
3+
export default createHonoServer({
4+
onGracefulShutdown: async () => {
5+
// Simulate cleanup operations (e.g., closing database connections)
6+
await new Promise((resolve) => setTimeout(resolve, 1000));
7+
},
8+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;

0 commit comments

Comments
 (0)