uppy/examples/reactrouter/server.ts
Prakash d9d44ce5e5
upgrade to express v5 (#6304)
exact copy of #6175 

- I will point the uppy deps upgrade pr to this PR's branch
- no other changes needed in this branch as #6175 was reviewed and all
the comments were resolved

this should be the order of merge : 

1. #6300
2. uppy deps upgrade ( yet to be raised ) 
3. merge this to main
4. merge biome changes #6244 

@mifi let me know if you think otherwise.

---------

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
2026-05-21 11:14:23 +08:00

113 lines
3.4 KiB
TypeScript

import { mkdir } from 'node:fs/promises'
import path from 'node:path'
import { createRequestHandler } from '@react-router/express'
import { FileStore } from '@tus/file-store'
import { Server as TusServer } from '@tus/server'
import express from 'express'
import type { ViteDevServer } from 'vite'
async function startServer() {
try {
// Setup upload directory
const uploadDir = path.join(process.cwd(), 'uploads')
await mkdir(uploadDir, { recursive: true }).catch(() => {})
// Create TUS server for resumable uploads
const tusServer = new TusServer({
path: '/api/upload/tus',
datastore: new FileStore({ directory: uploadDir }),
})
// Setup Vite dev server
const viteDevServer: ViteDevServer = await import('vite').then((vite) =>
vite.createServer({
server: { middlewareMode: true },
logLevel: 'info',
}),
)
// React Router request handler
const reactRouterHandler = createRequestHandler({
build: () =>
viteDevServer.ssrLoadModule(
'virtual:react-router/server-build',
) as Promise<any>,
})
const app = express()
// Use Vite dev middleware
app.use(viteDevServer.middlewares)
// TUS upload endpoints (before React Router)
app.all('/api/upload/tus', (req, res) => tusServer.handle(req, res))
app.all('/api/upload/tus/{*splat}', (req, res) =>
tusServer.handle(req, res),
)
// Handle Chrome DevTools requests silently
app.get('/.well-known/appspecific/com.chrome.devtools.json', (req, res) => {
res.status(404).end()
})
// React Router handles all other routes
app.all('/{*splat}', reactRouterHandler)
const port = process.env.PORT || 3000
const server = app.listen(port, () => {
console.log(`🚀 Server running at http://localhost:${port}`)
console.log(`📁 TUS uploads: /api/upload/tus`)
console.log(`📁 XHR uploads: /api/upload/xhr`)
console.log(`Press Ctrl+C to stop the server`)
})
// Graceful shutdown handling
const shutdown = async (signal: string) => {
console.log(`\n${signal} received. Shutting down gracefully...`)
server.close(async (err) => {
if (err) {
console.error('Error during server close:', err)
process.exit(1)
}
try {
await viteDevServer.close()
console.log('✅ Server closed successfully')
process.exit(0)
} catch (closeErr) {
console.error('Error closing Vite dev server:', closeErr)
process.exit(1)
}
})
// Force exit after 10 seconds if graceful shutdown fails
setTimeout(() => {
console.error('❌ Forceful shutdown after timeout')
process.exit(1)
}, 10000)
}
// Handle various shutdown signals
process.on('SIGINT', () => shutdown('SIGINT'))
process.on('SIGTERM', () => shutdown('SIGTERM'))
process.on('SIGHUP', () => shutdown('SIGHUP'))
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err)
shutdown('UNCAUGHT_EXCEPTION')
})
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason)
shutdown('UNHANDLED_REJECTION')
})
} catch (error) {
console.error('Failed to start server:', error)
process.exit(1)
}
}
// Start the server
startServer()