7.0 KiB
7.0 KiB
name, description
| name | description |
|---|---|
| elysia-best-practices | Use when writing, reviewing, or refactoring Elysia server code in this repository, including route handlers, schemas, response statuses, lifecycle hooks, plugins, OpenAPI docs, health checks, render API endpoints, and queue-facing API behavior. |
Elysia Best Practices
Overview
Apply current Elysia guidance to this Bun Remotion rendering service while
preserving its API contract: /api/render can enqueue async jobs, render
synchronously, report BullMQ status, cancel jobs, and expose health checks.
Workflow
- Fetch current Elysia docs first with Context7. This repo currently uses
elysia1.4.27,@elysiajs/openapi1.4.14, and@elysiajs/swagger1.3.1; compare docs against those packages before adopting examples that use newer@elysia/openapiimports. - Identify the layer being changed:
- HTTP entrypoint:
server/index.ts - Shared request schemas:
server/types/DocumentSchema.ts,server/types/CaptionStyleSchema.ts - Queue behavior:
server/services/render_queue.ts - Rendering and cleanup:
server/services/render_video.ts - S3/webhooks/config:
server/services/s3.ts,server/services/webhook.ts,server/config.ts
- HTTP entrypoint:
- Keep Elysia routes thin. Validate and shape HTTP inputs at the route, then delegate rendering, queue, upload, and webhook work to services.
- Verify with
bun run lintand a focused smoke test throughbun run server,/api/health, and the affected/api/renderpath.
Project Map
- Runtime: Bun with strict TypeScript and path aliases from
tsconfig.json. - Server root:
new Elysia({ prefix: "/api" })inserver/index.ts. - Current endpoints:
POST /api/render: async queue whencallbackUrlis present; synchronous render/upload fallback otherwise.GET /api/render/:renderId: BullMQ job state and progress.DELETE /api/render/:renderId: cancellation.GET /api/health: liveness response.
- Existing schemas use Elysia
tandStatic; reuse this style instead of introducing Zod, Valibot, or ad hoc runtime checks.
Route Contracts
- Define schemas for
body,params,query,headers, andresponse. Elysia infers handler types from schemas, so avoid duplicate TypeScript interfaces unless they are exported fromStatic<typeof Schema>. - For route responses with multiple statuses, define
responseby status code and returnstatus(code, body)from the handler. Prefer this for new code over mutatingset.status; current docs callset.statuslegacy and it cannot validate response types as precisely. - Preserve existing API payload field names unless intentionally migrating a
client contract:
renderId,status,progress_pct,output_path,callback_delivered, anderror. - Use literal unions for finite render states and style values. Keep captions
and transcription schemas reusable in
server/types/, not embedded inside handlers. - Validate URL-like inputs that leave the service boundary. For new fields such as callback or source URLs, prefer a schema-level constraint or a small service validator before queueing work.
Status And Errors
- Use the handler context
status()function for expected outcomes:return status(202, { renderId, status: "queued" })andreturn status(404, result). - Use
onErrorfor cross-cutting logging and sanitized error responses, and register it before routes it must affect. Do not leak S3 credentials, signed URLs, local output paths, full Remotion CLI logs, or Redis connection details. - Validation failures normally return 422. In production, Elysia omits detailed validation internals by default; keep that behavior unless debugging explicitly requires a temporary override.
- When adding typed domain errors, register classes with
.error()and narrow in.onError(). For one route only, use a local routeerrorhook.
Lifecycle And Plugins
- Hook order matters. Interceptor hooks apply only to routes registered after them; place auth, tracing, error mapping, CORS, or request logging before the routes they should cover.
- Elysia plugin lifecycles and schemas are encapsulated by default. Use
local,scoped, orglobalintentionally when extracting route groups or shared guards, especially if a guard should affect parent/apiroutes. - Prefer
resolvefor per-request values that depend on validated data, such as a parsed render ID, normalized callback URL, or tenant/folder value. Usederivefor request-derived context before validation only when validation is not needed first. - Use
decoratefor stable shared services or helpers, not request-specific mutable data. Avoid putting queue job state in Elysiastore; BullMQ and Redis are the source of truth. - If route count grows, extract plugins by concern, for example
renderRoutes,healthRoutes, andopenApiPlugin, then mount them under the existing prefixed app.
OpenAPI
- If exposing docs, prefer one OpenAPI integration and make it match installed
packages. This repo already has
@elysiajs/openapiand@elysiajs/swagger; do not add@elysia/openapiwithout an intentional dependency migration. - Add
detailmetadata for public endpoints: tags, summary, description, and response examples where helpful. Keep docs accurate by defining runtime schemas for request and response shapes. - Treat render endpoints as operational API, not demo routes. Document async queue behavior, callback delivery, cancellation semantics, and sync fallback separately.
Service Boundaries
- Do not start extra BullMQ workers from route modules. The current server starts one worker at process boot; keep worker lifecycle explicit and graceful.
- Keep cleanup in
finallywhen route code creates local render outputs. Do not leave generated videos or props files behind after uploads or failures. - Avoid blocking request handlers with long synchronous work unless preserving
the existing no-
callbackUrlsync fallback. Prefer queueing for new expensive render operations. - Keep env requirements in
server/config.tsand.env.exampletogether. Never hardcode credentials, bucket names, Redis URLs, or public host assumptions in Elysia handlers.
Validation
- Always run
bun run lintafter TypeScript/Elysia changes. - For schema or status changes, smoke invalid requests and expected non-200 responses, not only the happy path.
- For queue-facing changes, smoke
POST /api/renderwithcallbackUrl,GET /api/render/:renderId, andDELETE /api/render/:renderId. - For docs changes, verify the generated OpenAPI page/spec if the plugin is mounted.
Source Anchors
- Validation and response schemas: https://elysiajs.com/tutorial/getting-started/validation/
- Handler context and
status(): https://elysiajs.com/essential/handler - Lifecycle and hook ordering: https://elysiajs.com/essential/life-cycle
- Error handling: https://elysiajs.com/patterns/error-handling
- Encapsulation, scopes, and guards: https://elysiajs.com/tutorial/getting-started/encapsulation/
- OpenAPI patterns: https://elysiajs.com/patterns/openapi