refactor: replace next with elysia and bun#1
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis change migrates the backend server and API routes from a Next.js/Express/React/TRPC stack to a Bun/Elysia/Supabase-based architecture. It removes all Next.js and related API route files, replacing them with Elysia route modules for document, file, upload, log, and healthcheck endpoints. The server entry point is reimplemented to use Elysia, and dependencies are updated accordingly. Changes
Sequence Diagram(s)High-Level: Request Flow in New Elysia ServersequenceDiagram
participant Client
participant HTTPServer
participant ElysiaApp
participant RouteHandler
participant Database
participant S3
Client->>HTTPServer: Sends HTTP request
HTTPServer->>ElysiaApp: Adapts and forwards request
ElysiaApp->>RouteHandler: Matches and invokes route
RouteHandler->>Database: (if needed) Query/update DB
RouteHandler->>S3: (if needed) Generate presigned URL
RouteHandler-->>ElysiaApp: Response
ElysiaApp-->>HTTPServer: Response
HTTPServer-->>Client: Sends HTTP response
Estimated code review effort🎯 5 (Critical) | ⏱️ ~75+ minutes Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Summary of Changes
Hello @arthrod, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
I've undertaken a significant refactor to transition the project's backend infrastructure from Next.js to a more modern and performant stack utilizing Bun and Elysia. This change involves a complete overhaul of how API routes are handled, moving from Next.js's built-in API system to Elysia's routing capabilities, including its tRPC plugin. The goal is to enhance performance and simplify the server-side architecture. This refactor also includes updating various dependencies and adjusting the build process to align with the new tooling.
Highlights
- Complete Tooling Migration: The project's core tooling has been completely migrated from Next.js to a Bun and Elysia-based backend, leveraging Bun's performance and Elysia's lightweight framework.
- API Route Rerouting and Consolidation: All existing Next.js API routes have been systematically replaced with new Elysia routes, ensuring that all backend functionalities, including tRPC integration, are now handled by the new framework.
- Efficient Static HTML Serving: The server now utilizes
@elysiajs/htmlto efficiently serve static HTML content, streamlining the frontend delivery process within the Elysia ecosystem. - Updated Build and Development Workflow: Key project scripts in
package.jsonhave been updated to usebuncommands for development, building, and starting the application, aligning with the new runtime environment. - TypeScript Configuration for Elysia HTML: The
tsconfig.jsonhas been configured to support Elysia's HTML plugin, enabling JSX syntax for HTML templating directly within TypeScript files.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
There was a problem hiding this comment.
Code Review
This pull request is a major refactoring that replaces the Next.js backend with a new stack based on Bun and Elysia. The changes are extensive, removing all Next.js API routes and replacing them with new, modular Elysia routes. The transition also involves updating the tRPC context, package management, and server entry point. My review focuses on improving the new implementation by leveraging Elysia's features for better type safety and maintainability, such as using plugins for authentication, schemas for body/param validation, and simplifying the server setup.
| const authHeader = headers['authorization'] as string | undefined | ||
| if (!authHeader) { set.status = 401; return { error: 'Authentication required' } } | ||
| const token = authHeader.split(' ')[1] | ||
| const userId = await authenticateUser(token) |
There was a problem hiding this comment.
The authentication logic (extracting the header, splitting the token, and calling authenticateUser) is repeated in every route handler. This creates a lot of boilerplate and makes the code harder to maintain.
Elysia's plugin system and hooks are perfect for centralizing this logic. You can create an authentication plugin that derives the userId and adds it to the context for all protected routes.
Here's an example of how you could create an auth plugin:
// src/server/authPlugin.ts
import { Elysia, TRPCError } from 'elysia';
import { authenticateUser } from './auth';
export const auth = new Elysia({ name: 'auth' })
.derive(async ({ headers }) => {
const authHeader = headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Authorization header missing or invalid' });
}
const token = authHeader.split(' ')[1];
const userId = await authenticateUser(token);
if (!userId) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Invalid token' });
}
return { userId };
});
// Then use it in your routes:
// app.use(auth).get('/api/document/:id', ({ userId, params, set }) => { ... });By applying this plugin, you can remove the repetitive authentication code from each handler, making your routes much cleaner and more secure. This pattern can be applied to all routes requiring authentication in document.ts, files.ts, and upload.ts.
| const server = createServer(async (req, res) => { | ||
| const url = `http://${req.headers.host}${req.url}` | ||
| const request = new Request(url, { | ||
| method: req.method, | ||
| headers: req.headers as any, | ||
| body: req.method === 'GET' || req.method === 'HEAD' ? undefined : (req as any) | ||
| }) | ||
|
|
||
| const response = await app.handle(request) | ||
| res.writeHead(response.status, Object.fromEntries(response.headers as any)) | ||
| const body = Buffer.from(await response.arrayBuffer()) | ||
| res.end(body) | ||
| }) |
There was a problem hiding this comment.
The manual http.createServer implementation is verbose and contains several as any type casts, which reduces type safety. Elysia is designed to handle server setup more elegantly.
You could simplify this by using app.listen() and integrating socket.io with the server instance it returns. This would make the code more idiomatic to Elysia and improve type safety by removing the need for as any.
For example, you could refactor this to something like:
// ... app setup
const server = app.listen({
port: Number(process.env.PORT) || 8080,
hostname: '0.0.0.0'
});
setupWebSocket(server.server!); // Pass the underlying http.Server
console.log(`Server listening on port ${server.port}`);This approach leverages Elysia's built-in server handling and provides a cleaner integration point for socket.io. Note that app.listen returns a Bun.Server object, and its .server property is the underlying http.Server that socket.io can attach to.
| if (!authHeader) { set.status = 401; return { error: 'Authentication required' } } | ||
| const token = authHeader.split(' ')[1] | ||
| const userId = await authenticateUser(token) | ||
| const { id: documentId, versionId } = params as any |
There was a problem hiding this comment.
The use of params as any bypasses Elysia's type inference for route parameters, which undermines type safety. For a route like /api/document/:id/versions/:versionId, Elysia can automatically infer that params is of type { id: string; versionId: string; }.
You should remove the as any cast and rely on the inferred types.
| const { id: documentId, versionId } = params as any | |
| const { id: documentId, versionId } = params |
| } | ||
| try { | ||
| const s3Client = getS3Client() | ||
| const bucketName = process.env.AWS_BUCKET_NAME! |
There was a problem hiding this comment.
Using the non-null assertion operator (!) on process.env.AWS_BUCKET_NAME can lead to a runtime crash if the environment variable is not set. While you have validation in s3.ts, it's safer to avoid direct access with !.
A better approach would be to get the bucket name from a validated configuration object, which you could export from s3.ts after validation. This pattern is also repeated on line 205.
| return { error: 'Invalid token' } | ||
| } | ||
|
|
||
| const { fileName, fileType, fileSize } = body as any |
There was a problem hiding this comment.
The request body is cast to any, which bypasses type checking and validation. Elysia provides a powerful way to handle this with schemas.
You should define a schema for the request body and include it in your route definition. This will give you automatic validation and full type safety for body.
Here's how you can improve it:
import { Elysia, t } from 'elysia'
// ...
const uploadBodySchema = t.Object({
fileName: t.String(),
fileType: t.String(),
fileSize: t.Number()
});
export const registerUploadRoute = (app: Elysia) =>
app.post('/api/upload', async ({ body, headers, set }) => {
// 'body' is now fully typed and validated
const { fileName, fileType, fileSize } = body;
// ...
}, {
body: uploadBodySchema
});This change makes your endpoint more robust and easier to work with.
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 12
🔭 Outside diff range comments (2)
src/server/api/trpc.ts (2)
26-38: Incorrect TRPC context generic — passing the function type instead of the context shape
initTRPC.context<typeof createTRPCContext>()uses the function type, not the resolved context type. This breaks typings and may cause subtle runtime/compile issues. Use the awaited return type:-const t = initTRPC.context<typeof createTRPCContext>().create({ +type TRPCContext = Awaited<ReturnType<typeof createTRPCContext>>; +const t = initTRPC.context<TRPCContext>().create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, });
8-23: Harden auth header parsing and exposeresHeadersin context
- Parse the Authorization header defensively; don’t assume
split(' ').- Expose
resHeadersfrom the fetch adapter so procedures can set cookies/headers (e.g., auth flows).- Avoid noisy error logs when there’s simply no token.
-export const createTRPCContext = async (opts: FetchCreateContextFnOptions) => { - const { req } = opts; - const token = req.headers.get('authorization')?.split(' ')[1]; +export const createTRPCContext = async ({ req, resHeaders }: FetchCreateContextFnOptions) => { + const auth = req.headers.get('authorization') ?? ''; + const token = auth.toLowerCase().startsWith('bearer ') + ? auth.slice(7).trim() + : undefined; let userId: string | null = null; try { userId = await authenticateUser(token); } catch (error) { - console.error('Authentication error:', error); + // Avoid noisy logs when there's simply no token present + if (token) console.warn('Authentication error:', error); } return { prisma, userId, - req, + req, + resHeaders, }; };
🧹 Nitpick comments (5)
tsconfig.json (2)
15-17: tSC doesn’t load language service plugins; verify JSX typing or adjust config
- The "plugins" entry is only used by the TS language service (editor), not by tsc. Since CI uses
npx tsc --noEmit,@kitajs/ts-html-pluginlikely won’t run there.- With
"jsx": "react"plus custom factories, ensure JSX types are available without relying on the plugin (e.g., via@elysiajs/htmltypes).- Alternatively, consider
"jsx": "react-jsx"with an appropriate"jsxImportSource"and removing the custom factories.Please confirm
npx tsc --noEmitpasses locally without editor assistance. If not, we should switch approach.Also applies to: 25-29
24-24: Type resolution note: prefer"types"over"typeRoots"for Bun + Node typingsUsing
"typeRoots"can unintentionally constrain ambient type discovery to@typesonly. For Bun-based projects, it’s common to explicitly include Bun and Node types via"types": ["bun-types", "node"]instead of configuringtypeRoots. Consider replacingtypeRootswith an explicit"types"list if you need Bun typings and predictable ambient types.src/server/routes/healthcheck.ts (1)
5-13: Healthcheck headers/status: no-store and 503 are more appropriate
- Add
Cache-Control: no-storeto avoid intermediary caching.- Use 503 (Service Unavailable) on failure; it’s more conventional for health endpoints than 500.
export const registerHealthcheck = (app: Elysia) => app.get('/api/healthcheck', async ({ set }) => { + set.headers['Cache-Control'] = 'no-store' try { await db.$queryRaw`SELECT 1` return { status: 'healthy' } } catch (error) { console.error('Healthcheck failed:', error) - set.status = 500 + set.status = 503 return { status: 'unhealthy', error: 'Database connection failed' } } })src/server/index.tsx (1)
52-55: Optional: prefer Elysia’s native.listen()and@elysiajs/wsfor Bun-first runtimeGiven the Bun/Elysia move, you can avoid the custom Node HTTP bridge entirely:
- Use
app.listen(port)(Bun/Node compatible) and Elysia’s WS plugin instead of a customcreateServer+ adapter.- This simplifies server code and removes subtle header/body edge cases.
package.json (1)
6-8: Consider adding production build optimizationsThe build script could benefit from production optimizations.
- "build": "bun build src/server/index.ts --outdir dist", + "build": "bun build src/server/index.ts --outdir dist --target=bun --minify",Also consider adding a test script:
"start": "bun run src/server/index.ts", + "test": "bun test",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (25)
next.config.js(0 hunks)package.json(2 hunks)src/app/api/files/[id]/complete/route.ts(0 hunks)src/app/api/log/route.ts(0 hunks)src/app/api/upload/route.ts(0 hunks)src/pages/api/document/[id].ts(0 hunks)src/pages/api/document/[id]/collaborators.ts(0 hunks)src/pages/api/document/[id]/versions.ts(0 hunks)src/pages/api/document/[id]/versions/[versionId].ts(0 hunks)src/pages/api/document/create.ts(0 hunks)src/pages/api/document/index.ts(0 hunks)src/pages/api/files/[id].ts(0 hunks)src/pages/api/files/index.ts(0 hunks)src/pages/api/healthcheck.ts(0 hunks)src/pages/api/trpc/[trpc].ts(0 hunks)src/server/api/trpc.ts(1 hunks)src/server/index.ts(0 hunks)src/server/index.tsx(1 hunks)src/server/routes/document.ts(1 hunks)src/server/routes/files.ts(1 hunks)src/server/routes/healthcheck.ts(1 hunks)src/server/routes/log.ts(1 hunks)src/server/routes/upload.ts(1 hunks)src/utils/api.ts(0 hunks)tsconfig.json(1 hunks)
💤 Files with no reviewable changes (16)
- next.config.js
- src/app/api/log/route.ts
- src/pages/api/files/index.ts
- src/pages/api/healthcheck.ts
- src/pages/api/files/[id].ts
- src/pages/api/document/index.ts
- src/utils/api.ts
- src/app/api/files/[id]/complete/route.ts
- src/app/api/upload/route.ts
- src/server/index.ts
- src/pages/api/document/create.ts
- src/pages/api/document/[id]/versions/[versionId].ts
- src/pages/api/trpc/[trpc].ts
- src/pages/api/document/[id]/collaborators.ts
- src/pages/api/document/[id]/versions.ts
- src/pages/api/document/[id].ts
| "@aws-sdk/client-s3": "^3.864.0", | ||
| "@aws-sdk/s3-presigned-post": "^3.864.0", | ||
| "@aws-sdk/s3-request-presigner": "3.864.0", | ||
| "@aws-sdk/types": "^3.862.0", |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Verify AWS SDK version consistency
All AWS SDK packages use version 3.864.0 except @aws-sdk/types which uses ^3.862.0. This inconsistency could lead to compatibility issues.
- "@aws-sdk/types": "^3.862.0",
+ "@aws-sdk/types": "^3.864.0",📝 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.
| "@aws-sdk/client-s3": "^3.864.0", | |
| "@aws-sdk/s3-presigned-post": "^3.864.0", | |
| "@aws-sdk/s3-request-presigner": "3.864.0", | |
| "@aws-sdk/types": "^3.862.0", | |
| "@aws-sdk/client-s3": "^3.864.0", | |
| "@aws-sdk/s3-presigned-post": "^3.864.0", | |
| "@aws-sdk/s3-request-presigner": "3.864.0", | |
| - "@aws-sdk/types": "^3.862.0", | |
| + "@aws-sdk/types": "^3.864.0", |
🤖 Prompt for AI Agents
In package.json lines 17 to 20, the AWS SDK package versions are inconsistent;
most use version 3.864.0 while @aws-sdk/types uses ^3.862.0. Update the version
of @aws-sdk/types to ^3.864.0 to ensure all AWS SDK packages use the same
version for compatibility.
| const server = createServer(async (req, res) => { | ||
| const url = `http://${req.headers.host}${req.url}` | ||
| const request = new Request(url, { | ||
| method: req.method, | ||
| headers: req.headers as any, | ||
| body: req.method === 'GET' || req.method === 'HEAD' ? undefined : (req as any) | ||
| }) | ||
|
|
||
| const response = await app.handle(request) | ||
| res.writeHead(response.status, Object.fromEntries(response.headers as any)) | ||
| const body = Buffer.from(await response.arrayBuffer()) | ||
| res.end(body) |
There was a problem hiding this comment.
Node⇄Fetch bridging: headers normalization, body streaming, and Set-Cookie handling
- Passing
req.headersdirectly can mis-handle multi-valued headers (e.g.,set-cookieon the way back) and non-string values. - Piping the response body is preferable to buffering everything into memory.
- Multiple
Set-Cookieheaders must be forwarded as an array;Object.fromEntries(response.headers)collapses them.
-const server = createServer(async (req, res) => {
- const url = `http://${req.headers.host}${req.url}`
- const request = new Request(url, {
- method: req.method,
- headers: req.headers as any,
- body: req.method === 'GET' || req.method === 'HEAD' ? undefined : (req as any)
- })
-
- const response = await app.handle(request)
- res.writeHead(response.status, Object.fromEntries(response.headers as any))
- const body = Buffer.from(await response.arrayBuffer())
- res.end(body)
-})
+const server = createServer(async (req, res) => {
+ const url = `http://${req.headers.host}${req.url}`
+
+ // Normalize Node headers -> WHATWG Headers
+ const headers = new Headers()
+ for (const [k, v] of Object.entries(req.headers)) {
+ if (Array.isArray(v)) {
+ for (const vv of v) headers.append(k, vv)
+ } else if (typeof v !== 'undefined') {
+ headers.set(k, String(v))
+ }
+ }
+
+ // Build Request; convert body for non-GET/HEAD
+ const body =
+ req.method === 'GET' || req.method === 'HEAD'
+ ? undefined
+ : ((Readable as any).toWeb?.(req) ?? (req as any))
+
+ const request = new Request(url, { method: req.method, headers, body })
+ const response = await app.handle(request)
+
+ // Write headers excluding set-cookie (handle separately)
+ const headerEntries = Array.from(response.headers.entries()).filter(
+ ([k]) => k.toLowerCase() !== 'set-cookie'
+ )
+ res.writeHead(response.status, Object.fromEntries(headerEntries))
+
+ // Preserve multiple Set-Cookie headers if present
+ const setCookie =
+ (response as any).headers?.getSetCookie?.() ??
+ (response as any).headers?.raw?.()?.['set-cookie'] ??
+ []
+ if (Array.isArray(setCookie) && setCookie.length) {
+ res.setHeader('Set-Cookie', setCookie)
+ }
+
+ // Stream body when possible; avoid sending a body on HEAD
+ if (req.method === 'HEAD' || !response.body) {
+ res.end()
+ return
+ }
+ const nodeStream = (Readable as any).fromWeb?.(response.body as any)
+ if (nodeStream && typeof nodeStream.pipe === 'function') {
+ nodeStream.pipe(res)
+ } else {
+ const buf = Buffer.from(await response.arrayBuffer())
+ res.end(buf)
+ }
+})Add this import outside of the selected change range:
import { Readable } from 'node:stream'🤖 Prompt for AI Agents
In src/server/index.tsx around lines 36 to 47, fix the handling of headers and
response body streaming between Node and Fetch APIs. Instead of passing
req.headers directly to the Request constructor, normalize headers to properly
handle multi-valued and non-string headers. For the response, avoid buffering
the entire body into memory; instead, pipe the response body stream to res.
Also, handle multiple Set-Cookie headers by forwarding them as an array rather
than collapsing them with Object.fromEntries. Additionally, add the import
"import { Readable } from 'node:stream'" at the top of the file outside the
current change range.
There was a problem hiding this comment.
@coderabbitai please refactor to use exclusively elysia.
There was a problem hiding this comment.
Code Implementation 🛠️
Implementation: Replace the manual Node HTTP to Fetch bridge with Elysia's native server handling by starting the Elysia app directly. Remove the createServer block and websocket setup that depends on the Node server. Add .listen(port) on the Elysia app and log readiness via the returned server info. This ensures exclusive use of Elysia, avoiding custom header normalization and streaming logic.
| const server = createServer(async (req, res) => { | |
| const url = `http://${req.headers.host}${req.url}` | |
| const request = new Request(url, { | |
| method: req.method, | |
| headers: req.headers as any, | |
| body: req.method === 'GET' || req.method === 'HEAD' ? undefined : (req as any) | |
| }) | |
| const response = await app.handle(request) | |
| res.writeHead(response.status, Object.fromEntries(response.headers as any)) | |
| const body = Buffer.from(await response.arrayBuffer()) | |
| res.end(body) | |
| const port = Number(process.env.PORT) || 8080 | |
| const server = app.listen(port) | |
| console.log(`Server listening on port ${server?.hostname}:${server?.port}`) |
See review comment here
There was a problem hiding this comment.
@arthrod, here's the refactored code using Elysia exclusively:
import { Elysia } from 'elysia'
import cors from '@elysiajs/cors'
import { html, Html } from '@elysiajs/html'
import { trpc } from '@elysiajs/trpc'
import { websocket } from '@elysiajs/websocket'
import { appRouter } from './api/root'
import { createTRPCContext } from './api/trpc'
import { registerHealthcheck } from './routes/healthcheck'
import { registerLogRoute } from './routes/log'
import { registerUploadRoute } from './routes/upload'
import { registerFileRoutes } from './routes/files'
import { registerDocumentRoutes } from './routes/document'
const app = new Elysia()
.use(cors())
.use(html())
.use(websocket())
.use(trpc(appRouter, { createContext: createTRPCContext }))
.get('/', () => (
<html lang="en">
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
))
// WebSocket endpoint (if needed - adjust based on your websocket.ts implementation)
.ws('/ws', {
open(ws) {
console.log('WebSocket connection opened')
},
message(ws, message) {
console.log('Received:', message)
ws.send(message)
},
close(ws) {
console.log('WebSocket connection closed')
}
})
registerHealthcheck(app)
registerLogRoute(app)
registerUploadRoute(app)
registerFileRoutes(app)
registerDocumentRoutes(app)
const port = process.env.PORT || 8080
app.listen(port)
console.log(`🦊 Elysia is running at http://localhost:${port}`)
export type App = typeof appThis refactored version:
- Removes the custom Node.js HTTP server wrapper
- Uses Elysia's built-in
.listen()method - Adds WebSocket support through Elysia's native WebSocket plugin
- Maintains all your existing route registrations
- Provides a cleaner, more idiomatic Elysia implementation
Note: You may need to adjust the WebSocket implementation based on what's in your ./websocket module. If you have specific WebSocket requirements from that module, you can integrate them into the .ws() endpoint configuration.
There was a problem hiding this comment.
Skipped: comment is from another GitHub bot.
There was a problem hiding this comment.
@arthrod, you're welcome! Glad I could help with the Elysia refactoring. The implementation should now be cleaner and more aligned with Elysia's intended usage patterns.
✨🐰✨
| const plateElementSchema: z.ZodType<any> = z.lazy(() => | ||
| z.object({ | ||
| type: z.string(), | ||
| children: z.array(z.union([plateElementSchema, plateTextSchema])) | ||
| }).passthrough() | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider schema depth limits for recursive validation
The recursive Plate schema could cause stack overflow with deeply nested content.
Add a maximum depth limit to prevent excessive recursion:
-const plateElementSchema: z.ZodType<any> = z.lazy(() =>
- z.object({
- type: z.string(),
- children: z.array(z.union([plateElementSchema, plateTextSchema]))
- }).passthrough()
-)
+const MAX_DEPTH = 10
+const plateElementSchema: z.ZodType<any> = z.lazy(() =>
+ z.object({
+ type: z.string(),
+ children: z.array(z.union([plateElementSchema, plateTextSchema]))
+ }).passthrough().refine(
+ (data) => {
+ const checkDepth = (node: any, depth = 0): boolean => {
+ if (depth > MAX_DEPTH) return false
+ if (node.children && Array.isArray(node.children)) {
+ return node.children.every((child: any) => checkDepth(child, depth + 1))
+ }
+ return true
+ }
+ return checkDepth(data)
+ },
+ { message: `Document nesting exceeds maximum depth of ${MAX_DEPTH}` }
+ )
+)📝 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.
| const plateElementSchema: z.ZodType<any> = z.lazy(() => | |
| z.object({ | |
| type: z.string(), | |
| children: z.array(z.union([plateElementSchema, plateTextSchema])) | |
| }).passthrough() | |
| ) | |
| const MAX_DEPTH = 10 | |
| const plateElementSchema: z.ZodType<any> = z.lazy(() => | |
| z.object({ | |
| type: z.string(), | |
| children: z.array(z.union([plateElementSchema, plateTextSchema])) | |
| }).passthrough().refine( | |
| (data) => { | |
| const checkDepth = (node: any, depth = 0): boolean => { | |
| if (depth > MAX_DEPTH) return false | |
| if (node.children && Array.isArray(node.children)) { | |
| return node.children.every((child: any) => checkDepth(child, depth + 1)) | |
| } | |
| return true | |
| } | |
| return checkDepth(data) | |
| }, | |
| { message: `Document nesting exceeds maximum depth of ${MAX_DEPTH}` } | |
| ) | |
| ) |
🤖 Prompt for AI Agents
In src/server/routes/document.ts around lines 15 to 20, the recursive schema
definition for plateElementSchema lacks a depth limit, which can cause stack
overflow with deeply nested data. Modify the schema to include a maximum depth
parameter that tracks recursion depth and stops further nesting beyond a set
limit, returning an error or empty array to prevent excessive recursion.
| await db.$transaction([ | ||
| db.document.update({ | ||
| where: { id: documentId }, | ||
| data: { users: { disconnect: document.users.map(u => ({ id: u.id })) } } | ||
| }), | ||
| db.version.deleteMany({ where: { documentId } }), | ||
| db.document.delete({ where: { id: documentId } }) | ||
| ]) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Transaction array should use async transaction
The transaction array syntax is deprecated. Use async transaction for better error handling.
- await db.$transaction([
- db.document.update({
- where: { id: documentId },
- data: { users: { disconnect: document.users.map(u => ({ id: u.id })) } }
- }),
- db.version.deleteMany({ where: { documentId } }),
- db.document.delete({ where: { id: documentId } })
- ])
+ await db.$transaction(async (tx) => {
+ await tx.document.update({
+ where: { id: documentId },
+ data: { users: { disconnect: document.users.map(u => ({ id: u.id })) } }
+ })
+ await tx.version.deleteMany({ where: { documentId } })
+ await tx.document.delete({ where: { id: documentId } })
+ })📝 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.
| await db.$transaction([ | |
| db.document.update({ | |
| where: { id: documentId }, | |
| data: { users: { disconnect: document.users.map(u => ({ id: u.id })) } } | |
| }), | |
| db.version.deleteMany({ where: { documentId } }), | |
| db.document.delete({ where: { id: documentId } }) | |
| ]) | |
| await db.$transaction(async (tx) => { | |
| await tx.document.update({ | |
| where: { id: documentId }, | |
| data: { users: { disconnect: document.users.map(u => ({ id: u.id })) } } | |
| }) | |
| await tx.version.deleteMany({ where: { documentId } }) | |
| await tx.document.delete({ where: { id: documentId } }) | |
| }) |
🤖 Prompt for AI Agents
In src/server/routes/document.ts around lines 231 to 238, the current use of
db.$transaction with an array is deprecated. Refactor the code to use the async
transaction syntax by passing an async callback function to db.$transaction.
Inside this callback, perform the update, deleteMany, and delete operations
sequentially using await, ensuring proper error handling and transaction
management.
| const { userId: collaboratorId } = collaboratorSchema.parse(body) | ||
| const user = await db.user.findUnique({ where: { id: collaboratorId } }) | ||
| if (!user) { set.status = 404; return { error: 'User not found' } } | ||
|
|
||
| await db.document.update({ where: { id: documentId }, data: { users: { connect: { id: collaboratorId } } } }) | ||
| return { message: 'Collaborator added successfully' } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add validation for duplicate collaborators
The code doesn't check if the user is already a collaborator before adding them.
const { userId: collaboratorId } = collaboratorSchema.parse(body)
const user = await db.user.findUnique({ where: { id: collaboratorId } })
if (!user) { set.status = 404; return { error: 'User not found' } }
+
+ // Check if already a collaborator
+ const existingCollaborator = await db.document.findFirst({
+ where: {
+ id: documentId,
+ users: { some: { id: collaboratorId } }
+ }
+ })
+ if (existingCollaborator) {
+ set.status = 400
+ return { error: 'User is already a collaborator' }
+ }
await db.document.update({ where: { id: documentId }, data: { users: { connect: { id: collaboratorId } } } })📝 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.
| const { userId: collaboratorId } = collaboratorSchema.parse(body) | |
| const user = await db.user.findUnique({ where: { id: collaboratorId } }) | |
| if (!user) { set.status = 404; return { error: 'User not found' } } | |
| await db.document.update({ where: { id: documentId }, data: { users: { connect: { id: collaboratorId } } } }) | |
| return { message: 'Collaborator added successfully' } | |
| const { userId: collaboratorId } = collaboratorSchema.parse(body) | |
| const user = await db.user.findUnique({ where: { id: collaboratorId } }) | |
| if (!user) { set.status = 404; return { error: 'User not found' } } | |
| // Check if already a collaborator | |
| const existingCollaborator = await db.document.findFirst({ | |
| where: { | |
| id: documentId, | |
| users: { some: { id: collaboratorId } } | |
| } | |
| }) | |
| if (existingCollaborator) { | |
| set.status = 400 | |
| return { error: 'User is already a collaborator' } | |
| } | |
| await db.document.update({ | |
| where: { id: documentId }, | |
| data: { users: { connect: { id: collaboratorId } } } | |
| }) | |
| return { message: 'Collaborator added successfully' } |
🤖 Prompt for AI Agents
In src/server/routes/document.ts around lines 284 to 289, the code adds a
collaborator without checking if they are already associated with the document.
To fix this, add a validation step before the update to query if the user is
already a collaborator on the document. If they are, return an appropriate error
response instead of adding them again. This prevents duplicate collaborators
from being added.
|
|
||
| const s3Client = getS3Client() | ||
| const bucketName = process.env.AWS_BUCKET_NAME | ||
| const filesWithUrls = await Promise.all( |
There was a problem hiding this comment.
Add environment variable validation
Using non-null assertions on environment variables is unsafe. Validate them at startup.
Add environment variable validation at the module level:
// Add at the top of the file after imports
const AWS_BUCKET_NAME = process.env.AWS_BUCKET_NAME
if (!AWS_BUCKET_NAME) {
throw new Error('AWS_BUCKET_NAME environment variable is required')
}Then replace all occurrences:
- const bucketName = process.env.AWS_BUCKET_NAME
+ const bucketName = AWS_BUCKET_NAME- const bucketName = process.env.AWS_BUCKET_NAME!
+ const bucketName = AWS_BUCKET_NAMEAlso applies to: 134-134, 205-205
🤖 Prompt for AI Agents
In src/server/routes/files.ts at lines 89, 134, and 205, environment variables
like AWS_BUCKET_NAME are accessed using non-null assertions, which is unsafe. To
fix this, add validation at the top of the file after imports by assigning
process.env.AWS_BUCKET_NAME to a constant and throwing an error if it is
undefined. Then replace all direct uses of process.env.AWS_BUCKET_NAME with this
validated constant throughout the file.
| set.status = 204 | ||
| return |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Return empty response properly for 204 status
Elysia handles 204 responses differently. Return null or empty string for proper 204 response.
await db.file.delete({ where: { id: fileId } })
set.status = 204
- return
+ return null📝 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.
| set.status = 204 | |
| return | |
| await db.file.delete({ where: { id: fileId } }) | |
| set.status = 204 | |
| return null |
🤖 Prompt for AI Agents
In src/server/routes/files.ts around lines 215 to 216, the code sets the status
to 204 but does not return a null or empty string, which is required by Elysia
to properly handle 204 responses. Modify the return statement to return null or
an empty string after setting the status to 204 to ensure the response is
correctly processed.
| app.get('/api/log', async ({ set }) => { | ||
| try { | ||
| await db.$queryRaw`SELECT 1` | ||
| console.log('Database connection test successful') | ||
| return { status: 'Database connection successful' } | ||
| } catch (error) { | ||
| console.error('Database connection test failed:', error) | ||
| set.status = 500 | ||
| return { | ||
| status: 'Database connection failed', | ||
| error: error instanceof Error ? error.message : 'Unknown error' | ||
| } | ||
| } |
There was a problem hiding this comment.
Avoid leaking internal DB error messages to clients; add no-store and use 503
Returning error.message can leak internals. Align this with the healthcheck behavior and improve cache/status semantics.
export const registerLogRoute = (app: Elysia) =>
app.get('/api/log', async ({ set }) => {
+ set.headers['Cache-Control'] = 'no-store'
try {
await db.$queryRaw`SELECT 1`
console.log('Database connection test successful')
return { status: 'Database connection successful' }
} catch (error) {
console.error('Database connection test failed:', error)
- set.status = 500
- return {
- status: 'Database connection failed',
- error: error instanceof Error ? error.message : 'Unknown error'
- }
+ set.status = 503
+ return { status: 'Database connection failed' }
}
})📝 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.
| app.get('/api/log', async ({ set }) => { | |
| try { | |
| await db.$queryRaw`SELECT 1` | |
| console.log('Database connection test successful') | |
| return { status: 'Database connection successful' } | |
| } catch (error) { | |
| console.error('Database connection test failed:', error) | |
| set.status = 500 | |
| return { | |
| status: 'Database connection failed', | |
| error: error instanceof Error ? error.message : 'Unknown error' | |
| } | |
| } | |
| export const registerLogRoute = (app: Elysia) => | |
| app.get('/api/log', async ({ set }) => { | |
| // Prevent caching of this health endpoint | |
| set.headers['Cache-Control'] = 'no-store' | |
| try { | |
| await db.$queryRaw`SELECT 1` | |
| console.log('Database connection test successful') | |
| return { status: 'Database connection successful' } | |
| } catch (error) { | |
| console.error('Database connection test failed:', error) | |
| // Service Unavailable | |
| set.status = 503 | |
| return { status: 'Database connection failed' } | |
| } | |
| }) |
🤖 Prompt for AI Agents
In src/server/routes/log.ts around lines 5 to 17, avoid exposing internal
database error messages to clients by removing the error.message from the
response. Instead, return a generic error status message. Also, set the HTTP
status code to 503 to indicate service unavailability and add a 'Cache-Control'
header with 'no-store' to prevent caching of the error response.
| const { fileName, fileType, fileSize } = body as any | ||
| if (!fileName || !fileType || !fileSize) { | ||
| set.status = 400 | ||
| return { error: 'Missing required fields' } | ||
| } |
There was a problem hiding this comment.
Add proper type validation and file size limits
Using as any bypasses type safety. Additionally, there's no validation for file size limits or allowed file types.
- const { fileName, fileType, fileSize } = body as any
- if (!fileName || !fileType || !fileSize) {
- set.status = 400
- return { error: 'Missing required fields' }
- }
+ const bodySchema = z.object({
+ fileName: z.string().min(1).max(255),
+ fileType: z.string().regex(/^(image|video|audio|application|text)\/[\w\-\+\.]+$/),
+ fileSize: z.number().positive().max(100 * 1024 * 1024) // 100MB limit
+ })
+
+ const validatedBody = bodySchema.safeParse(body)
+ if (!validatedBody.success) {
+ set.status = 400
+ return { error: 'Invalid request data', details: validatedBody.error.issues }
+ }
+
+ const { fileName, fileType, fileSize } = validatedBody.dataConsider importing zod at the top:
import { Elysia } from 'elysia'
import { randomUUID } from 'crypto'
+import { z } from 'zod'
import type { FileStatus } from '@prisma/client'📝 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.
| const { fileName, fileType, fileSize } = body as any | |
| if (!fileName || !fileType || !fileSize) { | |
| set.status = 400 | |
| return { error: 'Missing required fields' } | |
| } | |
| import { Elysia } from 'elysia' | |
| import { randomUUID } from 'crypto' | |
| import { z } from 'zod' | |
| import type { FileStatus } from '@prisma/client' | |
| // … earlier in the handler … | |
| const bodySchema = z.object({ | |
| fileName: z.string().min(1).max(255), | |
| fileType: z.string().regex(/^(image|video|audio|application|text)\/[\w\-\+\.]+$/), | |
| fileSize: z.number().positive().max(100 * 1024 * 1024) // 100MB limit | |
| }) | |
| const validatedBody = bodySchema.safeParse(body) | |
| if (!validatedBody.success) { | |
| set.status = 400 | |
| return { error: 'Invalid request data', details: validatedBody.error.issues } | |
| } | |
| const { fileName, fileType, fileSize } = validatedBody.data | |
| // … rest of the handler … |
🤖 Prompt for AI Agents
In src/server/routes/upload.ts around lines 29 to 33, the code uses 'as any' to
cast the request body, which bypasses type safety, and lacks validation for file
size limits and allowed file types. Replace the 'as any' cast with a proper
schema validation using a library like zod: define a schema that specifies
required fields with their types, enforce allowed file types, and set maximum
file size limits. Validate the incoming body against this schema and handle
validation errors by returning appropriate error responses.
There was a problem hiding this comment.
@coderabbitai why do we need type validation with elysia and eden?
There was a problem hiding this comment.
@arthrod, great question! While Eden provides excellent compile-time type safety for TypeScript clients, runtime validation on the server side is still crucial for several reasons:
-
Security at the API boundary: The server endpoints can be called by any HTTP client, not just your Eden-typed frontend. Direct API calls, curl requests, or malicious actors can send arbitrary data that bypasses Eden's type checking.
-
Runtime guarantees: Using
body as anytells TypeScript to trust whatever comes in, but at runtime, the actual data might be malformed, missing fields, or contain invalid values. This can cause crashes or security vulnerabilities. -
Eden + Elysia best practice: Elysia actually has built-in schema validation using its
t(Type) builder that works seamlessly with Eden. When you define schemas in Elysia, Eden automatically infers the types from them, giving you both runtime validation AND type safety:
import { Elysia, t } from 'elysia'
export const registerUploadRoute = (app: Elysia) =>
app.post('/api/upload', async ({ body, set }) => {
// body is already validated and typed!
const { fileName, fileType, fileSize } = body
// ...
}, {
body: t.Object({
fileName: t.String({ minLength: 1, maxLength: 255 }),
fileType: t.String(),
fileSize: t.Number({ minimum: 1, maximum: 100 * 1024 * 1024 })
})
})This way, you get the best of both worlds: Eden clients get automatic type inference, and your server validates all incoming requests regardless of their source.
There was a problem hiding this comment.
Ask❓
@coderabbitai why do we need type validation with elysia and eden?
Answer:
Good question. Even with Elysia and Eden providing typed endpoints and client inference, you still need runtime validation for a few reasons:
- Client input is untrusted: Types from Eden are compile-time only. A malicious or buggy client can send malformed JSON at runtime. Without runtime validation (e.g., zod), your route could accept invalid shapes or missing fields and crash or corrupt data.
- Security and constraints enforcement: Things like max file size, allowed MIME types, and filename length are business rules that must be enforced at runtime. TypeScript can’t enforce these at runtime; validation libraries can.
- Robust error handling: Runtime validation lets you return clear 4xx errors with details instead of 500s, improving DX and debugging.
- Boundary between systems: Data from external sources (browsers, cURL, other services) needs schema checks at the API boundary to prevent unexpected values from propagating into your DB or S3 calls.
- Future-proofing: As schemas evolve, runtime validation helps catch incompatible clients and provides safe migrations.
Concretely in your upload route:
- Using “as any” bypasses safety entirely.
- Adding zod ensures fileName/fileType/fileSize are present and constrained (e.g., 100 MB cap, allowed MIME patterns), preventing oversized uploads and invalid content types from being persisted or presigned.
Summary: Elysia/Eden give strong TypeScript inference and nice DX, but they don’t replace runtime validation against untrusted input. Keep zod (or similar) at the edge to ensure correctness, security, and predictable errors.
There was a problem hiding this comment.
Skipped: comment is from another GitHub bot.
| type: fileType, | ||
| size: fileSize, | ||
| key, | ||
| url: `https://${presignedData.bucket}.s3.${presignedData.region}.amazonaws.com/${key}`, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use dynamic S3 URL construction
The hardcoded S3 URL pattern may not work correctly for all AWS regions and configurations.
- url: `https://${presignedData.bucket}.s3.${presignedData.region}.amazonaws.com/${key}`,
+ url: presignedData.url.split('?')[0], // Use the base URL from presignedDataAlternatively, construct the URL using the AWS SDK's built-in methods or environment variables for the S3 endpoint.
📝 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.
| url: `https://${presignedData.bucket}.s3.${presignedData.region}.amazonaws.com/${key}`, | |
| url: presignedData.url.split('?')[0], // Use the base URL from presignedData |
🤖 Prompt for AI Agents
In src/server/routes/upload.ts at line 49, the S3 URL is constructed using a
hardcoded pattern that may not be valid for all AWS regions or configurations.
To fix this, replace the manual URL construction with a method that dynamically
generates the S3 endpoint URL, such as using the AWS SDK's utilities or
environment variables that provide the correct S3 endpoint. This ensures
compatibility across different regions and setups.
|
/gemini do we have an actual plugin that does what you are saying as opposed to a customnized plugin? // src/server/authPlugin.ts export const auth = new Elysia({ name: 'auth' }) // Then use it in your routes: |
|
That's a great question! The While there might not be a generic, off-the-shelf Elysia plugin that perfectly encapsulates your specific
So, to answer directly, you've correctly implemented a custom plugin for your authentication needs using Elysia's built-in mechanisms. This is generally preferred for application-specific concerns like user authentication over trying to find a generic plugin that might not fit your exact requirements. |
|
@coderabbitai why do we need type validation with elysia and eden? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🌐 Web query: 💡 Result: Eden Treaty is a component of the Elysia framework that facilitates end-to-end type safety between client and server applications. By leveraging TypeScript's type inference, Eden Treaty ensures that types are consistent across both client and server without the need for code generation. (elysiajs.com) How Eden Treaty Provides Type Safety:
Example Usage: To use Eden Treaty, you first export your existing Elysia server type: // server.ts
import { Elysia, t } from 'elysia';
const app = new Elysia()
.get('/hi', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: t.Object({
id: t.Number(),
name: t.String(),
}),
})
.listen(3000);
export type App = typeof app;Then, on the client side, you can consume the Elysia API with full type safety: // client.ts
import { treaty } from '@elysiajs/eden';
import type { App } from './server';
const app = treaty<App>('localhost:3000');
// Call [GET] at '/'
const { data } = await app.hi.get();
// Call [PUT] at '/nendoroid/:id'
const { data: nendoroid, error } = await app.nendoroid({ id: 1895 }).put({
name: 'Skadi',
from: 'Arknights',
});In this example, Eden Treaty ensures that the client-side code is type-safe and consistent with the server-side definitions, providing a seamless and reliable development experience. (elysiajs.com) Citations:
@arthrod Great question! While Eden Treaty provides excellent compile-time type safety, you still need runtime validation for several critical reasons: Compile-Time vs RuntimeTypeScript types are erased during compilation - they don't exist in the JavaScript that actually runs. Eden Treaty ensures your client and server agree on the contract, but it can't validate actual incoming data at runtime. Why Runtime Validation is Essential
// Without runtime validation, this could accept ANY data
app.post('/user', ({ body }) => {
// body could be null, a string, or contain SQL injection!
db.createUser(body); // 💥 Dangerous!
})
// With Elysia's validation
app.post('/user', ({ body }) => {
// Elysia ensures body matches schema at runtime
db.createUser(body); // ✅ Safe - validated data
}, {
body: t.Object({
name: t.String(),
email: t.Email(), // Also validates email format!
age: t.Number({ min: 0, max: 120 })
})
})
// URL: /items?page=2&limit=10
app.get('/items', ({ query }) => {
// Without validation: query.page is "2" (string)
// With validation: query.page is 2 (number)
return getItems(query.page, query.limit);
}, {
query: t.Object({
page: t.Numeric(), // Converts string to number
limit: t.Numeric()
})
})
// Elysia automatically returns 400 with detailed validation errors
// { "error": "Invalid body.email: Expected email format" }
The Best of Both WorldsWith Elysia + Eden, you get:
This combination eliminates the need for separate type definitions, manual validation code, or code generation tools while ensuring both compile-time and runtime safety. |
User description
Summary
Testing
npx tsc --noEmithttps://chatgpt.com/codex/tasks/task_b_6898a6d09a4c8324bf150ba8b452738c
PR Type
Enhancement
Description
Replace Next.js with Elysia and Bun runtime
Convert API routes from Next.js to Elysia handlers
Update tRPC adapter for Elysia integration
Serve static HTML using @elysiajs/html
Diagram Walkthrough
File Walkthrough
22 files
Remove Next.js file completion routeRemove Next.js log routeRemove Next.js upload routeRemove Next.js document API handlerRemove Next.js collaborators API handlerRemove Next.js versions API handlerRemove Next.js version detail handlerRemove Next.js document creation handlerRemove Next.js document list handlerRemove Next.js file management handlerRemove Next.js files list handlerRemove Next.js healthcheck handlerRemove Next.js tRPC adapterUpdate tRPC context for ElysiaRemove Express server implementationAdd Elysia document routesAdd Elysia file management routesAdd Elysia healthcheck routeAdd Elysia log routeAdd Elysia upload routeRemove Next.js tRPC clientAdd new Elysia server with HTML support2 files
Remove Next.js configurationUpdate TypeScript config for JSX1 files
Update dependencies for Bun and Elysia