# Elysia Interactive > Complete documentation for Elysia, a TypeScript web framework. --- Source: [at-glance/index.md](at-glance/index.md) # At a glance Elysia is an ergonomic web framework for building backend servers. It’s designed for simplicity and type safety, with a familiar API, extensive TypeScript support, and optimizations for Bun. Let’s start with a small server. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', ({ params: { id } }) => id) .post('/form', ({ body }) => body) .listen(3000) ``` ## Performance Elysia uses Bun and optimizations such as static code analysis to generate code for handling requests efficiently. The following results come from the [Bun HTTP Framework Benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/f654bcd175165bce2e3c83b65eaecd8e34294ffd#results). Average is the arithmetic mean of requests per second. Startup is in milliseconds. Memory usage shows server RSS before and after the load tests in MB. See the [benchmark conditions](#ref-1). | Framework | Runtime | Request/sec | Startup | Memory usage | | --------- | ------- | ----------: | -------: | ---------------: | | elysia | bun | 214,518.67 | 16.0 ms | 36.3 / 48.7 MB | | h3 | bun | 172,978.673 | 7.3 ms | 33.6 / 50.0 MB | | hono | bun | 169,752.09 | 7.4 ms | 33.5 / 44.3 MB | | effect | bun | 123,353.122 | 32.5 ms | 48.8 / 73.6 MB | | fastify | bun | 122,655.538 | 76.8 ms | 74.9 / 193.8 MB | | fastify | node | 103,292.998 | 69.7 ms | 95.7 / 205.0 MB | | h3 | node | 98,758.288 | 45.5 ms | 85.3 / 190.1 MB | | hono | node | 93,107.478 | 48.9 ms | 97.0 / 220.6 MB | | elysia | node | 92,489.338 | 58.6 ms | 93.0 / 181.3 MB | | effect | node | 88,635.68 | 53.5 ms | 93.0 / 177.6 MB | | express | bun | 63,217.945 | 42.5 ms | 64.9 / 152.4 MB | | koa | node | 44,591.625 | 51.7 ms | 96.1 / 192.2 MB | | express | node | 42,293.953 | 44.2 ms | 87.2 / 208.8 MB | | nest | node | 31,939.89 | 116.5 ms | 118.7 / 224.3 MB | Elysia has also been compared with Go and Rust frameworks in [TechEmpower round 22](#ref-2). Performance depends on the workload, runtime, hardware, and framework version. Measure your own workload before choosing a framework. ## TypeScript Elysia is designed to help you write less TypeScript. It infers types from your routes so you can focus on business logic instead of repeating declarations. ```ts import { Elysia } from 'elysia' new Elysia() .get('/user/:id', ({ params: { id } }) => id) .get('/user/abc', () => 'abc') .listen(3000) ``` The `:id` segment becomes `params.id` both at runtime and in the handler’s types, without a manual declaration. Its type is `string`. TypeScript is recommended, but it isn’t required to use Elysia. Type inference helps while you write code; use a schema when you also need runtime validation. ## Type integrity Import `t` from Elysia to define a schema that validates values at runtime and provides their TypeScript types. That gives your input a single source of truth. Let’s change the path parameter from a string to a number: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/user/:id', { params: t.Object({ id: t.Number() }) }, ({ params: { id } }) => id) .listen(3000) ``` The schema comes before the handler in Elysia 2. A request to `/user/123` gives the handler the number `123`; `/user/abc` fails validation. Hover over `id` in your editor to see the inferred `number` type. The same schema describes the runtime check and the handler’s TypeScript input. ## Standard Schema Elysia supports [Standard Schema](https://github.com/standard-schema/standard-schema), so you can use compatible versions of your preferred validation library. The ecosystem includes Zod, Valibot, ArkType, Effect Schema, Yup, Joi integrations, and [other implementations](https://github.com/standard-schema/standard-schema). Check that the library or adapter you use implements the Standard Schema interface. ```ts import { Elysia } from 'elysia' import { z } from 'zod' import * as v from 'valibot' new Elysia() .get('/id/:id', { params: z.object({ id: z.coerce.number() }), query: v.object({ name: v.literal('Lilith') }) }, ({ params: { id }, query: { name } }) => id) .listen(3000) ``` Here Zod converts the path parameter to a number, while Valibot requires `?name=Lilith`. Elysia infers the validated types from both schemas. ## OpenAPI Elysia works with standards such as OpenAPI, WinterTC, and Standard Schema so you can connect it to tools you already use. Add the [OpenAPI plugin](/plugins/openapi/) to generate API documentation from your routes and schemas: ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .get('/user/:id', { params: t.Object({ id: t.Number() }) }, ({ params: { id } }) => id) .listen(3000) ``` The plugin provides a documentation page you can share with your team. Use a release compatible with Elysia 2; see the plugin reference for installation and configuration. ## OpenAPI Type Gen Elysia also supports generating OpenAPI schemas from TypeScript types with `fromTypes()`: ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use(openapi({ references: fromTypes() })) .get('/user/:id', { params: t.Object({ id: t.Number() }) }, ({ params: { id } }) => id) .listen(3000) ``` This brings a FastAPI-like approach to TypeScript: use your code’s types as input to documentation generation instead of duplicating them in manual annotations. See [OpenAPI from types](/patterns/openapi/#openapi-from-types) for its setup and supported types. ## End-to-end type safety Type safety doesn’t stop at the server. Elysia’s client library, **Eden**, lets your frontend use the server’s types, in a workflow similar to tRPC. Export the type of your server from `server.ts`, then import it in `client.ts`: ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use(openapi({ references: fromTypes() })) .get('/user/:id', { params: t.Object({ id: t.Number() }) }, ({ params: { id } }) => id) .listen(3000) export type App = typeof app ``` ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') // Request /user/617 const { data } = await app.user({ id: 617 }).get() console.log(data) ``` Eden uses the existing Elysia types without a separate client code-generation step. The client imports only the server’s type, keeping server implementation code out of its bundle. Elysia is here to help you build a confident backend, and everything you connect to it. ## Type soundness Successful responses are only part of an API. Elysia can infer **all possible response types**, including errors, so your client can handle them safely. server.ts ```ts import { Elysia, t } from 'elysia' const plugin = new Elysia() .macro({ auth: { cookie: t.Cookie({ session: t.String() }), beforeHandle({ cookie: { session }, status }) { if (session.value !== 'valid') return status(401) } } }) const app = new Elysia() .use(plugin) .get('/user/:id', { auth: true, params: t.Object({ id: t.Number() }) }, ({ params: { id }, status }) => { if (Math.random() > 0.1) return status(420) return id }) .listen(3000) export type App = typeof app ``` Here, Elysia can return a `number` for a successful request but also infer `401` or `420` error in type-level so client can handle all three cases with type safety. client.ts ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') const { data, error } = await app.user({ id: 617 }).get() if (error) console.log(error.status, error.value) else console.log(data) ``` The client can handle the typed success and error results. See [Eden](/eden/) for the client API and [macros](/patterns/macro/) for reusable route behavior. ## Platform agnostic Elysia is optimized for Bun’s native features, but it isn’t limited to Bun. Its [WinterTC-compatible](https://wintertc.org/) Request/Response interface makes other runtime integrations possible. You can use Elysia with: - Bun. - [Node.js](/integrations/node/). - [Deno](/integrations/deno/). - [Cloudflare Workers](/integrations/cloudflare-worker/). - [Vercel](/integrations/vercel/). - [Expo](/integrations/expo/) API routes. - [Next.js](/integrations/nextjs/) API routes. - [Astro](/integrations/astro/) API routes. Each integration describes its adapter and deployment setup. The integrations section has more supported environments. ## Our community We want Elysia to feel friendly and welcoming, with a bright, playful design and our hand-drawn anime mascot, *Elysia chan*. Technology can be approachable and bring a little joy to people’s lives. We still take the framework seriously: reliability and performance matter when you’re trusting it with your next project. Community members have shared production-use stories involving [X](https://x.com/shlomiatar/status/1822381556142362734), [Bank for Agriculture and Agricultural Co-operatives](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13924470), [Cluely](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-14420139), [CS.Money](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13913513), and [Abacate Pay](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13922081). At one point, more than 10,000 open-source projects used Elysia. See [GitHub dependents](https://github.com/elysiajs/elysia/network/dependents) for the changing project list. Development began in 2022 and has continued with contributions from the community. Here are a few places to join in: ### [Discord](https://discord.gg/eaFJ2KDJck) [The official Elysia community server.](https://discord.gg/eaFJ2KDJck) ### [Twitter](https://twitter.com/elysiajs) [Follow Elysia updates and announcements.](https://twitter.com/elysiajs) ### [GitHub](https://github.com/elysiajs) [Explore the source and contribute.](https://github.com/elysiajs) *** 1\. Average throughput, startup time, and server memory usage. See the [benchmark conditions and results at commit f654bcd](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/f654bcd175165bce2e3c83b65eaecd8e34294ffd#results). 2\. The cross-language comparison refers to [TechEmpower benchmark round 22](https://www.techempower.com/benchmarks/#section=data-r22\&hw=ph\&test=composite). --- Source: [blog/elysia-02/index.md](blog/elysia-02/index.md) # Elysia 0.2 - The Blessing 「[Blessing](https://youtu.be/3eytpBOkOFA)」brings many improvements, mainly in TypeScript performance, type-inference, better auto-completion, and new features to reduce boilerplate. Named after YOASOBI’s song「祝福」, an opening for Witch from “Mobile Suit Gundam: The Witch from Mercury”. ## Defers / Lazy Loading Module Elysia 0.2 now adds support for lazy loading modules and async plugins. This makes it possible to defer plugin registration and incrementally apply plugins after the Elysia server has started, achieving the fastest possible start-up time in Serverless/Edge environments. To create a deferred module, simply mark the plugin as async: ```typescript const plugin = async (app: Elysia) => { const stuff = await doSomeHeavyWork() return app.get('/heavy', stuff) } app.use(plugin) ``` ### Lazy Loading Some modules might be heavy, and importing them before starting the server might not be a good idea. We can tell Elysia to skip the module and register it when it finishes loading by using an `import` statement in `use`: ```typescript app.use(import('./some-heavy-module')) ``` This will register the module after the import is finished, making the module lazy-loaded. Deferred plugins and lazy loading modules will have full type-inference available right out of the box. ## Reference Model Now Elysia can memorize schemas and reference them directly in Schema fields, without creating an import file via `Elysia.setModel`. This list of available schemas brings auto-completion, complete type-inference, and validation as you would expect from inline schemas. To use a reference model, first, register the model with `setModel`, then write a model name to reference a model in `schema`. ```typescript const app = new Elysia() .setModel({ sign: t.Object({ username: t.String(), password: t.String() }) }) .post('/sign', ({ body }) => body, { schema: { body: 'sign', response: 'sign' } }) ``` This will provide auto-completion of known models. ![Screenshot 2566-01-23 at 13 24 28](https://user-images.githubusercontent.com/35027979/213980696-8f20a934-c500-4f97-884c-ff2dd2efadfe.png) And type references prevent you from accidentally returning an invalid type. ![Screenshot 2566-01-23 at 13 26 00](https://user-images.githubusercontent.com/35027979/213980738-0e99cb25-a50f-4888-8879-f00d4ad04363.png) Using `@elysiajs/swagger` will also create a separate `Model` section to list available models. ![Screenshot 2566-01-23 at 13 23 41](https://user-images.githubusercontent.com/35027979/213980936-5857e30b-fd4b-4fc3-8aff-fdb9054980d3.png) References also handle validation as you would expect. In short, it’s the same as using inline schemas, but now you only need to type the name of the schema to handle validation and typing, instead of a long list of imports. ## OpenAPI Detail field Introducing a new field, `schema.detail`, for customizing details for the route, following the standard of OpenAPI Schema V2 with auto-completion. ![Screenshot 2566-01-23 at 13 54 11](https://user-images.githubusercontent.com/35027979/213981321-5717e514-aa4b-492a-b45a-9e69099dc8a8.png) This allows you to write better documentation and have a fully editable Swagger as you want: ![Screenshot 2566-01-23 at 13 23 41](https://user-images.githubusercontent.com/35027979/213981545-46efc6cc-34bc-4db2-86ed-530d27d7ba97.png) ## Union Type The previous version of Elysia sometimes had problems with distinct Union types, as Elysia tried to catch the response to create a full type reference for Eden. This resulted in invalidation of possible types. ## Union Response Made possible by Union Type, returning multiple response statuses for `schema` is now available using `schema.response[statusCode]`. ```typescript app .post( '/json/:id', ({ body, params: { id } }) => ({ ...body, id }), { schema: { body: 'sign', response: { 200: t.Object({ username: t.String(), password: t.String(), id: t.String() }), 400: t.Object({ error: t.String() }) } } } ) ``` Elysia will try to validate all schemas in `response`, allowing one of the types to be returned. Return types are also supported and reported in Swagger’s response. ## Faster Type Inference As Elysia 0.1 explored the possibility of using type inference to improve Developer Experience, we found that sometimes it took a long time to update type inference because of heavy type inference and inefficient custom generics. With Elysia 0.2, type inference is now optimized for speed, preventing duplication of heavy type unwrapping, resulting in better performance for updating types and inference. ## Ecosystem With Elysia 0.2 enabling async plugins and deferred modules, many new plugins that weren’t possible before have become a reality. For example: - Elysia Static plugin with non-blocking capability - Eden with union-type inference for multiple responses - New Elysia Apollo Plugin for Elysia ### Notable Improvements: - `onRequest` and `onParse` can now access `PreContext` - Support `application/x-www-form-urlencoded` by default - Body parser now parses `content-type` with extra attributes, e.g., `application/json;charset=utf-8` - Decode URI path parameters - Eden now reports an error if Elysia is not installed - Skip declaration of existing models and decorators ### Breaking Changes: - `onParse` now accepts `(context: PreContext, contentType: string)` instead of `(request: Request, contentType: string)` - To migrate, add `.request` to context to access `Request` ### Afterward Thank you for supporting Elysia and for your interest in this project. This release brings better DX and hopefully everything you need to write great software with Bun. We now have a [Discord server](https://discord.gg/eaFJ2KDJck) where you can ask any questions about Elysia or just hang out and chill—everyone is welcome. With these wonderful tools, we are excited to see what amazing software you will build. > Not to be part of those images someone paints > > Not advancing in that show chosen by someone else > > You and I, alive to write our story > > Will never let you be lone and be gone from your side --- Source: [blog/elysia-03/index.md](blog/elysia-03/index.md) # Elysia 0.3 - 大地の閾を探して \[Looking for Edge of Ground] This release is named after Camellia’s song [「大地の閾を探して \[Looking for Edge of Ground\]」](https://youtu.be/oyJf72je2U0) ft. Hatsune Miku, the last track of my favorite Camellia album,「U.U.F.O」. This song has a high impact on me personally, so I’m not taking the name lightly. This is the most challenging update, bringing the biggest release of Elysia yet, with a rethinking and redesign of Elysia’s architecture to be highly scalable while making as few breaking changes as possible. I’m pleased to announce the release candidate of Elysia 0.3 with exciting new features coming right up. ## Elysia Fn Introducing Elysia Fn. Run any backend function on the frontend with full auto-completion and full type support. [](/blog/elysia-03/elysia-fn.mp4) For rapid development, Elysia Fn allows you to “expose” backend code to call from the frontend with full type-safety, autocompletion, original code comments, and “click-to-definition”, allowing you to speed up the development. You can use Elysia Fn with Eden for full-type safety via Eden Fn. ### Permission You can limit the function’s allow or deny scopes, check for an authorization header and other header fields, validate parameters, or limit access to keys programmatically. Key checking supports type-safety and auto-completion of all possible functions, so you’re not missing out on some functions or accidentally typing down the wrong name. ![Narrowed Key](/blog/elysia-03/narrowed-key.webp) And narrowing the scope of a property programmatically also narrows down the type of parameters, providing full type-safety. ![Narrowed Params](/blog/elysia-03/narrowed-param.webp) ### Technical detail In technical terms, Elysia Fn uses JavaScript’s Proxy to capture object properties and parameters to create batched requests for the server to handle and return values across the network. Elysia Fn extends superjson, allowing native types in JavaScript like Error, Map, Set, and undefined to be parsed across JSON data. Elysia Fn supports multiple use-cases, for example accessing Prisma on the client-side Nextjs app. Theoretically, it’s possible to use Redis, Sequelize, RabbitMQ, and more. As Elysia is running on Bun, Elysia Fn can run over 1.2 million operations/second concurrently (tested on M1 Max). Learn more about Elysia Fn at [Eden Fn](/eden/). ## Type Rework Over 6.5-9x faster for type checking, and an uncountable reduction in the types’ LoC. In Elysia 0.3, over 80% of Elysia and Eden types have been rewritten to focus on performance, type-inference, and fast auto-completion. Testing for over 350 routes with complex types, Elysia uses only 0.22 seconds to generate a type declaration to use with Eden. As Elysia routes now compile directly to literal objects instead of Typebox references, Elysia’s type declaration is much smaller than it was in 0.2 and is easier for Eden to consume. And by much smaller, it means 50-99% smaller. Not only is Elysia’s integration with TypeScript significantly faster, but Elysia is also better at understanding TypeScript and your code. For example, with 0.3, Elysia will be less strict with plugin registration, allowing you to register the plugin without full type-completion of Elysia Instance. Inlining the `use` function now infers the parent type, and the nested guard can reference types of models from the parent more accurately. Type declarations can now also be built and exported. With the type rewrite, Elysia understands TypeScript far better than it used to. Type-completion will be significantly faster than it was, and we encourage you to try it out to see how fast it is. For more detail, see this [thread on Twitter](https://twitter.com/saltyAom/status/1629876280517869568?s=20). ## File Upload Thanks to Bun 0.5.7, Form Data is implemented and enabled by default in Elysia 0.3 with `multipart/formdata`. To define type completion and validation for uploading a file, `Elysia.t` now extends TypeBox with `File` and `Files` for file validation. The validation includes checking for file type with auto-completion of standard file size, the minimum and maximum size of the file, and the total number of files per field. Elysia 0.3 also features `schema.contentType` to explicitly validate the incoming request type to strictly check headers before validating the data. ## OpenAPI Schema 3.0.x With Elysia 0.3, Elysia now uses OpenAPI schema 3.0.x by default to better state API definitions and better support multiple types based on content-type. `schema.details` is now updated to OpenAPI 3.0.x, and Elysia also updates the Swagger plugin to match the OpenAPI 3.0.x to take advantage of new features in OpenAPI 3 and Swagger, especially with file uploading. ## Eden Rework To support more demand for Elysia, including Elysia Fn and Rest together, Eden has been reworked to scale with the new architecture. Eden now exports 3 types of functions. - [Eden Treaty](/eden/treaty/overview/) `eden/treaty`: Original Eden syntax you know and love - [Eden Fn](/eden/) `eden/fn`: Access to Eden Fn - [Eden Fetch](/eden/fetch/) `eden/fetch`: Fetch-like syntax, for highly complex Elysia type (> 1,000 routes / Elysia instance) With the rework of type definitions and support for Elysia Eden, Eden is now much faster and better at inferring types from the server. Auto-completion is faster and uses fewer resources than it used to. In fact, Eden’s type declaration has been almost 100% reworked to reduce the size and inference time, making it support over 350 routes of auto-completion in a blink of an eye (\~0.26 seconds). To make Elysia Eden fully type-safe, with Elysia’s better understanding of TypeScript, Eden can now narrow down the type based on response status, allowing you to capture the type correctly under any condition. ![Narrowed error.webp](/blog/elysia-03/narrowed-error.webp) ### Notable Improvement: - Add string format: ‘email’, ‘uuid’, ‘date’, ‘date-time’ - Update @sinclair/typebox to 0.25.24 - Update Raikiri to 0.2.0-beta.0 (ei) - Add file upload test thanks to #21 (@amirrezamahyari) - Pre compile lowercase method for Eden - Reduce complex instruction for most Elysia types - Compile `ElysiaRoute` type to literal - Optimize type compilation, type inference and auto-completion - Improve type compilation speed - Improve TypeScript inference between plugin registration - Optimize TypeScript inference size - Context creation optimization - Use Raikiri router by default - Remove unused function - Refactor `registerSchemaPath` to support OpenAPI 3.0.3 - Add `error` inference for Eden - Mark `@sinclair/typebox` as optional `peerDependencies` Fix: - Raikiri 0.2 throws an error on not found - Union response with `t.File` is not working - Definitions aren’t defined on Swagger - details are missing on the group plugin - group plugin is unable to compile schema - group is not exportable because EXPOSED is a private property - Multiple cookies don’t set `content-type` to `application/json` - `EXPOSED` is not exported when using `fn.permission` - Missing merged return type for `.ws` - Missing nanoid - context side-effects - `t.Files` in swagger refers to a single file - Eden response type is unknown - Unable to type `setModel` inference definition via Eden - Handle errors thrown in a non-permission function - Exported variable has or is using name ‘SCHEMA’ from external module - Exported variable has or is using name ‘DEFS’ from external module - Possible errors for building Elysia app with `declaration: true` in `tsconfig.json` Breaking Change: - Rename `inject` to `derive` - Deprecate `ElysiaRoute`, changed to inline - Remove `derive` - Update from OpenAPI 2.x to OpenAPI 3.0.3 - Move context.store\[SYMBOL] to meta\[SYMBOL] ## Afterward With the introduction of Elysia Fn, I’m personally excited to see how it will be adopted in frontend development, removing the line between frontend and backend. And Elysia’s Type Rework makes type-checking and auto-completion much faster. I’m excited to see how you will use Elysia to create the wonderful things you are going to build. We have a [Discord server](https://discord.gg/eaFJ2KDJck) dedicated to Elysia. Feel free to say hi or just chill and hang out. Thank you for supporting Elysia. > Under a celestial map that never have ends > > On a cliff that never have name > > I just holwed > > Hoping the neverending reverberation will reach you > > And I believe someday, I will stand on edge of the ground > > (Until the day I can be back to you to tell it) --- Source: [blog/elysia-04/index.md](blog/elysia-04/index.md) # Elysia 0.4 - 月夜の音楽会 (Moonlit Night Concert) Named after the opening music of [“The Liar Princess and the Blind Prince” trailer](https://youtu.be/UdBespMvxaA), [「月夜の音楽会」(Moonlit Night Concert)](https://youtu.be/o8b-IQulh1c) composed and sung by Akiko Shikata. This version doesn’t introduce an exciting new feature, but rather a more solid foundation and internal improvements for the future of Elysia. ## Ahead of Time Compile By default, Elysia has to deal with conditional checking in various situations, for example, checking if the route’s life-cycle exists before running it, or unwrapping a validation schema if provided. This introduces minimal overhead to Elysia overall because even if the route doesn’t have a life-cycle event attached to it, it needs to be runtime checked. Since every function is checked at compile time, it’s not possible to have a conditional async function, for example, a simple route that returns a file should be synchronous, but since it’s compile-time checking, some routes might be async thus making the same simple route async too. An async function introduces an additional tick to the function, making it a bit slower. But since Elysia is a foundation for web servers, we want to optimize every part to make sure that you don’t run into performance problems. We fix this small overhead by introducing Ahead of Time Compilation. As the name suggests, instead of checking dynamic life-cycle and validation at runtime, Elysia checks life-cycle, validation, and the possibility of an async function and generates a compact function, removing unnecessary parts like unused life-cycle events and validation. This makes conditional async functions possible, since instead of using a centralized function for handling, we compose a new function especially created for each route. Elysia then checks all life-cycle functions and handlers to see if there’s an async function, and if not, the function will be synchronous to reduce additional overhead. ## TypeBox 0.26 TypeBox is a library that powers Elysia’s validation and type provider to create a type-level single source of truth, re-exported as **Elysia.t**. In this update, we update TypeBox from 0.25.4 to 0.26. This brings a lot of improvements and new features, for example, a `Not` type and `Convert` for `coercion` values which we might support in a future version of Elysia. But one benefit for Elysia would be `Error.First()`, which allows us to get the first error of a type instead of using an iterator. This reduces overhead in creating a new Error to send back to the client. There are some changes to **TypeBox** and **Elysia.t** that normally wouldn’t have much effect on your end, but you can see the new features in [TypeBox release here.](https://github.com/sinclairzx81/typebox/blob/master/changelog/0.26.0.md) ## Validate response per status Previously, Elysia’s response validation checked responses with multiple statuses using a union type. This might have unexpected results for highly dynamic apps with a strict response for status. For example, if you have a route like this: ```ts app.post('/strict-status', process, { schema: { response: { 200: t.String(), 400: t.Number() } } }) ``` It’s expected that if a 200 response is not a string, then it should throw a validation error, but in reality, it wouldn’t throw an error because response validation is using a union. This might leave an unexpected value to the end user and a type error for Eden Treaty. With this release, a response is validated per status instead of union, which means that it will strictly validate based on response status instead of unioned type. ## Separation of Elysia Fn Elysia Fn is a great addition to Elysia. With Eden, it breaks the boundary between client and server allowing you to use any server-side function in your client, fully type-safe and even with primitive types like Error, Set, and Map. But with the primitive type support, Elysia Fn depends on “superjson” which is around 38% of Elysia’s dependency size. In this release, to use Elysia Fn, you’re required to explicitly install `@elysiajs/fn` to use Elysia Fn. This approach is like installing an additional feature, the same as `cargo add --feature`. This makes Elysia lighter for servers that don’t use Elysia Fn, following our philosophy, **To ensure that you have what you actually need**. However, if you forget to install Elysia Fn and accidentally use Elysia Fn, there will be a type warning reminding you to install Elysia Fn before usage, and a runtime error telling you the same thing. For migration, besides a breaking change of installing `@elysiajs/fn` explicitly, there’s no migration needed. ## Conditional Route This release introduces the `.if` method for registering a conditional route or plugin. This allows you to declaratively register routes for a specific condition, for example, excluding Swagger documentation from the production environment. ```ts const isProduction = process.env.NODE_ENV === 'production' const app = new Elysia().if(!isProduction, (app) => app.use(swagger()) ) ``` Eden Treaty will be able to recognize the route as if it’s a normal route/plugin. ## Custom Validation Error Big thanks to amirrezamahyari on [#31](https://github.com/elysiajs/elysia/pull/31) which allows Elysia to access TypeBox’s error property, for a better programmatic error response. ```ts new Elysia() .onError(({ code, error, set }) => { if (code === 'NOT_FOUND') { set.status = 404 return 'Not Found :(' } if (code === 'VALIDATION') { set.status = 400 return { fields: error.all() } } }) .post('/sign-in', () => 'hi', { schema: { body: t.Object({ username: t.String(), password: t.String() }) } }) .listen(3000) ``` Now you can create a validation error for your API not limited to only the first error. *** ### Notable Improvement: - Update TypeBox to 0.26.8 - Inline a declaration for response type - Refactor some types for faster response - Use Typebox `Error().First()` instead of iteration - Add `innerHandle` for returning an actual response (for benchmark) ### Breaking Change: - Separate `.fn` to `@elysiajs/fn` ## Afterward This release might not be a big release with a new exciting feature, but it improves the foundation and provides a Proof of Concept for plans I have for Elysia in the future, making Elysia even faster and more versatile than it was. I’m really excited for what will unfold in the future. Thank you for your continuous support for Elysia\~ > the moonlit night concert, our secret > > let’s start again without letting go of this hand > the moonlit night concert, our bonds > > I want to tell you, “you are not a liar” > the memories in my heart is like flower that keeps blooming > > no matter what you look like, you are my songstress > > be by my side tonight --- Source: [blog/elysia-05/index.md](blog/elysia-05/index.md) # Elysia 0.5 - Radiant Named after Arknights’ original music, 「[Radiant](https://youtu.be/QhUjD--UUV4)」composed by Monster Sirent Records. Radiant pushes the boundary of performance with more stability improvements, especially for types and dynamic routes. ## Static Code Analysis Elysia 0.4 introduced Ahead of Time compilation, allowing Elysia to optimize function calls and eliminate much of the overhead we previously had. Today we are expanding Ahead of Time compilation to be even faster with Static Code Analysis, to be the fastest Bun web framework. Static Code Analysis allows Elysia to read your functions, handlers, life-cycle, and schema, then try to adjust the fetch handler, compile the handler ahead of time, eliminate any unused code, and optimize where possible. For example, if you’re using `schema` with a body type of Object, Elysia expects that this route is JSON first, and will parse the body as JSON instead of relying on dynamic checking with Content-Type header: ```ts app.post('/sign-in', ({ body }) => signIn(body), { schema: { body: t.Object({ username: t.String(), password: t.String() }) } }) ``` This allows us to improve performance of body parsing by almost 2.5x. With Static Code Analysis, instead of betting on whether you will use expensive properties, Elysia can read your code and detect what you will be using, and adjust itself ahead of time to fit your needs. This means that if you’re not using expensive properties like `query`, or `body`, Elysia will skip the parsing entirely to improve the performance. ```ts // Body is not used, skip body parsing app.post('/id/:id', ({ params: { id } }) => id, { schema: { body: t.Object({ username: t.String(), password: t.String() }) } }) ``` With Static Code Analysis, and Ahead of Time compilation, you can rest assured that Elysia is very good at reading your code and adjusts itself to maximize the performance automatically. Static Code Analysis allows us to improve Elysia performance beyond what we have imagined. Here are some notable improvements: - overall improvement by \~15% - static router faster by \~33% - empty query parsing \~50% - strict type body parsing faster by \~100% - empty body parsing faster by \~150% With this improvement, we are able to surpass **Stricjs** in terms of performance, when compared using Elysia 0.5.0-beta.0 and Stricjs 2.0.4. We intend to explain this topic and how we improve performance with Static Code Analysis in more detail in our research paper, to be published in the future. ## New Router, “Memoirist” Since 0.2, we have been building our own Router, “Raikiri”. Raikiri served its purpose. It’s built from the ground up to be fast with our custom Radix Tree implementation. But as we progress, we found that Raikiri doesn’t perform well with complex Radix Tree reconciliation, which causes developers to report many bugs, especially with dynamic routes, which are usually solved by re-ordering routes. We understood and patched many areas in Raikiri’s Radix Tree reconciliation algorithm. However, our algorithm is complex and gets more expensive as we patch the router, until it doesn’t fit our purpose anymore. That’s why we introduce a new router, “Memoirist”. Memoirist is a stable Radix Tree router to quickly handle dynamic paths based on Medley Router’s algorithm, while on the static side it takes advantage of Ahead of Time compilation. With this release, we will be migrating from Raikiri to Memoirist for stability improvement and even faster path mapping than Raikiri. We hope that any problems you have encountered with Raikiri will be solved with Memoirist and we encourage you to give it a try. ## TypeBox 0.28 TypeBox is a core library that powers Elysia’s strict type system known as **Elysia.t**. In this update, we update TypeBox from 0.26 to 0.28 to make an even more fine-grained Type System closer to a strictly typed language. We update Typebox to improve Elysia typing system to match new TypeBox features with newer versions of TypeScript like **Constant Generic**. ```ts new Elysia() .decorate('version', 'Elysia Radiant') .model( 'name', Type.TemplateLiteral([ Type.Literal('Elysia '), Type.Union([ Type.Literal('The Blessing'), Type.Literal('Radiant') ]) ]) ) // Strictly check for template literal .get('/', ({ version }) => version) ``` This allows us to strictly check for template literals or a pattern of strings/numbers to validate for you during both runtime and development all at once. ### Ahead of Time & Type System And with Ahead of Time compilation, Elysia can adjust itself to optimize and match schema defined type to reduce overhead. That’s why we introduced a new Type, **URLEncoded**. As we previously mentioned before, Elysia can now take advantage of schema and optimize itself Ahead of Time. Body parsing is one of the more expensive areas in Elysia, which is why we introduce a dedicated type for parsing body like URLEncoded. By default, Elysia will parse body based on body’s schema type as the following: - t.URLEncoded -> `application/x-www-form-urlencoded` - t.Object -> `application/json` - t.File -> `multipart/form-data` - the rest -> `text/plain` However, you can explicitly tell Elysia to parse body with the specific method using `type` as the following: ```ts app.post('/', ({ body }) => body, { type: 'json' }) ``` `type` may be one of the following: ```ts type ContentType = | // Shorthand for 'text/plain' | 'text' // Shorthand for 'application/json' | 'json' // Shorthand for 'multipart/form-data' | 'formdata' // Shorthand for 'application/x-www-form-urlencoded'\ | 'urlencoded' | 'text/plain' | 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded' ``` You can find more details at the [explicit body](/essential/life-cycle/#explicit-parser) page in concept. ### Numeric Type We found that one of the redundant tasks our developers found when using Elysia is parsing numeric strings. That’s why we introduce a new **Numeric** Type. Previously in Elysia 0.4, to parse numeric strings, we need to use `transform` to manually parse the string ourselves. ```ts app.get('/id/:id', ({ params: { id } }) => id, { schema: { params: t.Object({ id: t.Number() }) }, transform({ params }) { const id = +params.id if(!Number.isNaN(id)) params.id = id } }) ``` We found that this step is redundant and full of boilerplate. We want to tap into this problem and solve it in a declarative way. Thanks to Static Code Analysis, Numeric type allows you to define a numeric string and parse it to a number automatically. Once validated, a numeric type will be parsed as a number automatically both at runtime and at the type level to fit our needs. ```ts app.get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Numeric() }) }) ``` You can use numeric type on any property that supports schema typing, including: - params - query - headers - body - response We hope that you will find this new Numeric type useful in your server. You can find more details at the [numeric type](/essential/validation/) page in concept. With TypeBox 0.28, we are making Elysia’s type system more complete, and we are excited to see how it plays out on your end. ## Inline Schema You might have noticed already that our examples are not using a `schema.type` to create a type anymore, because we are making a **breaking change** to move schema and inline it in the hook statement instead. ```ts // ? From app.get('/id/:id', ({ params: { id } }) => id, { schema: { params: t.Object({ id: t.Number() }) }, }) // ? To app.get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) ``` We think a lot when making a breaking change especially to one of the most important parts of Elysia. Based on a lot of tinkering and real-world usage, we tried to suggest this new change to our community with a vote, and found that around 60% of Elysia developers are happy with migrating to the inline schema. But we also listen to the rest of our community, and try to address the arguments against this decision: ### Clear separation With the old syntax, you have to explicitly tell Elysia that the part you are creating is a schema using `Elysia.t`. Creating a clear separation between life-cycle and schema is clearer and has better readability. But from our intensive tests, we found that most people don’t have any problem reading the new syntax or separating life-cycle hooks from schema types. We found that it still has a clear separation with `t.Type` and functions, and different syntax highlighting when reviewing the code. Although the new syntax is not as clear as an explicit schema, people can get used to it very quickly, especially if they are familiar with Elysia. ### Auto completion One of the other areas that people are concerned about is reading auto-completion. Merging schema and life-cycle hook caused the auto-completion to have around 10 properties for auto-complete to suggest, and based on proven general User Experience research, it can be frustrating for users to have that many options to choose from, and can cause a steeper learning curve. However, we found that the schema property name of Elysia is predictable enough to get over this problem once developers are used to Elysia types. For example, if you want to access headers, you can access `headers` in Context, and to type `headers`, you can type headers in a hook. Both share the same name for predictability. With this, Elysia might have a little more learning curve, however it’s a trade-off that we are willing to take for better developer experience. ## “headers” fields Previously, you can get headers field by accessing `request.headers.get`, and you might wonder why we don’t ship headers by default. ```ts app.post('/headers', ({ request: { headers } }) => { return headers.get('content-type') }) ``` Parsing headers has its own overhead, and we found that many developers don’t access headers often, so we decided to leave headers un-implemented. But that has changed with Static Code Analysis. Elysia can read your code to see if you intend to use a header or not, and then dynamically parse headers based on your code. Static Code Analysis allows us to add more new features without any overhead. ```ts app.post('/headers', ({ headers }) => headers['content-type']) ``` Parsed headers will be available as a plain object with a lower-case key of the header name. ## State, Decorate, Model rework One of the main features of Elysia is the ability to customize Elysia to your needs. We revisited `state`, `decorate`, and `setModel`, and we saw that the API is not consistent, and can be improved. We found that many have been using `state` and `decorate` repeatedly when setting multiple values, and want to set them all at once, the same as `setModel`. We want to unify the API specification of `setModel` to be used the same way as `state` and `decorate` to be more predictable. So we renamed `setModel` to `model`, and added support for setting single and multiple values for `state`, `decorate`, and `model` with function overloading. ```ts import { Elysia, t } from 'elysia' const app = new Elysia() // ? set model using label .model('string', t.String()) .model({ number: t.Number() }) .state('visitor', 1) // ? set model using object .state({ multiple: 'value', are: 'now supported!' }) .decorate('visitor', 1) // ? set model using object .decorate({ name: 'world', number: 2 }) ``` And we raised the minimum supported version of TypeScript to 5.0 to improve strict typing with **Constant Generic**. `state`, `decorate`, and `model` now support literal types and template strings to strictly validate types both at runtime and at the type level. ```ts // ? state, decorate, now support literal app.get('/', ({ body }) => number, { body: t.Literal(1), response: t.Literal(2) }) ``` ### Group and Guard We found that many developers often use `group` with `guard`, and that nesting them can be redundant and full of boilerplate. Starting with Elysia 0.5, we add a guard scope for `.group` as an optional second parameter. ```ts // ✅ previously, you need to nest guard inside a group app.group('/v1', (app) => app.guard( { body: t.Literal() }, (app) => app.get('/student', () => 'Rikuhachima Aru') ) ) // ✅ new, compatible with old syntax app.group( '/v1', { body: t.Literal('Rikuhachima Aru') }, app => app.get('/student', () => 'Rikuhachima Aru') ) // ✅ compatible with function overload app.group('/v1', app => app.get('/student', () => 'Rikuhachima Aru')) ``` We hope that you will find all these revisited APIs more useful and better suited to your use-case. ## Type Stability Elysia Type System is complex. We can declare variables at the type level, reference types by name, apply multiple Elysia instances, and even have support for closure-like behavior at the type level, which is really complex to make you have the best developer experience especially with Eden. But sometimes types aren’t working as intended when we update the Elysia version, because we have to manually check them before every release, which can cause human error. With Elysia 0.5, we add unit tests for testing at the type level to prevent possible bugs in the future. These tests will run before every release and, if an error happens, will prevent us from publishing the package, forcing us to fix the type problem. This means that you can now rely on us to check for type integrity for every release, and be confident that there will be fewer bugs in regard to type references. *** ### Notable Improvement: - Add CommonJS support for running Elysia with Node adapter - Remove manual fragment mapping to speed up path extraction - Inline validator in `composeHandler` to improve performance - Use one time context assignment - Add support for lazy context injection via Static Code Analysis - Ensure response non nullability - Add unioned body validator check - Set default object handler to inherit - Using `constructor.name` mapping instead of `instanceof` to improve speed - Add dedicated error constructor to improve performance - Conditional literal fn for checking onRequest iteration - improve WebSocket type Breaking Change: - Rename `innerHandle` to `fetch` - to migrate: rename `.innerHandle` to `fetch` - Rename `.setModel` to `.model` - to migrate: rename `setModel` to `model` - Remove `hook.schema` to `hook` - to migrate: remove schema and curly brace `schema.type`: ```ts // from app.post('/', ({ body }) => body, { schema: { body: t.Object({ username: t.String() }) } }) // to app.post('/', ({ body }) => body, { body: t.Object({ username: t.String() }) }) ``` - remove `mapPathnameRegex` (internal) ## Afterward Pushing the performance boundary of JavaScript with Bun is what we are really excited about! Even with the new features every release, Elysia keeps getting faster, with improved reliability and stability. We hope that Elysia will become one of the choices for the next generation TypeScript framework. We’re glad to see many talented open-source developers bring Elysia to life with their outstanding work like [Bogeychan’s Elysia Node](https://github.com/bogeychan/elysia-polyfills) and Deno adapter, Rayriffy’s Elysia rate limit, and we hope to see yours in the future too! Thanks for your continuous support for Elysia, and we hope to see you in the next release. > I won’t let the people down, gonna raise them high > > We’re getting louder everyday, yeah, we’re amplified > > Stunning with the light > > You’re gonna want to be on my side > > Yeah, you know it’s **full speed ahead** --- Source: [blog/elysia-06/index.md](blog/elysia-06/index.md) # Elysia 0.6 - This Game Named after the opening of the legendary anime, **“No Game No Life”**, 「[This Game](https://youtu.be/kJ04dMmimn8)」composed by Konomi Suzuki. This Game pushes the boundary from medium-size projects to large-scale apps with a re-imagined plugin model, dynamic mode, pushing developer experience with declarative custom errors, collecting more metrics with ‘onResponse’, customizable loose and strict path mapping, TypeBox 0.30 and WinterCG framework interop. ###### (We are still waiting for No Game No Life season 2) ## New Plugin Model This Game introduces a new syntax for plugin registration and a new plugin model internally. Previously you could register a plugin by defining a callback function for an Elysia instance like this: ```ts const plugin = (app: Elysia) => app.get('/', () => 'hello') ``` With the new plugin, you can now turn an Elysia instance into a plugin: ```ts const plugin = new Elysia() .get('/', () => 'hello') ``` This allows any Elysia instance, even an existing one, to be used across applications, removing any possible additional callback and tab spacing. This improves Developer Experience significantly when working with nested groups. ```ts // < 0.6 const group = (app: Elysia) => app .group('/v1', (app) => app .get('/hello', () => 'Hello World') ) // >= 0.6 const group = new Elysia({ prefix: '/v1' }) .get('/hello', () => 'Hello World') ``` We encourage you to use the new model of Elysia plugin instance, as we can take advantage of Plugin Checksum and new possible features in the future. However, we are **NOT deprecating** the callback function method as there are some cases where the function model is useful like: - Inline function - Plugins that require information about the main instance (for example accessing OpenAPI schema) With this new plugin model, we hope that you can make your codebase even easier to maintain. ## Plugin Checksum By default, Elysia plugins use function callbacks to register plugins. This means that if you register a plugin for type declaration, it will duplicate itself just to provide type support, leading to duplication of plugins used in production. This is why Plugin Checksum is introduced, to de-duplicate plugins registered for type declaration. To opt-in to Plugin Checksum, you need to use a new plugin model, and provide a `name` property to tell Elysia to prevent duplicate registration of the plugin. ```ts const plugin = new Elysia({ name: 'plugin' }) ``` This allows Elysia to identify the plugin and deduplicate it based on name. Any duplicated name will be registered only once but type-safety will be provided after registration even if the plugin is deduplicated. In case your plugin needs configuration, you can provide the configuration into a **seed** property to generate a checksum for deduplicating the plugin. ```ts const plugin = (config) = new Elysia({ name: 'plugin', seed: config }) ``` Name and seed will be used to generate a checksum to de-duplicate registration, which leads to even better performance improvement. This update also fixed plugin lifecycle deduplication, which accidentally inlined the lifecycle when Elysia was not sure if the plugin’s event was local or global. As always, this means performance improvement for an app that’s larger than “Hello World”. ## Mount and WinterCG Compliance WinterCG is a standard for web-interoperable runtimes supported by Cloudflare, Deno, Vercel Edge Runtime, Netlify Function and various others. WinterCG is a standard to allow web servers to run interoperably across runtimes, which use Web Standard definitions like Fetch, Request, and Response. With this, Elysia partially follows WinterCG compliance as we are optimized for Bun but also openly support other runtimes if possible. This allows any framework and code that is WinterCG compliant to be run together in theory. An implementation is demonstrated by [Hono](https://honojs.dev) which introduces the **.mount** method to [run multiple frameworks together in one codebase](https://twitter.com/honojs/status/1684839623355490304), including Remix, Elysia, Itty Router, and Hono itself in a simple function. By this, we implemented the same logic for Elysia by introducing the `.mount` method to run any framework or code that is WinterCG compliant. To use `.mount`, [simply pass a `fetch` function](https://twitter.com/saltyAom/status/1684786233594290176): ```ts const app = new Elysia() .get('/', () => 'Hello from Elysia') .mount('/hono', hono.fetch) ``` A **fetch** function is a function that accepts a Web Standard Request and returns a Web Standard Response as the definition of: ```ts // Web Standard Request-like object // Web Standard Response type fetch = (request: RequestLike) => Response ``` By default, this declaration is used by: - Bun - Deno - Vercel Edge Runtime - Cloudflare Worker - Netlify Edge Function - Remix Function Handler This means you can run all of the above code to interoperate with Elysia in a single server, or reuse an existing function in one deployment, without needing to set up a Reverse Proxy for handling multiple servers. If the framework also supports a **.mount** function, you can deeply nest a framework that supports it infinitely. ```ts const elysia = new Elysia() .get('/Hello from Elysia inside Hono inside Elysia') const hono = new Hono() .get('/', (c) => c.text('Hello from Hono!')) .mount('/elysia', elysia.fetch) const main = new Elysia() .get('/', () => 'Hello from Elysia') .mount('/hono', hono.fetch) .listen(3000) ``` You can even reuse multiple existing Elysia projects in your server. ```ts import A from 'project-a/elysia' import B from 'project-b/elysia' import C from 'project-c/elysia' new Elysia() .mount(A) .mount(B) .mount(C) ``` If an instance passed to mount is an Elysia instance, it will resolve to `use` automatically, providing type-safety and support for Eden by default. This made the possibility of interoperable frameworks and runtimes a reality. ## Improved startup time Startup time is an important metric in a serverless environment in which Elysia excels incredibly, but we have taken it even further. By default, Elysia generates OpenAPI schema for every route automatically and stores it internally even if it is not used. In this version, Elysia defers the compilation and moves it to `@elysiajs/swagger` instead, allowing Elysia’s startup time to be even faster. And with various micro-optimizations made possible by the new Plugin model, startup time is now up to 35% faster. ## Dynamic Mode Elysia introduces Static Code Analysis and Ahead of Time compilation to push the boundary of performance. Static Code Analysis allows Elysia to read your code then produce the most optimized version of the code, allowing Elysia to push the performance to its limit. Even if Elysia is WinterCG compliant, environments like Cloudflare worker don’t support function composition. This means that Ahead of Time Compilation isn’t possible, leading us to create a dynamic mode which uses JIT compilation instead of AoT, allowing Elysia to run in Cloudflare Worker as well. To enable dynamic mode, set `aot` to false. ```ts new Elysia({ aot: false }) ``` Dynamic Mode is enabled by default in cloudflare worker. #### It’s worth noting that enabling Dynamic Mode will disable some features like dynamically injected code like `t.Numeric`, which parses strings to numbers automatically. Ahead of Time compilation can read, detect and optimize your code, trading startup time for extra performance gain, while dynamic mode uses JIT compilation, allowing startup time to be up to 6x faster. But it should be noted that startup time in Elysia is fast enough by default. Elysia is able to register 10,000 routes in just 78ms which means an average of 0.0079 ms/route. That being said, we are leaving a choice for you to decide yourself. ## Declarative Custom Error This update adds support for adding type support for handling custom errors. ```ts class CustomError extends Error { constructor(public message: string) { super(message) } } new Elysia() .addError({ MyError: CustomError }) .onError(({ code, error }) => { switch(code) { // With auto-completion case 'MyError': // With type narrowing // Error is typed as CustomError return error } }) ``` This allows us to handle custom types with type narrowing for handling custom errors and auto-completion for error codes to narrow down the correct types, fully type-safe declaratively. This fulfills one of our main philosophies, focusing on Developer Experience especially with types. Elysia Type System is complex, yet we try to prevent our users from needing to write a custom type or pass a custom generic, keeping all the code looking just like JavaScript. It just works, and all the code looks just like JavaScript. ## TypeBox 0.30 TypeBox is a core library that powers Elysia’s strict type system known as **Elysia.t**. In this update, we update TypeBox from 0.28 to 0.30 to make an even more fine-grained Type System close to a strictly typed language. These updates introduce new features and many interesting changes, for example **Iterator** type, reducing package size, TypeScript code generation. And with support for Utility Types like: - `t.Awaited` - `t.Uppercase` - `t.Capitlized` ## Strict Path We got a lot of requests for handling loose path. By default, Elysia handles paths strictly, which means that if you have to support paths with or without an optional `/`, they will not be resolved and you have to duplicate the pathname twice. ```ts new Elysia() .group('/v1', (app) => app // Handle /v1 .get('', handle) // Handle /v1/ .get('/', handle) ) ``` By this, many have been requesting that `/v1/` should also resolve `/v1` as well. With this update, we add support for loose path matching by default, to opt-in into this feature automatically. ```ts new Elysia() .group('/v1', (app) => app // Handle /v1 and /v1/ .get('/', handle) ) ``` To disable loosePath mapping, you can set `strictPath` to true to use the previous behavior: ```ts new Elysia({ strictPath: false }) ``` We hope that this will clear any questions regarding path matching and its expected behavior. ## onResponse This update introduces a new lifecycle hook called `onResponse`. This is a proposal proposed by [elysia#67](https://github.com/elysiajs/elysia/issues/67). Previously Elysia’s life-cycle worked as illustrated in this diagram. ![Elysia life-cycle diagram](/blog/elysia-06/lifecycle-05.webp) For any metric, data-collection or logging purpose, you can use `onAfterHandle` to run the function to collect metrics. However, this lifecycle isn’t executed when the handler runs into an error, whether it’s a routing error or a custom error provided. This is why we introduced `onResponse` to handle all cases of Response. You can use `onRequest`, and `onResponse` together to measure a metric of performance or any required logging. Quoted > However, the onAfterHandle function only fires on successful responses. For instance, if the route is not found, or the body is invalid, or an error is thrown, it is not fired. How can I listen to both successful and non-successful requests? This is why I suggested onResponse. > > Based on the drawing, I would suggest the following: ![Elysia life-cycle diagram with onResponse hook](/blog/elysia-06/lifecycle-06.webp) *** ### Notable Improvement: - Added an error field to the Elysia type system for adding custom error messages - Support Cloudflare worker with Dynamic Mode (and ENV) - AfterHandle now automatically maps the value - Using bun build to target Bun environment, improving the overall performance by 5-10% - Deduplicated inline lifecycle when using plugin registration - Support for setting `prefix` - Recursive path typing - Slightly improved type checking speed - Recursive schema collision causing infinite types ### Change: - Moved **registerSchemaPath** to @elysiajs/swagger - \[Internal] Add qi (queryIndex) to context ### Breaking Change: - \[Internal] Removed Elysia Symbol - \[Internal] Refactored `getSchemaValidator`, `getResponseSchemaValidator` to named parameters - \[Internal] Moved `registerSchemaPath` to `@elysiajs/swagger` ## Afterward We have just passed a one year milestone, and are really excited about how Elysia and Bun have improved over the year! Pushing the performance boundaries of JavaScript with Bun, and developer experience with Elysia, we are thrilled to have kept in touch with you and our community. Every update keeps making Elysia even more stable, and gradually provides a better developer experience without a drop in performance and features. We’re thrilled to see our community of open-source developers bringing Elysia to life with their projects like: - [Elysia Vite Plugin SSR](https://github.com/timnghg/elysia-vite-plugin-ssr) allowing us to use Vite Server Side Rendering using Elysia as the server. - [Elysia Connect](https://github.com/timnghg/elysia-connect) which made Connect’s plugin compatible with Elysia And many more developers that choose Elysia for their next big project. With our commitment, we also recently introduced [Mobius](https://github.com/saltyaom/mobius), an open-source TypeScript library to parse GraphQL to TypeScript types without relying on code generation by using TypeScript template literal types entirely to be the very first framework to achieve end-to-end type safety without relying on code generation. We are incredibly thankful for your overwhelming continuous support for Elysia, and we hope to see you pushing the boundaries together in the next release. > As this whole new world cheers my name > > I will never leave it to fate > > and when I see a chance, I will pave the way > > I calls checkmate > > This is the time to breakthrough > > So I will rewrite the story and finally change all the rule > > We are maverick > > We won’t give in, until we win this game > > Though I don’t know what tomorrow holds > > I’ll make a bet any play my cards to win this game > > Unlike the rest, I’ll do my best, and I won’t ever lose > > To give up this chance would be a deadly since, so let’s bet it all > > I put all my fate in used let **the game begin** --- Source: [blog/elysia-07/index.md](blog/elysia-07/index.md) # Elysia 0.7 - Stellar Stellar Named after our never giving up spirit, our beloved Virtual YouTuber, ~~Suicopath~~ Hoshimachi Suisei, and her brilliant voice: 「[Stellar Stellar](https://youtu.be/AAsRtnbDs-0)」from her first album:「Still Still Stellar」. Once forgotten, she really is a star that truly shines in the dark. **Stellar Stellar** brings many exciting new updates to help Elysia solidify the foundation, and handle complexity with ease, featuring: - Entirely rewritten types, up to 13x faster type inference. - “Trace” for declarative telemetry and better performance audit. - Reactive Cookie model and cookie validation to simplify cookie handling. - TypeBox 0.31 with custom decoder support. - Rewritten Web Socket for even better support. - Definitions remapping, and declarative affix for preventing name collision. - Text based status ## Rewritten Type A core feature of Elysia is developer experience. Type is one of the most important aspects of Elysia, as it allows us to do many amazing things like unified type, syncing your business logic, typing, documentation and frontend. We want you to have an outstanding experience with Elysia, focusing on your business logic part, and letting Elysia handle the rest, whether it’s type-inference with unified type or the Eden connector for syncing types with the backend. To achieve that, we put our effort into creating a unified type system to synchronize all of the types, but as the features grow, we found that our type inference might not be fast enough because of our lack of TypeScript experience a year ago. With the experience we gained along the way of handling a complex type system, various optimizations, and many projects like [Mobius](https://github.com/saltyaom/mobius), we challenge ourselves to speed up our type system once again, making this a second type rewrite for Elysia. We delete and rewrite every Elysia type from the ground up to make Elysia types an order of magnitude faster. Here’s a comparison between 0.6 and 0.7 on a simple `Elysia.get` example: ![Elysia 0.6](/blog/elysia-07/type-0-6.webp)![Elysia 0.7](/blog/elysia-07/type-0-7.webp) With our newfound experience, and newer TypeScript features like const generic, we are able to simplify a lot of our code, reducing our codebase by over a thousand lines of types. This allows us to refine our type system to be even faster, and even more stable. ![Comparison between Elysia 0.6 and 0.7 on a complex project with our 300 routes, and 3,500 lines of type declaration](/blog/elysia-07/inference-comparison.webp) Using Perfetto and TypeScript CLI to generate a trace on a large-scale and complex app, we measure up to 13x inference speed. And if you might wonder if we might break type inference with 0.6 or not, we do have a unit test at the type level to make sure that, in most cases, there’s no breaking change for types. We hope this improvement will help you with even faster type inference like faster auto-completion, and load time from your IDE to be near instant to help your development to be even faster and more fluent than ever before. ## Trace Performance is another one of the important aspects for Elysia. We don’t want to be fast for benchmarking purposes. We want you to have a really fast server in real-world scenarios, not just benchmarking. There are many factors that can slow down your app, and it’s hard to identify one. That’s why we introduce **“Trace”**. **Trace** allows us to tap into a life-cycle event and identify performance bottlenecks for our app. ![Example of usage of Trace](/blog/elysia-07/trace.webp) This example code allows us to tap into all **beforeHandle** events, and extract the execution time one-by-one before setting the Server-Timing API to inspect the performance bottleneck. And this is not limited to only `beforeHandle`. Any event can be traced, even the `handler` itself. The naming convention is named after life-cycle events you are already familiar with. This API allows us to effortlessly audit performance bottlenecks of your Elysia server and integrate with the report tools of your choice. By default, Trace uses AoT compilation and Dynamic Code injection to conditionally report an event that you actually use automatically, which means there’s no performance impact at all. ## Reactive Cookie We merged our cookie plugin into Elysia core. Same as Trace, Reactive Cookie uses AoT compilation and Dynamic Code injection to conditionally inject the cookie usage code, leading to no performance impact if you don’t use one. Reactive Cookie takes a more modern approach like signals to handle cookies with an ergonomic API. ![Example of usage of Reactive Cookie](/blog/elysia-07/cookie.webp) There’s no `getCookie` or `setCookie`. Everything is just a cookie object. When you want to use a cookie, you just extract the name and get/set its value like this: ```typescript app.get('/', ({ cookie: { name } }) => { // Get name.value // Set name.value = "New Value" }) ``` Then the cookie will automatically sync the value with headers and the cookie jar, making the `cookie` object a single source of truth for handling cookies. The Cookie Jar is reactive, which means that if you don’t set the new value for the cookie, the `Set-Cookie` header will not be sent to keep the same cookie value and reduce the performance bottleneck. ### Cookie Schema With the merge of cookies into the core of Elysia, we introduce a new **Cookie Schema** for validating cookie values. This is useful when you have to strictly validate a cookie session or want to have a strict type or type inference for handling cookies. ```typescript app.get('/', ({ cookie: { name } }) => { // Set name.value = { id: 617, name: 'Summoning 101' } }, { cookie: t.Cookie({ value: t.Object({ id: t.Numeric(), name: t.String() }) }) }) ``` Elysia encodes and decodes cookie values for you automatically, so if you want to store JSON in a cookie like decoded JWT value, or just want to make sure if the value is a numeric string, you can do that effortlessly. ### Cookie Signature And lastly, with the introduction of Cookie Schema and the `t.Cookie` type, we are able to create a unified type for signing/verifying cookie signatures automatically. Cookie signature is a cryptographic hash appended to a cookie’s value, generated using a secret key and the content of the cookie to enhance security by adding a signature to the cookie. This makes sure that the cookie value is not modified by a malicious actor, helping in verifying the authenticity and integrity of the cookie data. To handle cookie signature in Elysia, it’s as simple as providing a `secret` and `sign` property: ```typescript new Elysia({ cookie: { secret: 'Fischl von Luftschloss Narfidort' } }) .get('/', ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } }, { cookie: t.Cookie({ profile: t.Object({ id: t.Numeric(), name: t.String() }) }, { sign: ['profile'] }) }) ``` Provide a cookie secret and a `sign` property to indicate which cookie should have signature verification. Elysia then signs and unsigns cookie values automatically, eliminating the need to call **sign** / **unsign** functions manually. Elysia handles Cookie’s secret rotation automatically, so if you have to migrate to a new cookie secret, you can just append the secret, and Elysia will use the first value to sign a new cookie, while trying to unsign cookies with the rest of the secrets if they match. ```typescript new Elysia({ cookie: { secrets: ['Vengeance will be mine', 'Fischl von Luftschloss Narfidort'] } }) ``` The Reactive Cookie API is declarative and straightforward, and there’s something magical about the ergonomics it provides, and we are really looking forward to you trying it. ## TypeBox 0.31 With the release of 0.7, we are updating to TypeBox 0.31 to bring even more features to Elysia. This brings new exciting features like support for TypeBox’s `Decode` in Elysia natively. Previously, a custom type like `Numeric` required a dynamic code injection to convert numeric strings to numbers, but with the use of TypeBox’s decode, we are allowed to define a custom function to encode and decode the value of a type automatically. This allows us to simplify the type to: ```typescript Numeric: (property?: NumericOptions) => Type.Transform(Type.Union([Type.String(), Type.Number(property)])) .Decode((value) => { const number = +value if (isNaN(number)) return value return number }) .Encode((value) => value) as any as TNumber, ``` Instead of relying on an extensive check and code injection, it’s simplified by a `Decode` function in TypeBox. We have rewritten all types that require Dynamic Code Injection to use `Transform` for easier code maintenance. Not only limited to that, with `t.Transform` you can now also define a custom type with a custom function to Encode and Decode manually, allowing you to write more expressive code than ever before. We can’t wait to see what you will bring with the introduction of `t.Transform`. ### New Type With the introduction of **Transform**, we have added a new type like `t.ObjectString` to automatically decode an Object value in a request. This is useful when you have to use **multipart/formdata** for handling file uploading but it doesn’t support objects. You can now just use `t.ObjectString()` to tell Elysia that the field is a stringified JSON, so Elysia can decode it automatically. ```typescript new Elysia({ cookie: { secret: 'Fischl von Luftschloss Narfidort' } }) .post('/', ({ body: { data: { name } } }) => name, { body: t.Object({ image: t.File(), data: t.ObjectString({ name: t.String() }) }) }) ``` We hope that this will simplify the need for JSON with **multipart**. ## Rewritten Web Socket Aside from entirely rewriting types, we also entirely rewrote Web Socket as well. Previously, we found that Web Socket has 3 major problems: 1. Schema is not strictly validated 2. Slow type inference 3. The need for `.use(ws())` in every plugin With this new update, we solve all of the problems above while improving the performance of Web Socket. 1. Now, Elysia’s Web Socket is strictly validated, and type is synced automatically. 2. We remove the need for `.use(ws())` for using WebSocket in every plugin. And we bring a performance improvement to the already fast Web Socket. Previously, Elysia Web Socket needed to handle routing for every incoming request to unify the data and context. But with the new model, Web Socket can now infer the data for its route without relying on a router. This brings the performance close to Bun’s native Web Socket performance. Thanks to [Bogeychan](https://github.com/bogeychan) for providing the test case for Elysia Web Socket, helping us to rewrite Web Socket with confidence. ## Definitions Remap Proposed on [#83](https://github.com/elysiajs/elysia/issues/83) by [Bogeychan](https://github.com/bogeychan). To summarize, Elysia allows us to decorate a request and store with any value we desire. However, some plugins might have a duplicate name with the value we have, and sometimes a plugin has a name collision but we can’t rename the property at all. ### Remapping As the name suggests, this allows us to remap existing `state`, `decorate`, `model`, `derive` to anything we like to prevent name collision, or just rename a property. When you provide a function as the first parameter, the callback will accept the current value, allowing us to remap the value to anything we like. ```typescript new Elysia() .state({ a: "a", b: "b" }) // Exclude b state .state(({ b, ...rest }) => rest) ``` This is useful when you have to deal with a plugin that has some duplicate name, allowing you to remap the name of the plugin: ```typescript new Elysia() .use( plugin .decorate(({ logger, ...rest }) => ({ pluginLogger: logger, ...rest })) ) ``` The Remap function can be used with `state`, `decorate`, `model`, `derive` to help you define a correct property name and prevent name collision. ### Affix To provide a smoother experience, some plugins might have a lot of property values which can be overwhelming to remap one-by-one. The **Affix** function, which consists of a **prefix** and **suffix**, allows us to remap all properties of an instance, preventing the name collision of the plugin. ```typescript const setup = new Elysia({ name: 'setup' }) .decorate({ argon: 'a', boron: 'b', carbon: 'c' }) const app = new Elysia() .use( setup .prefix('decorator', 'setup') ) .get('/', ({ setupCarbon }) => setupCarbon) ``` This allows us to bulk remap a property of the plugin effortlessly, preventing the name collision of the plugin. By default, **affix** will handle both runtime and type-level code automatically, remapping the property to camelCase as a naming convention. In some conditions, you can also remap `all` property of the plugin: ```typescript const app = new Elysia() .use( setup .prefix('all', 'setup') ) .get('/', ({ setupCarbon }) => setupCarbon) ``` We hope that remapping and affix will provide a powerful API for you to handle multiple complex plugins with ease. ## True Encapsulation Scope With the introduction of Elysia 0.7, Elysia can now truly encapsulate an instance by treating a scoped instance as another instance. The new scope model can even prevent events like `onRequest` from being resolved on a main instance, which was not possible. ```typescript const plugin = new Elysia({ scoped: true, prefix: '/hello' }) .onRequest(() => { console.log('In Scoped') }) .get('/', () => 'hello') const app = new Elysia() .use(plugin) // 'In Scoped' will not log .get('/', () => 'Hello World') ``` Furthermore, scoped is now truly scoped down both at runtime and at the type level which is not possible without the type rewrite mentioned before. This is exciting from the maintainer’s side because previously, it was almost impossible to truly encapsulate the scope of an instance, but using `mount` and WinterCG compliance, we are finally able to truly encapsulate the instance of the plugin while providing a soft link with main instance properties like `state`, `decorate`. ## Text based status There are over 64 standard HTTP status codes to remember, and I admit that sometimes we also forget the status we want to use. This is why we ship 64 HTTP Status codes in text-based form with autocompletion for you. ![Example of using text-based status code](/blog/elysia-07/teapot.webp) Text will then resolve to a status code automatically as expected. As you type, there should be auto-completion for text popping up automatically for your IDE, whether it’s NeoVim or VSCode, as it’s a built-in TypeScript feature. ![Text-based status code showing auto-completion](/blog/elysia-07/teapot-autocompletion.webp) This is a small ergonomic feature to help you develop your server without switching between IDE and MDN to search for a correct status code. ## Notable Improvement Improvement: - `onRequest` can now be async - add `Context` to `onError` - lifecycle hooks now accept arrays of functions - static Code Analysis now supports rest parameters - break down dynamic router into a single pipeline instead of inlining to static router to reduce memory usage - set `t.File` and `t.Files` to `File` instead of `Blob` - skip class instance merging - handle `UnknownContextPassToFunction` - [#157](https://github.com/elysiajs/elysia/pull/179) WebSocket - added unit tests and fixed example & api by @bogeychan - [#179](https://github.com/elysiajs/elysia/pull/179) add github action to run bun test by @arthurfiorette Breaking Change: - remove `ws` plugin, migrate to core - rename `addError` to `error` Change: - using single findDynamicRoute instead of inlining to static map - remove `mergician` - remove array routes due to problem with TypeScript - rewrite Type.ElysiaMeta to use TypeBox.Transform Bug fix: - strictly validate response by default - `t.Numeric` not working on headers / query / params - `t.Optional(t.Object({ [name]: t.Numeric }))` causing error - add null check before converting `Numeric` - inherit store to instance plugin - handle class overlapping - [#187](https://github.com/elysiajs/elysia/pull/187) InternalServerError message fixed to “INTERNAL\_SERVER\_ERROR” instead of “NOT\_FOUND” by @bogeychan - [#167](https://github.com/elysiajs/elysia/pull/167) mapEarlyResponse with aot on after handle ## Afterward Since the latest release, we have gained over 2,000 stars on GitHub! Taking a look back, we have progressed more than we have ever imagined back then. Pushing the boundary of TypeScript, and developer experience even to the point that we are doing something we feel is truly profound. With every release, we are gradually one step closer to bringing the future we drew a long time ago. A future where we can freely create anything we want with an astonishing developer experience. We feel truly thankful to be loved by you and the lovely community of TypeScript and Bun. It’s exciting to see Elysia is brought to life by amazing developers like: - [Ethan Niser with his amazing BETH Stack](https://youtu.be/aDYYn9R-JyE?si=hgvGgbywu_-jsmhR) - Being mentioned by [Fireship](https://youtu.be/dWqNgzZwVJQ?si=AeCmcMsTZtNwmhm2) - Having official integration for [Lucia Auth](https://github.com/pilcrowOnPaper/lucia) And many more developers that choose Elysia for their next project. Our goal is simple, to bring an eternal paradise where you can pursue your dream and everyone can live happily. Thank you for your love and overwhelming support for Elysia. We hope we can make the future in which we pursue our dreams a reality one day. **May all the beauty be blessed** > Stretch out that hand as if to reach someone > > I’m just like you, nothing special > > That’s right, I’ll sing the song of the night > > Stellar Stellar > > In the middle of the world, the universe > > The music won’t ever, ever stop tonight > > That’s right, I’d always longed to be > > Not Cinderella, forever waiting > > But the prince that came to for her > > Cause I’m a star, that’s why > > Stellar Stellar --- Source: [blog/elysia-08/index.md](blog/elysia-08/index.md) # Elysia 0.8 - Gate of Steiner Named after the ending song of Steins;Gate Zero, [**“Gate of Steiner”**](https://youtu.be/S5fnglcM5VI). Gate of Steiner isn’t focused on new, exciting APIs and features but on API stability and a solid foundation to make sure the API will be stable once Elysia 1.0 is released. However, we do bring improvements and new features, including: - [Macro API](#macro-api) - [New Lifecycle: resolve, mapResponse](#new-life-cycle) - [Error Function](#error-function) - [Static Content](#static-content) - [Default Property](#default-property) - [Default Header](#default-header) - [Performance and Notable Improvement](#performance-and-notable-improvement) ## Macro API Macro allows us to define a custom field to hook and guard by exposing full control over the life cycle event stack. This allows us to compose custom logic into a simple configuration with full type safety. Suppose we have an authentication plugin to restrict access based on role; we can define a custom **role** field. ```typescript import { Elysia } from 'elysia' import { auth } from '@services/auth' const app = new Elysia() .use(auth) .get('/', ({ user }) => user.profile, { role: 'admin' }) ``` Macro has full access to the life cycle stack, allowing us to add, modify, or delete existing events directly for each route. ```typescript const plugin = new Elysia({ name: 'plugin' }).macro(({ beforeHandle }) => { return { role(type: 'admin' | 'user') { beforeHandle( { insert: 'before' }, async ({ cookie: { session } }) => { const user = await validateSession(session.value) await validateRole('admin', user) } ) } } }) ``` We hope that with this macro API, plugin maintainers will be able to customize Elysia to their heart’s content, opening a new way to interact better with Elysia, and Elysia users will be able to enjoy even more ergonomic APIs Elysia could provide. The documentation for [Macro API](/patterns/macro) is now available in the **pattern** section. The next generation of customizability is now just a reach away from your keyboard and imagination. ## New Life Cycle Elysia introduces a new life cycle to fix an existing problem and add highly requested APIs, including **Resolve** and **MapResponse**: resolve: a safe version of **derive**. Executes in the same queue as **beforeHandle** mapResponse: Executes just after **afterResponse** to provide a transform function from a primitive value to a Web Standard Response ### Resolve A “safe” version of [derive](/essential/life-cycle/#derive). Designed to append a new value to the context after validation, storing it in the same stack as **beforeHandle**. The syntax for Resolve is identical to [derive](/essential/life-cycle/#derive). Below is an example of retrieving a bearer header from the Authorization plugin. ```typescript import { Elysia } from 'elysia' new Elysia() .guard( { headers: t.Object({ authorization: t.TemplateLiteral('Bearer ${string}') }) }, (app) => app .resolve(({ headers: { authorization } }) => { return { bearer: authorization.split(' ')[1] } }) .get('/', ({ bearer }) => bearer) ) .listen(3000) ``` ### MapResponse Executed just after **afterHandle**; designed to provide custom response mapping from a primitive value into a Web Standard Response. Below is an example of using mapResponse to provide response compression. ```typescript import { Elysia, mapResponse } from 'elysia' import { gzipSync } from 'bun' new Elysia() .mapResponse(({ response }) => { return new Response( gzipSync( typeof response === 'object' ? JSON.stringify(response) : response.toString() ) ) }) .listen(3000) ``` Why not use **afterHandle** instead of introducing a new API? Because **afterHandle** is designed to read and modify a primitive value, while plugins like HTML and Compression depend on creating a Web Standard Response. This means that plugins registered after this type of plugin will be unable to read or modify the value, making the plugin behavior incorrect. This is why we introduce a new life cycle that runs after **afterHandle**, dedicated to providing a custom response mapping instead of mixing response mapping and primitive value mutation in the same queue. ## Error Function We can set the status code by using either **set.status** or returning a new Response. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ set }) => { set.status = 418 return "I'm a teapot" }) .listen(3000) ``` This aligns with our goal: to send the literal value to the client instead of worrying about how the server should behave. However, this has proven challenging to integrate with Eden. Since we return a literal value, we can’t infer the status code from the response, making Eden unable to differentiate the response from the status code. This prevents Eden from reaching its full potential, especially in error handling, as it cannot infer types without declaring an explicit response type for each status. We have also received many requests from our users for a more explicit way to return the status code directly with the value—without relying on **set.status** or **new Response**, which can be verbose or require returning a response from a utility function declared outside the handler function. This is why we introduce an **error** function to return a status code alongside the value back to the client. ```typescript import { Elysia, error } from 'elysia' new Elysia() .get('/', () => error(418, "I'm a teapot")) .listen(3000) ``` Which is equivalent to: ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ set }) => { set.status = 418 return "I'm a teapot" }) .listen(3000) ``` The difference is that when you use an **error** function, Elysia will automatically distinguish response types by status code, helping Eden to infer a response based on status correctly. This means that by using **error**, we don’t have to include an explicit response schema to make Eden infer types correctly for each status code. ```typescript import { Elysia, error, t } from 'elysia' new Elysia() .get('/', ({ set }) => { set.status = 418 return "I'm a teapot" }, { response: { 418: t.String() } }) .listen(3000) ``` We recommend using the `error` function to return a response with the status code for correct type inference; however, we do not intend to remove the use of **set.status** from Elysia to keep existing servers working. ## Static Content Static Content refers to a response that almost always returns the same value regardless of the incoming request. This type of resource on the server is usually something like a public **File**, **video**, or hardcoded value that is rarely changed unless the server is updated. Most content in Elysia is static. We also found that many cases, like serving a static file or serving an HTML page using a template engine, are usually static content. This is why Elysia introduces a new API to optimize static content by determining the response ahead of time. ```typescript new Elysia() .get('/', () => Bun.file('video/kyuukurarin.mp4')) .get('/', Bun.file('video/kyuukurarin.mp4')) .listen(3000) ``` Notice that the handler is no longer a function but an inline value instead. This improves performance by around 20–25% by compiling the response ahead of time. ## Default Property Elysia 0.8 updates [TypeBox to 0.32](https://github.com/sinclairzx81/typebox/blob/index/changelog/0.32.0.md), which introduces many new features including dedicated RegEx, Deref, but most importantly the most requested feature in Elysia: **default** field support. Now, by defining a default field in Type Builder, Elysia will provide a default value if one is not provided, supporting schema types from type to body. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ query: { name } }) => name, { query: t.Object({ name: t.String({ default: 'Elysia' }) }) }) .listen(3000) ``` This allows us to provide a default value directly from the schema, especially useful when using a reference schema. ## Default Header We can set headers using **set.headers**; Elysia creates a default empty object for every request. Previously, we could use **onRequest** to append desired values to set.headers, but this always adds some overhead because a function is called. Stacking functions to mutate an object can be a little slower than having the desired value set upfront if the value is always the same for every request, like CORS or cache headers. This is why we now support setting default headers out of the box instead of creating an empty object for every new request. ```typescript new Elysia() .headers({ 'X-Powered-By': 'Elysia' }) ``` Elysia’s CORS plugin also has an update to use this new API to improve performance. ## Performance and notable improvements As usual, we found ways to optimize Elysia even more to make sure you have the best performance out of the box. ### Removal of bind We found that **.bind** slows down path lookup by around 5%. Removing bind from our codebase speeds up that process a bit. ### Static Query Analysis Elysia’s Static Code Analysis can now infer a query if the query name is referenced in the code. This usually results in a 15–20% speed-up by default. ### Video Stream Elysia now adds the **content-range** header to File and Blob by default to fix problems with large files like videos that need to be sent in chunks. Elysia will not send the **content-range** header if the status code is 206, 304, 412, or 416, or if the headers explicitly provide the **content-range** header. It’s recommended to use the [ETag plugin](https://github.com/bogeychan/elysia-etag) to handle the correct status code to avoid **content-range** collisions from the cache. This is initial support for the **content-range** header. We have created a discussion to implement more accurate behavior based on [RFC 7233](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2) in the future. Feel free to join the discussion to propose new behavior for Elysia with **content-range** and **ETag generation** at [Discussion 371](https://github.com/elysiajs/elysia/discussions/371). ### Runtime Memory improvement Elysia now reuses the return value of life cycle events instead of declaring a new dedicated value. This reduces memory usage slightly, improving performance under peak concurrent requests. ### Plugins Most official plugins now take advantage of newer **Elysia.headers**, Static Content, **MapResponse**, and revised code that complies with static code analysis to improve overall performance. Plugins improved by this include Static, HTML, and CORS. ### Validation Error Elysia now returns validation errors as JSON instead of text. It shows current errors, all errors, and expected values to help you identify an error more easily. Example: ```json { "type": "query", "at": "password", "message": "Required property", "expected": { "email": "eden@elysiajs.com", "password": "" }, "found": { "email": "eden@elysiajs.com" }, "errors": [ { "type": 45, "schema": { "type": "string" }, "path": "/password", "message": "Required property" }, { "type": 54, "schema": { "type": "string" }, "path": "/password", "message": "Expected string" } ] } ``` The **expect** and **errors** fields are removed by default in the production environment to prevent an attacker from identifying a model for further attacks. ## Notable Improvements **Improvements** - lazy query reference - add `content-range` header to `Blob` by default - update TypeBox to 0.32 - override lifecycle response of `be` and `af` **Breaking Change** - `afterHandle` no longer triggers early return **Changes** - change validation response to JSON - differentiate derive from `decorator['request']` as `decorator['derive']` - `derive` now doesn’t show inferred type in `onRequest` **Bug fixes** - remove `headers`, `path` from `PreContext` - remove `derive` from `PreContext` - Elysia types don’t output custom `error` ## Afterword It has been a great journey since the first release. Elysia evolved from a generic REST API framework to an ergonomic framework that supports end-to-end type safety and OpenAPI documentation generation. We would like to continue introducing more exciting features in the future. We are happy to have you and the developers building amazing stuff with Elysia, and your overwhelming, continuous support for Elysia encourages us to keep going. It’s exciting to see Elysia grow more as a community: - [Scalar’s Elysia theme](https://x.com/saltyAom/status/1737468941696421908?s=20) for new documentation instead of Swagger UI - [pkgx](https://pkgx.dev/) supports Elysia out of the box - The community submitted Elysia to [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r22\&hw=ph\&test=composite), ranking 32 among all frameworks in composite score, surpassing Go’s Gin and Echo We are now trying to provide more support for each runtime, plugin, and integration to return the kindness you have given us, starting with rewriting the documentation to be more detailed and beginner-friendly, [Integration with Next.js](/integrations/nextjs/), [Astro](/integrations/astro), and more to come in the future. And since the release of 0.7, we have seen fewer issues compared to previous releases. Now **we are preparing for the first stable release of Elysia**—Elysia 1.0—aiming to release **in Q1 2024** to repay your kindness. Elysia will now enter a soft API lockdown mode to prevent API changes and make sure there will be no or minimal breaking changes once the stable release arrives. So you can expect your Elysia app to work starting from 0.7 with no or minimal changes to support the stable release of Elysia. We again thank you for your continuous support for Elysia, and we hope to see you again on the day of the stable release. *Keep fighting for all that is beautiful in this world*. Until then, *El Psy Congroo*. > A drop in the darkness 小さな命 > > Unique and precious forever > > Bittersweet memories 夢幻の刹那 > > Make this moment last, last forever > > We drift through the heavens 果てない想い > > Filled with the love from up above > > He guides my travels せまる刻限 > > Shed a tear and leap to a new world --- Source: [blog/elysia-10/index.md](blog/elysia-10/index.md) # Elysia 1.0 - Lament of the Fallen Elysia 1.0 is the first stable release after development for 1.8 years. Since we started, we have always been waiting for a framework that focuses on developer experience, velocity, and how to write code for humans, not a machine. We battle-test Elysia in various situations, simulate medium and large-scale projects, and ship code to clients, and this is the first version that we felt confident enough to ship. Elysia 1.0 introduces significant improvements and contains 1 necessary breaking change. - [Sucrose](#sucrose) - Rewritten pattern matching static analysis instead of RegEx - [Improved startup time](#improved-startup-time) up to 14x - [Remove \~40 routes/instance TypeScript limitation](#remove-40-routesinstance-limit) - [Faster type inference](#type-inference-improvement) up to \~3.8x - [Treaty 2](#treaty-2) - [Hook type](#hook-type-breaking-change) (breaking changes) - [Inline error](#inline-error) for strict error check *** It’s a tradition that Elysia’s release notes have a version named after a song or media. This important version is named after [“Lament of the Fallen”](https://youtu.be/v1sd5CzR504). It is an animated short from **“Honkai Impact 3rd”** from my favorite arc, and my favorite character, **“Raiden Mei”** featuring her theme song, [“Honkai World Diva”](https://youtu.be/s_ZLfaZMpe0). It’s a very good game, and you should check it out. ー SaltyAom Also known as Raiden Mei from Gun Girl Z, Honkai Impact 3rd, Honkai Star Rail. And her “variation”, Raiden Shogun from Genshin Impact, and possibly Acheron from Honkai Star Rail (since she’s likely a bad-end herrscher form mentioned in Star Rail 2.1). Tip Remember, ElysiaJS is an open source library maintained by volunteers, and isn’t associated with Mihoyo nor Hoyoverse. But we are huge fans of the Honkai series, alright? ## Sucrose Elysia is optimized to have excellent performance proven in various benchmarks. This is thanks in large part to Bun and our custom JIT static code analysis. If you are not aware, Elysia has some sort of “compiler” embedded that reads your code and produces an optimized way to handle functions. The process is fast and happens on the fly without a need for a build step. However, it’s challenging to maintain as it’s written mostly in many complex RegEx, and can be slow at times if recursion happens. That’s why we rewrote our static analysis part to separate the code injection phase using a hybrid approach between partial AST-based analysis and pattern-matching named **“Sucrose”**. Instead of using full AST-based analysis, which is more accurate, we choose to implement only a subset of rules that are needed to improve performance as it needs to be fast at runtime. Sucrose is good at inferring the recursive property of the handler function accurately with low memory usage, resulting in up to 37% faster inference time and significantly reduced memory usage. Sucrose is shipped to replace RegEx-based analysis with partial AST and pattern matching starting from Elysia 1.0. ## Improved Startup time Thanks to Sucrose, and separation from the dynamic injection phase, we can defer the analysis to JIT instead of AOT. In other words, the “compile” phase can be lazily evaluated. We offload the evaluation phase from AOT to JIT when a route is matched for the first time and cache the result to compile on demand instead of compiling all routes before the server starts. At runtime, a single compilation is usually fast and takes no longer than 0.01-0.03 ms (milliseconds, not seconds). In a medium-sized application and stress test, we measure up to between \~6.5-14x faster start-up time. ## Remove \~40 routes/instance limit Previously you could only stack up to \~40 routes / 1 Elysia instance since Elysia 0.1. This is a limitation of TypeScript. Each queue has limited memory and, if that limit is exceeded, TypeScript will think that **“Type instantiation is excessively deep and possibly infinite”**. ```typescript const main = new Elysia() .get('/1', () => '1') .get('/2', () => '2') .get('/3', () => '3') // repeat for 40 times .get('/42', () => '42') // Type instantiation is excessively deep and possibly infinite ``` As a workaround, we need to separate an instance into a controller to overcome the limit and remerge the type to offload the queue like this. ```typescript const controller1 = new Elysia() .get('/42', () => '42') .get('/43', () => '43') const main = new Elysia() .get('/1', () => '1') .get('/2', () => '2') // repeat for 40 times .use(controller1) ``` However, starting from Elysia 1.0, we have overcome the limit after a year of optimizing for type-performance, specifically Tail Call Optimization, and variances. This means theoretically, we can stack an unlimited number of routes and methods until TypeScript breaks. (spoiler: we have done that and it’s around 558 routes/instance before the TypeScript CLI and language server break because of the JavaScript memory limit per stack/queue) ```typescript const main = new Elysia() .get('/1', () => '1') .get('/2', () => '2') .get('/3', () => '42') // repeat for n times .get('/550', () => '550') ``` So we increase the limit of \~40 routes to JavaScript memory limit instead, so try not to stack more than \~558 routes/instance, and separate into a plugin if necessary. ![TypeScript breaks on 558 routes](/blog/elysia-10/558-ts-limit.webp) The blocker that made us feel like Elysia is not ready for production has been finally resolved. ## Type Inference improvement Thanks to the effort we put into optimization, we measure **up to \~82%** in most Elysia servers. Thanks to the removed limitation of stack, and improved type performance, we can expect almost instant type checking and auto-completion even after stacking 500 routes. [](/blog/elysia-10/type-demo.mp4) Type inference performance is **up to 13x faster for Eden Treaty** by precomputing the type instead of offloading type remapping to Eden. Overall, Elysia, and Eden Treaty performing together would be **up to \~3.9x faster**. Here’s a comparison between the Elysia + Eden Treaty on 0.8 and 1.0 for 450 routes. ![Type performance comparison between Elysia Eden 0.8 and 1.0, the graph shows that Elysia 0.8 took \~1500ms while Elysia 1.0 took \~400ms](/blog/elysia-10/ely-comparison.webp) A stress test with 450 routes for Elysia with Eden Treaty gives the following results: - Elysia 0.8 took \~1500ms - Elysia 1.0 took \~400ms And thanks to the removal of stack limitation, and remapping process, it’s now possible to stack up to over 1,000 routes for a single Eden Treaty instance. ## Treaty 2 We asked you for feedback on Eden Treaty, what you like and what could have been improved, and you have pointed out some flaws in Treaty’s design and given us several proposals for improvement. That’s why today, we introduce Eden Treaty 2, an overhaul to a more ergonomic design. As much as we dislike breaking changes, Treaty 2 is a successor to Treaty 1. **What’s new in Treaty 2**: - More ergonomic syntax - End-to-end type safety for Unit Test - Interceptor - No ”$” prefix and property Our favorite one is end-to-end type safety for Unit tests. So instead of starting a mock server and sending a fetch request, we can use Eden Treaty 2 to write unit tests with auto-completion and type safety instead. test/index.test.ts ```typescript import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' import { treaty } from '@elysiajs/eden' const app = new Elysia().get('/hello', () => 'hi') const api = treaty(app) describe('Elysia', () => { it('return a response', async () => { const { data } = await api.hello.get() expect(data).toBe('hi') }) }) ``` The difference between the two is that **Treaty 2 is a successor to Treaty 1.** We don’t intend to introduce any breaking change to Treaty 1 nor force you to update to Treaty 2. You can choose to continue using Treaty 1 for your current project without updating to Treaty 2, and we maintain it in a maintenance mode. - You can import `treaty` to use Treaty 2. - And import `edenTreaty` for Treaty 1. The documentation for the new Treaty can be found in [Treaty overview](/eden/treaty/overview/), and for Treaty 1 in [Treaty legacy](/eden/treaty/legacy/). ## Hook type (breaking change) We hate breaking changes, and this is the first time we do it on a large scale. We put a lot of effort into API design to reduce changes made to Elysia, but this is necessary to fix a flawed design. Previously when we added a hook with **“on”** like `onTransform`, or `onBeforeHandle`, it would become a global hook. This is great for creating something like a plugin but is not ideal for a local instance like a controller. ```typescript const plugin = new Elysia() .onBeforeHandle(() => { console.log('Hi') }) // log Hi .get('/hi', () => 'in plugin') const app = new Elysia() .use(plugin) // will also log hi .get('/no-hi-please', () => 'oh no') ``` However, we found several problems arise from this behavior. - We found that many developers have a lot of nested guards even on the new instance. Guard is almost used as a way to start a new instance to avoid side effects. - global by default may cause unpredictable (side-effect) behavior if not careful, especially in a team with inexperienced developers. - We asked many developers both familiar and not familiar with Elysia, and found that most expected hooks to be local at first. - Following the previous point, we found that making hooks global by default can easily cause accidental bugs (side-effects) if not reviewed carefully, which are hard to debug and observe. *** To fix this, we introduce a hook type to specify how the hook should be inherited by introducing a **“hook-type”**. Hook types can be classified as follows: - local (default) - apply only to the current instance and descendants - scoped - apply to only 1 ascendant, current instance, and descendants - global (old behavior) - apply to all instances that apply the plugin (all ascendants, current, and descendants) To specify the hook’s type, simply add a `{ as: hookType }` to the hook. ```typescript const plugin = new Elysia() .onBeforeHandle(() => { .onBeforeHandle({ as: 'global' }, () => { console.log('hi') }) .get('/child', () => 'log hi') const main = new Elysia() .use(plugin) .get('/parent', () => 'log hi') ``` This API is designed to fix the **guard nesting problem** for Elysia, where developers are afraid to introduce a hook on root instances because of fear of side effects. For example, to create an authentication check for an entire instance, we need to wrap a route in a guard. ```typescript const plugin = new Elysia() .guard((app) => app .onBeforeHandle(checkAuthSomehow) .get('/profile', () => 'log hi') ) ``` However, with the introduction of hook type, we can remove the nesting guard boilerplate. ```typescript const plugin = new Elysia() .guard((app) => app .onBeforeHandle(checkAuthSomehow) .get('/profile', () => 'log hi') ) ``` Hook type will specify how the hook should be inherited. Let’s create a plugin to illustrate how hook type works. ```typescript // ? Value based on table value provided below const type = 'local' const child = new Elysia() .get('/child', () => 'hello') const current = new Elysia() .onBeforeHandle({ as: type }, () => { console.log('hi') }) .use(child) .get('/current', () => 'hello') const parent = new Elysia() .use(current) .get('/parent', () => 'hello') const main = new Elysia() .use(parent) .get('/main', () => 'hello') ``` By changing the `type` value, the result should be as follows: | type | child | current | parent | main | | -------- | ----- | ------- | ------ | ---- | | ‘local’ | ✅ | ✅ | ❌ | ❌ | | ‘scope’ | ✅ | ✅ | ✅ | ❌ | | ‘global’ | ✅ | ✅ | ✅ | ✅ | Migrating from Elysia 0.8, if you want to make a hook global, you have to specify that hook is global. ```typescript // From Elysia 0.8 new Elysia() .onBeforeHandle(() => "A") .derive(() => {}) // Into Elysia 1.0 new Elysia() .onBeforeHandle({ as: 'global' }, () => "A") .derive({ as: 'global' }, () => {}) ``` As much as we hate breaking changes and migration, we think this is an important fix that will happen sooner or later to fix problems. Most servers might not need you to apply migration yourself, but this **heavily depends on plugin authors**. If migration is required, it usually takes no longer than 5-15 minutes. For a complete migration note, see [Elysia#513](https://github.com/elysiajs/elysia/issues/513). For the documentation of hook type, see [Lifecycle#hook-type](https://beta.elysiajs.com/essential/scope.html#hook-type). ## Inline error Starting from Elysia 0.8, we can use the `error` function to return a response with a status code for Eden inference. However, this has some flaws. If you specify a response schema for a route, Elysia will be unable to provide an accurate auto-completion for the status code. For example, narrowing down an available status code. ![Using import error in Elysia](/blog/elysia-10/error-fn.webp) Inline error can be destructured from the handler as follows: ```typescript import { Elysia } from 'elysia' new Elysia() .get('/hello', ({ error }) => { if(Math.random() > 0.5) return error(418, 'Nagisa') return 'Azusa' }, { response: t.Object({ 200: t.Literal('Azusa'), 418: t.Literal('Nagisa') }) }) ``` Inline error can produce a fine-grained type from a schema, providing type narrowing, auto-completion, and type checking to the accuracy of value, placing a red squiggly underline at a value instead of an entire function. ![Using inline error function from Elysia with an auto-completion that shows narrowed down status code](/blog/elysia-10/inline-error-fn.webp) We recommend using inline error instead of import error for more accurate type safety. ## What does it mean for v1, and what’s next Reaching a stable release means we believe that Elysia is stable enough and ready to be used in production. Maintaining backward compatibility is now one of our goals, putting effort into not introducing breaking changes to Elysia except for security. Our goal is to make backend development feel easy, fun, and intuitive while making sure that the product built with Elysia will have a solid foundation. After this, we will be focusing on refining our ecosystem and plugins. Introducing an ergonomic way to handle redundant and mundane tasks, starting some internal plugin rewrites, authentication, synchronizing behavior between JIT and non-JIT mode, and **universal runtime support.** Bun works excellently as a runtime, package manager, and in all the tooling it offers, and we believe that Bun is going to be a future of JavaScript. We believe that opening Elysia to more runtimes and offering interesting Bun specific features (or at least ones that are easy to configure, e.g., [Bun Loaders API](https://bun.sh/docs/bundler/loaders)) will eventually get people to try Bun more than Elysia choosing to support only Bun. > Bun was right, the best way to migrate people from Node is to have compatibility layer and offers better DX, and performance on Bun > > — SaltyAom (@saltyAom), [March 14, 2024](https://twitter.com/saltyAom/status/1768303850858143887?ref_src=twsrc%5Etfw) Elysia core itself is partially WinterCG compatible, but not all the official plugins work with WinterCG. There are some with Bun specific features, and we want to fix that. We don’t have a specific date or version for universal runtime support yet as we will gradually adopt and test until we make sure that it will work without unexpected behavior. You can look forward to support for the following runtimes: - Node - Deno - Cloudflare Worker We also want to support the following: - Vercel Edge Function - Netlify Function - AWS Lambda / LLRT Moreover, we also support, and test Elysia on the following frameworks that support Server Side Rendering or Edge Function: - Nextjs - Expo - Astro - SvelteKit In the meantime, there’s an [Elysia Polyfills](https://github.com/bogeychan/elysia-polyfills) maintained by Bogeychan, one of the active contributors to Elysia. Additionally, we have rewritten [Eden documentation](/eden) to explain more in depth details about Eden and we think you should check it out. We also improve several pages, and remove redundant parts of the documentation. You can check the affected pages on [Elysia 1.0 documentation PR](https://github.com/elysiajs/documentation/pull/282/files). And finally, if you have problems with migration and additional questions related to Elysia, feel free to ask in Elysia’s Discord server. [Elysia Discord](https://discord.com/widget?id=1044804142461362206\&theme=dark) ## Notable Improvement ### Improvement: - fine-grained reactive cookie - using single source of truth for cookie - macro support for websocket - add `mapResolve` - add `{ as: 'global' | 'scoped' | 'local' }` to lifecycle event - add ephemeral type - inline `error` to handler - inline `error` has auto-completion and type checking based on status code - handler now checks the return type of `error` based on status code - utility `Elysia._types` for type inference - [#495](https://github.com/elysiajs/elysia/issues/495) Provide user friendly error for failed parse - handler now infers return type for error status for Treaty - `t.Date` now allows stringified dates - improves type test case - add test case for all life-cycle - resolve, mapResolve, derive, mapDerive use ephemeral type to scope down accurately - infer query dynamic variables ### Breaking Change: - [#513](https://github.com/elysiajs/elysia/issues/513) lifecycle is now local first ### Change: - group private API property - move `Elysia.routes` to `Elysia.router.history` - detect possible json before return - unknown response now returns as-is instead of JSON.stringify() - change Elysia validation error to JSON instead of string ### Bug fix: - [#466](https://github.com/elysiajs/elysia/issues/466) Async Derive leaks request context to other requests if `aot: true` - [#505](https://github.com/elysiajs/elysia/issues/505) Empty ObjectString missing validation inside query schema - [#503](https://github.com/elysiajs/elysia/issues/503) Beta: undefined class when using decorate and derive - onStop callback called twice when calling .stop - mapDerive now resolves to `Singleton['derive']` instead of `Singleton['store']` - `ValidationError` doesn’t return `content-type` as `application/json` - validate `error(status, value)` per status - derive/resolve always scoped to Global - duplicated onError call if not handled - [#516](https://github.com/elysiajs/elysia/issues/516) server timing breaks beforeHandle guards - cookie.remove() doesn’t set correct cookie path ## Afterword Tip The following contains personal feelings, possibly venting, ranting, possibly cringe and unprofessionalism that shouldn’t be written in software release notes. You may choose to not continue reading as we have stated all the necessary content for the release. 2 years ago, I had a tragic experience. It’s easily one of the most painful memories I have, working days and nights to keep up with unfair tasks that took advantage of a loose contract we had with some software house. It took more than 6 months, and I had to work from when I woke up until I slept (15 hours) on repeat, **without doing anything else, not even a 5-minute break for a day**, no time to relax, nothing besides coding for almost 2 months, not even a single day off. I worked even on the days when I was knocked out and almost had to work in a hospital bed. I was soulless, with no purpose in life at all. My only wish was to make it a dream. At the time, there were so many breaking changes, uncountable new features introduced through loopholes in loose requirements and contracts. Keeping track of it was almost impossible, and we even got scammed, not even getting the pay we deserved because they were “not satisfied”, and we couldn’t do anything about it. It took me a month to recover from a fear of writing code. Being unprofessional, I couldn’t even do my job properly due to trauma and told my manager that I suffered burnout. That’s why we hate breaking changes so much, and want to design Elysia to handle changes easily with TypeScript soundness even if it’s not good but it’s all we have. I don’t want anyone to ever experience something like that. We designed a framework to encounter all the flaws that we had from that contract. The technical flaws I saw in there didn’t have any JavaScript based solution that could satisfy me yet, so I experimented with one. I could just move on as I could avoid loose contracts like this in the future, and make money and not spend most of my free time creating a framework but I didn’t. There’s my favorite part, [a quote in the animated short](https://youtu.be/v1sd5CzR504?t=128) where Mei is against Kiana’s idea that she would sacrifice herself for the world, and Mei replies: \> Yet you shoulder everything alone, at the cost of your life. \> Maybe this is for the greater good… \> But how can I pretend this is the right thing? \> I only know that deep down… \> the world means nothing to me… \> without you It’s a depiction of a duality between the person who would sacrifice themself for the world, and the person who would sacrifice themself to save who they love. If we see a problem and move on, how can we know that the person who comes after us will not stumble upon the same problem we had? Someone needs to do something. That someone would sacrifice themself to save the others but then who would save the sacrificed one? The name **“Lament of the Fallen”** describes that, and why we create Elysia. \*Despite everything about it being my favorite, and I might relate myself personally a bit too much. *** Although Elysia was built from a bad memory and a tragic event, it’s a privilege to see that Elysia grew into something with so much love. And to see that what you built is loved and well received by others. Elysia is the work of Open Source developers, and not backed by any company. We have to do something for a living, and build Elysia in our free time. At one point I chose not to look for a job straight away just to work on Elysia for several months. We would love to spend our time improving Elysia continuously, and you could help us with [GitHub sponsors](https://github.com/sponsors/SaltyAom) to reduce the work we need to support ourselves, and have more free time to work on Elysia. We are just makers that want to create something to solve problems we have. *** We have been creating and experimenting a lot with Elysia, shipping real code to clients, and using Elysia in real projects to power tools behind our local community, [CreatorsGarten](https://creatorsgarten.org) (local tech community, not an organization). It took a lot of time, preparation, and courage to make sure that Elysia is ready for production. Of course, there will be bugs, but we are willing to listen, and fix them. It’s the start of something new. And it’s possible **because of you**. ー SaltyAom > All the incandescent stars of heaven will die at the end of days, > > Your gentle soul given to damnation. > > “Crimson moon shines upon a town that is smeared in blood” > > Cried the diva given into lament. > > All those sweeet little dreams buried deep in memories until the very end. > > > > [**If rescuing you is a sin, I’ll gladly become a sinner.**](https://youtu.be/v1sd5CzR504?t=260) --- Source: [blog/elysia-11/index.md](blog/elysia-11/index.md) # Elysia 1.1 - Grown-up's Paradise Named after a song by Mili, [**“Grown-up’s Paradise”**](https://youtu.be/KawV_oK6lIc), and used as the opening for [commercial announcement of Arknights TV animation season 3](https://youtu.be/sZ1OD0cL6Qw). As a day one Arknights player and long time Mili fan, never once would I have thought Mili would do a song for Arknights. You should check them out as they are the goat. Elysia 1.1 focuses on several improvements to Developer Experience as follows: - [OpenTelemetry](#opentelemetry) - [Trace v2 (breaking change)](#trace-v2) - [Normalization](#normalization) - [Data coercion](#data-type-coercion) - [Guard as](#guard-as) - [Bulk `as` cast](#bulk-cast) - [Response status reconciliation](#response-reconcilation) - [Optional path parameter](#optional-path-parameter) - [Generator response stream](#generator-response-stream) ## OpenTelemetry Observability is one of the important aspects for production. It allows us to understand how our server works in production, identifying problems and bottlenecks. One of the most popular tools for observability is **OpenTelemetry**. However, we acknowledge that it’s hard and takes time to set up and instrument your server correctly. It’s hard to integrate OpenTelemetry into most existing frameworks and libraries. Most revolve around hacky solutions, monkey patching, prototype pollution, or manual instrumentation as the framework is not designed for observability from the start. That’s why we introduce **first party support** for OpenTelemetry on Elysia. To start using OpenTelemetry, install `@elysiajs/opentelemetry` and apply the plugin to any instance. ```typescript import { Elysia } from 'elysia' import { opentelemetry } from '@elysiajs/opentelemetry' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' new Elysia() .use( opentelemetry({ spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter() ) ] }) ) ``` ![jaeger showing collected traces automatically](/blog/elysia-11/jaeger.webp) Elysia OpenTelemetry will **collect spans of any library compatible with the OpenTelemetry standard**, and will apply parent and child spans automatically. In the code above, we apply `Prisma` to trace how long each query took. By applying OpenTelemetry, Elysia will then: - collect telemetry data - Group relevant lifecycle events together - Measure how long each function took - Instrument HTTP requests and responses - Collect errors and exceptions You can export telemetry data to Jaeger, Zipkin, New Relic, Axiom or any other OpenTelemetry compatible backend. Here’s an example of exporting telemetry to [Axiom](https://axiom.co). ```typescript import { Elysia } from 'elysia' import { opentelemetry } from '@elysiajs/opentelemetry' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' new Elysia() .use( opentelemetry({ spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter({ url: 'https://api.axiom.co/v1/traces', headers: { Authorization: `Bearer ${Bun.env.AXIOM_TOKEN}`, 'X-Axiom-Dataset': Bun.env.AXIOM_DATASET } }) ) ] }) ) ``` ![axiom showing collected traces from OpenTelemetry](/blog/elysia-11/axiom.webp) Elysia OpenTelemetry is for applying OpenTelemetry to an Elysia server only. You can use OpenTelemetry SDK normally, and if the span is run under Elysia’s request span, it will automatically appear in Elysia’s trace. However, we also provide a `getTracer`, and `record` utility to collect spans from any part of your application. ```typescript import { Elysia } from 'elysia' import { record } from '@elysiajs/opentelemetry' export const plugin = new Elysia() .get('', () => { return record('database.query', () => { return db.query('SELECT * FROM users') }) }) ``` `record` is an equivalent to OpenTelemetry’s `startActiveSpan` but it will handle auto-closing and capture exceptions automatically. You may think of `record` as a label for your code that will be shown in a trace. ### Prepare your codebase for observability Elysia OpenTelemetry will group lifecycle and read the **function name** of each hook as the name of the span. It’s a good time to **name your function**. If your hook handler is an arrow function, you may refactor it to a named function to understand the trace better. Otherwise, your trace span will be named as `anonymous`. ```typescript const bad = new Elysia() // ⚠️ span name will be anonymous .derive(async ({ cookie: { session } }) => { return { user: await getProfile(session) } }) const good = new Elysia() // ✅ span name will be getProfile .derive(async function getProfile({ cookie: { session } }) { return { user: await getProfile(session) } }) ``` ## Trace v2 Elysia OpenTelemetry is built on Trace v2, replacing Trace v1. Trace v2 allows us to trace any part of our server with 100% synchronous behavior, instead of relying on a parallel event listener bridge (goodbye deadlock). It’s entirely rewritten to not only be faster, but also reliable, and accurate down to the microsecond by relying on Elysia’s ahead of time compilation and code injection. Trace v2 uses a callback listener instead of Promise to ensure that the callback is finished before moving on to the next lifecycle event. Here’s an example usage of Trace v2: ```typescript import { Elysia } from 'elysia' new Elysia() .trace(({ onBeforeHandle, set }) => { // Listen to before handle event onBeforeHandle(({ onEvent }) => { // Listen to all child event in order onEvent(({ onStop, name }) => { // Execute something after a child event is finished onStop(({ elapsed }) => { console.log(name, 'took', elapsed, 'ms') // callback is executed synchronously before next event set.headers['x-trace'] = 'true' }) }) }) }) ``` You may also use `async` inside trace. Elysia will block an event before proceeding to the next event until the callback is finished. Trace v2 is a breaking change to Trace v1, please check out [trace api](/patterns/trace/) documentation for more information. ## Normalization Elysia 1.1 now normalizes data before it’s being processed. To ensure that data is consistent and safe, Elysia will try to coerce data into an exact data shape defined in schema, removing additional fields, and normalizing data into a consistent format. For example, if you have a schema like this: ```typescript import { Elysia, t } from 'elysia' import { treaty } from '@elysiajs/eden' const app = new Elysia() .post('/', ({ body }) => body, { body: t.Object({ name: t.String(), point: t.Number() }), response: t.Object({ name: t.String() }) }) const { data } = await treaty(app).index.post({ name: 'SaltyAom', point: 9001, // ⚠️ additional field title: 'maintainer' }) // 'point' is removed as defined in response console.log(data) // { name: 'SaltyAom' } ``` This code does 2 things: - Remove `title` from body before it’s being used on the server - Remove `point` from response before it’s being sent to the client This is useful to prevent data inconsistency, and ensure that data is always in the correct format, and does not leak any sensitive information. ## Data type coercion Previously Elysia used an exact data type without coercion unless explicitly specified to. For example, to parse a query parameter as a number, you need to explicitly cast it as `t.Numeric` instead of `t.Number`. ```typescript import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', ({ query }) => query, { query: t.Object({ page: t.Numeric() }) }) ``` However, in Elysia 1.1, we introduce data type coercion, which will automatically coerce data into the correct data type if possible. This allows us to simply set `t.Number` instead of `t.Numeric` to parse a query parameter as a number. ```typescript import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', ({ query }) => query, { query: t.Object({ // ✅ page will be coerced into a number automatically page: t.Number() }) }) ``` This also applies to `t.Boolean`, `t.Object`, and `t.Array`. This is done by swapping schemas with possible coercion counterparts during the compilation phase ahead of time, and has the same effect as using `t.Numeric` or other coercion counterparts. ## Guard as Previously, `guard` will only apply to the current instance only. ```typescript import { Elysia } from 'elysia' const plugin = new Elysia() .guard({ beforeHandle() { console.log('called') } }) .get('/plugin', () => 'ok') const main = new Elysia() .use(plugin) .get('/', () => 'ok') ``` Using this code, `onBeforeHandle` will only be called when accessing `/plugin` but not `/`. In Elysia 1.1, we add an `as` property to `guard` allowing us to apply guard as `scoped` or `global` the same as adding an event listener. ```typescript import { Elysia } from 'elysia' const plugin1 = new Elysia() .guard({ as: 'scoped', beforeHandle() { console.log('called') } }) .get('/plugin', () => 'ok') // Same as const plugin2 = new Elysia() .onBeforeHandle({ as: 'scoped' }, () => { console.log('called') }) .get('/plugin', () => 'ok') ``` This will ensure that `onBeforeHandle` will be called on the parent as well, and follow the scoping mechanism. Adding `as` to guard is useful, because it allows us to apply multiple hooks respecting the scoping mechanism all at once. However, it also allows us to apply `schema` to ensure type safety for all the routes at once. ```typescript import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ as: 'scoped', response: t.String() }) .get('/ok', () => 'ok') .get('/not-ok', () => 1) const instance = new Elysia() .use(plugin) .get('/no-ok-parent', () => 2) const parent = new Elysia() .use(instance) // This is fine because response is defined as scoped .get('/ok', () => 3) ``` ## Bulk cast Continuing from the code above, sometimes we want to reapply a plugin to the parent instance as well, but as it’s limited by the `scoped` mechanism, it’s limited to 1 parent only. To apply to the parent instance, we need to **lift the scope up** to the parent instance. We can achieve this by casting it as **as(‘plugin’)**. ```typescript import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ as: 'scoped', response: t.String() }) .get('/ok', () => 'ok') .get('/not-ok', () => 1) const instance = new Elysia() .use(plugin) .as('plugin') .get('/no-ok-parent', () => 2) const parent = new Elysia() .use(instance) // This now error because `scoped` is lifted up to parent .get('/ok', () => 3) ``` The `as` cast will lift all an instance’s scope up. It works by reading all hook and schema scopes and lifting them up to the parent instance. This means if you have a `local` scope, and want to apply it to the parent instance, you can use `as('plugin')` to lift it up. ```typescript import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ response: t.String() }) .onBeforeHandle(() => { console.log('called') }) .get('/ok', () => 'ok') .get('/not-ok', () => 1) .as('plugin') const instance = new Elysia() .use(plugin) .get('/no-ok-parent', () => 2) .as('plugin') const parent = new Elysia() .use(instance) // This now error because `scoped` is lifted up to parent .get('/ok', () => 3) ``` This will cast **guard’s response** and **onBeforeHandle** as `scoped` thus lifting it up to the parent instance. **as** accepts two possible arguments: - `plugin` casts events to **scoped** - `global` casts events to **global** ```typescript import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ response: t.String() }) .onBeforeHandle(() => { console.log('called') }) .get('/ok', () => 'ok') .get('/not-ok', () => 1) .as('global') const instance = new Elysia() .use(plugin) .get('/no-ok-parent', () => 2) const parent = new Elysia() .use(instance) // This now error because `scoped` is lifted up to parent .get('/ok', () => 3) ``` This allows us to cast multiple hook scopes all at once without adding `as` to each hook or applying it to guard, or lifting an existing plugin scope up. ```typescript import { Elysia, t } from 'elysia' // On 1.0 const from = new Elysia() // Not possible to apply guard to parent on 1.0 .guard({ response: t.String() }) .onBeforeHandle({ as: 'scoped' }, () => { console.log('called') }) .onAfterHandle({ as: 'scoped' }, () => { console.log('called') }) .onParse({ as: 'scoped' }, () => { console.log('called') }) // On 1.1 const to = new Elysia() .guard({ response: t.String() }) .onBeforeHandle(() => { console.log('called') }) .onAfterHandle(() => { console.log('called') }) .onParse(() => { console.log('called') }) .as('plugin') ``` ## Response reconciliation In Elysia 1.0, Elysia will prefer one of the schemas from the scope, and will not merge them together. However, in Elysia 1.1, Elysia will try to reconcile response schemas from all scopes from each status code and merge them together. ```typescript import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ as: 'global', response: { 200: t.Literal('ok'), 418: t.Literal('Teapot') } }) .get('/ok', ({ error }) => error(418, 'Teapot')) const instance = new Elysia() .use(plugin) .guard({ response: { 418: t.String() } }) // This is fine because local response override .get('/ok', ({ error }) => error(418, 'ok')) const parent = new Elysia() .use(instance) // Error because global response .get('/not-ok', ({ error }) => error(418, 'ok')) ``` We can see that: - on instance: the response schema from the global scope is merged with the local scope, allowing us to override the global response schema in this instance - on parent: the response schema from the global scope is used, the local scope from **instance** is not applied because of scoping mechanism This is handled both at the type level and at runtime, providing us with a better type-integrity. ## Optional Path Parameter Elysia now supports optional path parameters by adding `?` to the end of a path parameter. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/ok/:id?', ({ params: { id } }) => id) .get('/ok/:id/:name?', ({ params: { id, name } }) => name) ``` In the example above, if we access: `/ok/1` will return `1` `/ok` will return `undefined` By default, accessing the optional path parameter will return `undefined` if it’s not provided. You can provide a default value by either using JavaScript default value or schema default value. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/ok/:id?', ({ params: { id } }) => id, { params: t.Object({ id: t.Number({ default: 1 }) }) }) ``` In this example, if we access: `/ok/2` will return `1` `/ok` will return `1` ## Generator response stream Previously, you could stream a response by using the `@elysiajs/stream` package. However, there are limitations: - Doesn’t provide inference type safety for Eden - Not as straightforward a way to stream a response Now, Elysia supports response streaming out of the box by using a generator function. ```typescript import { Elysia } from 'elysia' const app = new Elysia() .get('/ok', function* () { yield 1 yield 2 yield 3 }) ``` In this example, we can stream a response by using the `yield` keyword. Using a generator function, we can now infer the return type from the generator function and provide it to Eden directly. Eden will now infer the response type from the generator function as `AsyncGenerator`. ```typescript import { Elysia } from 'elysia' import { treaty } from '@elysiajs/eden' const app = new Elysia() .get('/ok', function* () { yield 1 yield 2 yield 3 }) const { data, error } = await treaty(app).ok.get() if (error) throw error for await (const chunk of data) console.log(chunk) ``` While streaming a response, it’s common that a request may be cancelled before the response is fully streamed. In that case, Elysia will automatically stop the generator function when the request is cancelled. We recommend migrating from `@elysiajs/stream` to generator functions for streaming responses, as it’s more straightforward and provides better type inference. The stream plugin will be in maintenance mode and will be deprecated in the future. ## Breaking Change - Parse value as string for all validators unless explicitly specified. - See [50a5d92](https://github.com/elysiajs/elysia/commit/50a5d92ea3212c5f95f94552e4cb7d31b2c253ad), [44bf279](https://github.com/elysiajs/elysia/commit/44bf279c3752c6909533d19c83b24413d19d27fa). - Remove objects auto-parsing in query unless explicitly specified via query - Except query string as defined in RFC 3986, TLDR; query string could be either a string or an array of strings. - Rename `onResponse` to `onAfterResponse` - \[Internal] Remove $passthrough in favor of toResponse - \[Internal] UnwrapRoute type now always resolves with status code ### Notable Change: - Add auto-complete for `set.headers` - Remove prototype pollution from hook - remove static analysis for query name - remove query replace ’+’ in favor removing static query analysis - Add `server` property - mapResponse is now called in error event - reconcile decorators at the type level - `onError` supports arrays of functions - Parse query object with and without schema - Deprecated `ObjectString` for parsing array - Sucrose: improve isContextPassToFunction, and extractMainParameter stability - Add `replaceSchemaType` - Add `route` to `context` - Optimize recursive MacroToProperty type - Parse query array and object - Optimize code path for `composeGeneralHandler` - Add debug report on compiler panic - Using `Cookie` instead of `Cookie` if schema is not defined - Reduce memory usage of route registration \~36% on large codebase - Reduce compilation code path - Remove trace inference - Reduce router compilation code path - removing route handler compilation cache (`st${index}`, `stc${index}`) - Add undefined union to cookie in case a cookie is not present - Optimize response status resolve type inference ### Bug fix: - Normalize headers accidentally uses query validator check instead - `onError` missing trace symbol - Headers validator compilation is not cached - Deduplicate macro propagation - Websocket in nested groups now works - Error response is not checked unless successful status code is provided ## Afterword Hi, SaltyAom here again and thank you for supporting Elysia for the past 2 years. It has been a lovely journey, and seeing so much overwhelming support for Elysia makes me feel so happy that I don’t know how to express it. I’m still very happy to work on Elysia and looking forward to a long journey with you and Elysia. However, working alone on Elysia is not easy. That’s why I need your help to support Elysia by reporting a bug, creating a PR (we are opensource after all), sharing anything you like about Elysia, or even just saying hi. For the past 2 years, I’ve known that Elysia is not perfect, and sometimes I might not have all the time to respond to issues but I’m trying my best to make it better and have a vision of what it could be. That’s why in the future, we will have more maintainers to help maintain Elysia plugins. Currently Bogeychan and Fecony are doing great at helping maintain the community server. *** As you may or may not know, originally ElysiaJS was named “KingWorld” before changing its name to “Elysia”. Same as Elysia naming convention, both are inspired by anime/game/vtuber subculture. KingWorld is named after the song [KINGWORLD](https://youtu.be/yVaQpUUAzik?si=Dmto2PgA0uDxNi3D) by Shirakami Fubuki and Sasakure.uk, who are my favorite vtuber and music producer, respectively. That’s **why the logo is designed in the style of an Arctic fox** after Fubuki. Elysia is obviously named after [Elysia](https://honkai-impact-3rd-archives.fandom.com/wiki/Elysia), my favorite character from the game Honkai Impact 3rd, after whom I also named my cat. Also I have a little gift. As you may know, I’m also a cosplayer in my spare time, and I have a cosplay of Honkai 3rd Elysia as well. ![Elysia maintainer](/blog/elysia-11/ely.webp) So uh, Elysia maintaining Elysia I guess? I’m planning to do a photoshoot of Elysia cosplay and share it with you in the future. As I like her so much, I want to make it perfect. That being said, I’m looking forward to seeing you in the next release, and thank you for supporting Elysia. > We were so easily satisfied and happy > > Even if I break your favorite teddy bear > > A “sorry” could fix everything > > When did it change? When did we forget? > > Why is it now so hard to forgive? > > Do we advance, never stopping our steps > > Because we are scared to look back on what we did? > Truth is, I know as long as we live > > Our ideals dye rivers scarlet > > Answer me, my sinking ship > > Where’s our tomorrow? > > Where does our future go? > > Does our hope have to be sown upon somebody’s sorrow? **ขอให้โลกใจดีกับเธอบ้างนะ** --- Source: [blog/elysia-12/index.md](blog/elysia-12/index.md) # Elysia 1.2 - You and Me Named after the song [Φ²](https://youtu.be/b9IkzWO63Fg) from the album “At the Fingertip of the Sea” by HoyoMix as used in [**“You and Me”**](https://youtu.be/nz_Ra4G57A4). Elysia 1.2 focuses on commitment to expand universal runtime support and developer experience: - [Adapter](#adapter) - [Macro with resolve](#macro-with-resolve) - [Parser](#parser) - [WebSocket](#websocket) - [Typebox 0.34](#typebox) - [Reduced Memory usage](#reduced-memory-usage) ## Adapter One of the most requested features is to support more runtimes. Elysia 1.2 introduces **adapter** to allow Elysia to run on different runtimes. ```ts import { node } from '@elysiajs/node' new Elysia({ adapter: node() }) .get('/', 'Hello Node') .listen(3000) ``` Elysia is designed to be run on Bun and will continue to have Bun as the primary runtime and prioritize features on Bun first. However, we are giving you more choices to try Elysia on different environments that fit your needs, eg. serverless like AWS Lambda, Supabase Function, etc. The goal of Elysia’s adapter is to provide a consistent API across different runtimes while maintaining the best performance with the same code or with minimal changes for each runtime. ### Performance Performance is one of Elysia’s strengths. We do not compromise on performance. Instead of relying on a bridge to convert Web Standard requests to Node Request/Response, Elysia uses the native Node API directly to achieve the best performance while providing Web Standard compatibility if needed. Utilizing Sucrose static code analysis allows Elysia to be faster than most Web-Standard frameworks like Hono, h3, and even native Node frameworks like Fastify, Express. ![Node Benchmark](/blog/elysia-12/node-benchmark.webp) As usual, you may find the benchmark in [Bun HTTP framework benchmark](https://github.com/saltyaom/bun-http-framework-benchmark). Elysia now supports runtime adapters for: - Bun - Web Standard (WinterCG) eg. Deno, Browser - Node (beta) Although Node adapter is still in beta, it has most of the features you expect from returning Generator Stream to WebSocket. We recommend you try it out. We will continue to expand support for more runtimes in the future starting with: - Cloudflare Worker - AWS Lambda - uWebSocket.js ### Universal runtime API To be compatible with different runtimes, Elysia now wraps hand-picked utility functions to provide a consistent API across different runtimes. For example, in Bun you may use `Bun.file` to return a file response which is not available in Node. ```ts import { Elysia } from 'elysia' import { Elysia, file } from 'elysia' new Elysia() .get('/', () => Bun.file('./public/index.html')) .get('/', () => file('./public/index.html')) ``` These utility functions are replicas of Bun’s utility functions designed to be compatible with runtimes that Elysia supports, which will be expanded in the future. Currently, Elysia has support for: - `file` - Return a file response - `form` - Return a formdata response - `server` - A port of Bun’s `Server` type declaration ## Macro with resolve Starting with Elysia 1.2, you can now use `resolve` in macro. ```ts import { Elysia } from 'elysia' new Elysia() .macro({ user: (enabled: true) => ({ resolve: ({ cookie: { session } }) => ({ user: session.value! }) }) }) .get('/', ({ user }) => user, { user: true }) ``` With the new macro object syntax, instead of retrieving lifecycle you can now return it instead to reduce boilerplate. Here’s a comparison between the old and new syntax: ```ts // ✅ Object Macro new Elysia() .macro({ role: (role: 'admin' | 'user') => ({ beforeHandle: ({ cookie: { session } }) => ({ user: session.value! }) }) }) // ⚠️ Function Macro new Elysia() .macro(({ onBeforeHandle }) => { role(role: 'admin' | 'user') { onBeforeHandle(({ cookie: { session } }) => ({ user: session.value! }) } }) ``` Both syntaxes are supported but the new object syntax is recommended. We don’t have plans to remove the previous syntax but we will focus on the new object syntax with new features. Note Due to TypeScript limitation, macro’s `resolve` only works with the new object syntax but not with the previous one. ## Name Parser Elysia 1.2 introduces a parser with a custom name, allowing you to specify which parser should be used for decoding the request body. ```ts import { Elysia } from 'elysia' new Elysia() .parser('custom', ({ contentType }) => { if(contentType === "application/kivotos") return 'nagisa' }) .post('/', ({ body }) => body, { parse: 'custom' }) ``` `parser` has a similar API to `onParse` but with a custom name allowing you to reference it in a route. You may also reference Elysia’s built-in parser or provide multiple parsers to be used in order. ```ts import { Elysia } from 'elysia' new Elysia() .parser('custom', ({ contentType }) => { if(contentType === "application/kivotos") return 'nagisa' }) .post('/', ({ body }) => body, { parse: ['custom', 'json'] }) ``` Parsers will be called in order. If the parser does not return a value, it will move to the next parser until one of the parsers returns a value. ## WebSocket We have rewritten WebSocket to be more performant and match the API and behavior of Bun’s latest WebSocket API while maintaining compatibility with each runtime. ```ts new Elysia() .ws('/ws', { ping: (message) => message, pong: (message) => message }) ``` WebSocket now has a more consistent API with HTTP routes and has a lifecycle similar to HTTP routes. ## TypeBox 0.34 Elysia 1.2 now has support for TypeBox 0.34. With this update, Elysia now uses TypeBox’s `t.Module` to handle reference models to support circular recursive types. ```ts import { Elysia, t } from 'elysia' new Elysia() .model({ a: t.Object({ a: t.Optional(t.Ref('a')) }) }) .post('/recursive', ({ body }) => body, { body: 'a' }) ``` ## Reduced Memory usage We have refactored Sucrose’s generated code for a swappable code generation process. This refactoring reduces code duplication, optimizes the router, and removes unnecessary code. This allows Elysia to reuse code from several parts and reduce a large portion of memory usage. For our project, by simply upgrading to Elysia 1.2, we have seen up to 2x memory reduction from 1.1. ![Memory comparison between 1.1 and 1.2](/blog/elysia-12/memory.webp) This memory optimization is scaled with the number of routes and the complexity of the route. So you may see reduced memory usage exponentially. ## Notable update Below are some of the notable improvements that are made in Elysia 1.2. ### Eden validation error Eden also now automatically infers `422` status code if a validation model is provided. ```ts import { treaty } from '@elysiajs/eden' import type { App } from './app' const api = treaty('localhost:3000') const { data, error } = await api.user.put({ name: 'saltyaom' }) if(error) switch(error.status) { case 422: console.log(error.summary) break default: console.error(error) } ``` ### Router We have updated route registration deduplication to be more optimized. Previously Elysia would check all of the possible routes to prevent route duplication. Now Elysia uses a checksum hash map to check if the route is already registered and merges the routing and code generation process loops for static route registration to improve performance. ### Changes - Event Listener now infers path parameter automatically based on scope - Add ‘scoped’ to bulk `as` for casting type to ‘scoped’ similar to ‘plugin’ - Update `cookie` to 1.0.1 - Update TypeBox to 0.33 - `content-length` now accepts numbers - use 16-digit hexadecimals for `id` in `trace` - disable `minify` in production build for better debugging/error reporting ## Breaking Change There should be minor required changes to your codebase to adapt to upgrade to Elysia 1.2. However, these are all the changes that you need to be aware of. ### parse `type` is now merged with `parse` to allow control over the order of custom and built-in parsers. ```ts import { Elysia, form, file } from 'elysia' new Elysia() .post('/', ({ body }) => body, { type: 'json' parse: 'json' }) ``` ### formdata Starting from 1.2, you now have to explicitly return `form` if the response is a formdata instead of automatically detecting if a file is present in a 1-level deep object. ```ts import { Elysia, form, file } from 'elysia' new Elysia() .post('/', ({ file }) => ({ .post('/', ({ file }) => form({ a: file('./public/kyuukurarin.mp4') })) ``` ### WebSocket WebSocket methods now return their respective values instead of returning `WebSocket`. This removes the ability to do method chaining. This is to make WebSocket match the same API with Bun’s WebSocket API for better compatibility and migration. ```ts import { Elysia } from 'elysia' new Elysia() .ws('/', { message(ws) { ws .send('hello') .send('world') ws.send('hello') ws.send('world') } }) ``` ### scoped Elysia has now removed the `constructor scoped` as it might be confused with `scope's scoped/global`. ```ts import { Elysia } from 'elysia' new Elysia({ scoped: false }) const scoped = new Elysia() const main = new Elysia() .mount(scoped) ``` ### Internal breaking changes - Remove router internal property static.http.staticHandlers - Router history compile now links with history composed ## Afterword Elysia 1.2 is an ambitious update that we have been working on for a while. A gambit to expand Elysia’s reach to more developers and more runtimes, but there is something else I want to say as well. ### Let’s start this again. Hi, how are you? I hope you are doing well. I still love doing this, writing blog posts about Elysia. It has been a while since then. You may have noticed that it has been a while since the last update, and it’s not a long one. I’m sorry for that. I hope you understand that we also have our own lives to take care of. We are not robots, we are human. Sometimes it’s about life, sometimes about work, sometimes about family, sometimes about finances. ### I want to be with you all the time. I’m doing what I love. I keep updating Elysia, keep writing blog posts, keep making art, but you know that I have something to take care of too. I have to bring food to the table. I have to take care of a lot of things financially. I have to take care of myself too. I hope you are doing well, I hope you are happy, I hope you are healthy, I hope you are safe. Even if I’m not here, a part of me known as Elysia will be with you. Thanks for being with me. > Here I feel the touch brought by the real number solution. > > Ripples of two souls now have reached our double slits. > > Casting stripes of light and dark in the world as days and nights. > > You set me free under the sun. > > I fly your cradle dreams to the moon and back. > > A worm will turn into a butterfly, > > before one can answer “who am I”. > > After the ice turns into water, > > the sea I hang upside down will be your sky. > And we finally met again, Seele. --- Source: [blog/elysia-13/index.md](blog/elysia-13/index.md) # Elysia 1.3 and Scientific Witchery Named after the song [Ga1ahad and Scientific Witchery](https://youtu.be/d-nxW9qBtxQ) by Mili. This release doesn’t come with shiny new features. It’s about refinement to make things better to the point that we consider it as **“magic”**. Elysia 1.3 brings near 0 overhead, refinement, technical debt fixes, and internal code refactoring, featuring: - [Exact Mirror](#exact-mirror) - [Bun System Router](#bun-system-router) - [Standalone Validator](#standalone-validator) - [Reduced Type Instantiation by half](#reduced-type-instantiation) - [Performance Improvement](#performance-improvement) - [Validation DX Improvement](#validation-dx-improvement) - [Rename error to status](#rename-error-to-status) ## Exact Mirror We introduced [normalize](/patterns/configuration/#normalize) in Elysia 1.1 to ensure that data matches our desired shape, and it works nicely. It helps reduce potential data leaks and unexpected properties, and our users love it. However, it comes with a performance cost. Under the hood, it uses `TypeBox's Value.Clean` to coerce data into specified schema **dynamically**. It works great but not as fast as we want it to be. TypeBox doesn’t offer a **compiled** version of `Value.Clean`, unlike `TypeCompiler.Check`, which takes advantage of knowing the shape ahead-of-time. That’s why we introduced a replacement with [Exact Mirror](https://github.com/elysiajs/exact-mirror). **Exact Mirror** is a drop-in replacement for TypeBox’s **Value.Clean** with significant performance improvements by leveraging ahead-of-time compilation. ### Performance For small objects without arrays, we measured **up to \~500x faster** for the same object. ![Exact Mirror run on small data resulting in 582.52x faster than TypeBox Value.Clean](/blog/elysia-13/exact-mirror-small.webp) > Exact Mirror run on small data And for medium and large-size objects, we measured **up to \~30x faster**. ![Exact Mirror run on medium and large data resulting in 29.46x and 31.6x in order](/blog/elysia-13/exact-mirror-large.webp) > Exact Mirror run on medium and large data ### What it means for Elysia Starting from Elysia 1.3, Exact Mirror is a default strategy for normalization replacing TypeBox. By upgrading to Elysia 1.3, you can expect a significant performance improvement **without any code changes**. Here’s the throughput on Elysia 1.2. ![Elysia with normalization turned off resulting in 49k req/sec](/blog/elysia-13/normalize-1.2.webp) > Elysia with normalization turned off And here’s the same code on Elysia 1.3. ![Elysia with normalization turned on resulting in 77k req/sec](/blog/elysia-13/normalize-1.3.webp) > Elysia with normalization turned on We measured up to \~1.5x throughput when using **a single** schema with normalization. This means if you use more than a single schema, you should see even more performance improvement. When comparing to the same code **without schema**, we see < 2% performance differences. ![Elysia runs with no validation results in 79k req/sec](/blog/elysia-13/no-validation.webp) > Elysia runs with no validation This is huge. Previously, you had to choose between safety and performance as we close the performance gap between using and not using validation. But now you don’t have to worry about it. But now, we drop validation overhead from a significant amount to almost near zero without requiring any changes on your side. It just works, like magic. However, if you would like to use TypeBox or disable normalization entirely, you can set it with the constructor like any other configuration: ```ts import { Elysia } from 'elysia' new Elysia({ normalize: 'typebox' // Using TypeBox }) ``` You can try the benchmark out yourself by visiting [Exact Mirror on GitHub](https://github.com/elysiajs/exact-mirror). ## System Router We have never had performance problems with the router in Elysia. It has excellent performance, and we have hyper-optimized it as much as we possibly can. We pushed it to the near limit of what JavaScript can offer in a practical sense. ### Bun Router However, Bun 1.2.3 offers a built-in solution to routing (possibly) in native code. Although for static routes we didn’t see much performance improvement, we found that **dynamic routes perform 2-5% faster** without any code changes. Starting from Elysia 1.3, we offer a dual router strategy by using both Bun’s native router and Elysia’s router. Elysia will try to use a Bun router if possible and fall back to Elysia’s router. ### Adapter To make this possible, we have to rewrite our internal compilation code to support a custom router from **adapter**. This means that it’s now possible to use a custom router alongside Elysia’s own router. This opens up an opportunity for performance improvement in some environments, for example: using built-in `uWebSocket.js router` which has a native implementation for routing. ## Standalone Validator In Elysia, we can define a schema and apply it to multiple routes with `guard`. We can then override a public schema by providing a schema in a route handler which sometimes looks like this: ![Elysia running with default override guard showing a schema getting overridden](/blog/elysia-13/schema-override.webp) > Elysia running with default override guard But sometimes we **don’t want to override** a schema. Instead we want both to work, allowing us to combine schemas instead of overriding them. Starting from Elysia 1.3, we can do just that. We can now tell Elysia not to override it and instead treat it as its own by providing a schema as **standalone**. ```ts import { Elysia } from 'elysia' new Elysia() .guard({ schema: 'standalone', response: t.Object({ title: t.String() }) }) ``` As a result, we have results that are like merging a local and global schema together. ![Elysia running with standalone merging multiple guards together](/blog/elysia-13/schema-standalone.webp) > Elysia running with standalone merging multiple guards together ## Reduced Type Instantiation Elysia’s type inference is already extremely fast. We are really confident in our optimization of type inference and it’s faster than most frameworks that use an express-like syntax. However, some of our users have really **really** large scale applications with multiple routes and complex type inference. We managed to **reduce type instantiation by half** in most cases, and measured up to 60% improvement in inference speed. ![type instantiation reduced from 109k to 52k](/blog/elysia-13/type-instantiation.webp) > type instantiation reduced from 109k to 52k We also changed the default behavior of `decorate` to use intersections instead of looping through every object and property recursively. This should solve the problem with users who use heavy objects/classes, for example, `PrismaClient`. As a result, we should end up with faster IDE auto-completion, suggestion, type checking and Eden Treaty. ## Performance Improvement We have refactored and optimized a lot of internal code which accumulates up to significant improvements. ### Route Registration We have refactored how we store route information and reuse an object reference instead of cloning/creating a new one. We saw the following improvements: - Up to \~5.6x reduced memory usage - Up to \~2.7x faster route registration time ![Route registration comparison between Elysia 1.2 (left), and 1.3 (right)](/blog/elysia-13/routes.webp) > Route registration comparison between Elysia 1.2 (left), and 1.3 (right) These optimizations should show real results for medium to large scale apps as they scale with how many routes the server has. ### Sucrose We have implemented Sucrose cache to reduce unnecessary re-computation and reused compiled routes when compiling each route for non-inline events. ![Sucrose performance comparison between Elysia 1.2 (left), and 1.3 (right)](/blog/elysia-13/sucrose.webp) > Sucrose performance comparison between Elysia 1.2 (left), and 1.3 (right) Sucrose converts each event into a checksum number and stores it as a cache. It uses little memory and will be cleaned up once the server has started. This improvement should help with the startup time of each route that reuses global/scoped events. ### Instance We saw a significant improvement when creating multiple instances and applying them as plugins. - Up to \~10x reduced memory usage - Up to \~3x faster plugin creation ![Elysia instance comparison between Elysia 1.2 (left), and 1.3 (right)](/blog/elysia-13/instance.webp) > Elysia instance comparison between Elysia 1.2 (left), and 1.3 (right) These optimizations will be applied automatically by upgrading to Elysia 1.3. However, these performance optimizations might not be significantly noticeable for small apps. Serving a simple Bun server has a fixed cost of around 10-15MB. These optimizations focus more on reducing existing overhead and help improve startup time. ### Faster performance in general Through various micro-optimizations, fixing technical debt, and eliminating unused compiled instructions, we saw some general improvements in Elysia request processing speed. In some cases up to 40%. ![Elysia.handle comparison between Elysia 1.2 and 1.3](/blog/elysia-13/handle.webp) > Elysia.handle comparison between Elysia 1.2 and 1.3 ## Validation DX Improvement We want Elysia validation to **just work**. You can just tell it what you want, then you get it. It’s one of the most valuable aspects of Elysia. In this update, we have improved some areas that we have been lacking. ### Encode schema We have moved [encodeSchema](/patterns/configuration/) out of `experimental`, and enabled it by default. This allows us to use [t.Transform](https://github.com/sinclairzx81/typebox?tab=readme-ov-file#types-transform) to apply custom response mapping to return to the end user. ![Using t.Transform to intercept a value into a new one](/blog/elysia-13/encode-schema.webp) > Using t.Transform to intercept a value into a new one This example code will intercept a response, replacing “hi” with “intercepted” instead. ### Sanitize To prevent SQL injection and XSS, and to ensure string input/output is safe, we introduced the [sanitize](/patterns/configuration/#sanitize) option. It accepts a function or an array of functions that intercepts every `t.String`, and transforms it into a new value. ![Using sanitize with Bun.escapeHTML](/blog/elysia-13/sanitize.webp) > Using sanitize with Bun.escapeHTML In this example, we are using **Bun.escapeHTML** and replacing every “dorothy” with “doro” instead. As `sanitize` will apply to every schema globally, it must be applied on a root instance. This should greatly reduce the boilerplate to safely validate and transform each string field manually. ### Form In previous versions of Elysia, it was not possible to type-check a FormData response with [form](/essential/handler/#form-data) and `t.Object` at compile time. We have now introduced a new [t.Form](/patterns/typebox/#form) type to fix that. ![Using t.Form to validate FormData](/blog/elysia-13/form.webp) > Using t.Form to validate FormData To migrate to type-check form, simply replace `t.Object` with `t.Form` in the response schema. ### File Type Elysia now uses [file-type](https://github.com/sindresorhus/file-type) to validate file type. ![Defining file type using t.File](/blog/elysia-13/file-type.webp) > Defining file type using t.File Once `type` is specified, Elysia will automatically detect file type by checking the magic number. However, it’s also listed as **peerDependencies** and not installed with Elysia by default to reduce bundle size for users who don’t need it. It’s recommended to update to Elysia 1.3 if you rely on file type validation for better security. ### Elysia.Ref We can create a reference model by using `Elysia.model` and reference it by name. However, sometimes we need to reference it inside a schema. We can do just that by using `Elysia.Ref` to reference the model with auto-completion. ![Using Elysia.Ref to reference model](/blog/elysia-13/elysia-ref.webp) > Using Elysia.Ref to reference model You can also use `t.Ref` to reference a model, but it wouldn’t provide auto-completion. ### NoValidate We received some feedback that some users want to quickly prototype their API or sometimes have problems trying to enforce validation. In Elysia 1.3, we introduced `t.NoValidate` to skip validation. ![Using t.NoValidate to tell Elysia to skip validation](/blog/elysia-13/no-validate.webp) > Using t.NoValidate to tell Elysia to skip validation This will tell Elysia to skip runtime validation, but still provide TypeScript type checking and OpenAPI schema for API documentation. ## Status We have received a lot of responses about the naming of `error`. Starting with Elysia 1.3, we decided to deprecate `error`, and recommend the use of `status` instead. ![IDE showing that error is deprecated and renamed to status](/blog/elysia-13/status.webp) > IDE showing that error is deprecated and renamed to status The `error` function will work as it is in the previous version, with no immediate changes required. However, we recommend refactoring to `status` instead as we will be supporting `error` function for at least the next 6 months or until around Elysia 1.4 or 1.5. To migrate, simply rename `error` to `status`. ## “.index” is removed from Treaty Previously, you had to add `(treaty).index` to handle paths that end with **/**. Starting with Elysia 1.3, we decided to drop the use of `.index` and can simply bypass it to call the method directly. ![Eden Treaty showing no-use of .index](/blog/elysia-13/treaty-index.webp) > Eden Treaty showing no-use of .index This is a **breaking change** but should require minimal effort to migrate. To migrate, simply remove `.index` from your codebase. This should be a simple change by using IDE search to bulk change-and-replace by matching `.index` to remove it. ## Notable changes Here are some notable changes from the changelog. ### Improvement - `encodeSchema` now stable and enabled by default - optimize types - reduce redundant type check when using Encode - optimize isAsync - unwrap Definition\[‘typebox’] by default to prevent unnecessary UnwrapTypeModule call - Elysia.form can now be type-checked - refactor type-system - refactor `_types` into `~Types` - using aot compilation to check for custom Elysia type, eg. Numeric - refactor `app.router.static`, and move static router code generation to compile phase - optimize memory usage on `add`, `_use`, and some utility functions - improve startup time on multiple routes - dynamically create cookie validator as needed in compilation process - reduce object cloning - optimize start index for finding delimiter of a content type header - Promise can now be a static response - `ParseError` now keeps stack trace - refactor `parseQuery` and `parseQueryFromURL` - add `config` options to `mount` - recompile automatically after async modules are mounted - support macro on when hook has function - support resolve macro on ws - [#1146](https://github.com/elysiajs/elysia/pull/1146) add support to return web API’s File from handler - [#1165](https://github.com/elysiajs/elysia/pull/1165) skip non-numeric status codes in response schema validation - [#1177](https://github.com/elysiajs/elysia/issues/1177) cookie does not sign when an error is thrown ### Bug fix - `Response` returned from `onError` is using octet stream - unintentional memory allocation when using `mergeObjectArray` - handle empty space on Date query ### Change - only provide `c.request` to mapResponse when `maybeStream` is true - use plain object for `routeTree` instead of `Map` - remove `compressHistoryHook` and `decompressHistoryHook` - webstandard handler now returns `text/plain` if not on Bun - use non const value for `decorate` unless explicitly specified - `Elysia.mount` now sets `detail.hide = true` by default ### Breaking Change - remove `as('plugin')` in favor of `as('scoped')` - remove root `index` for Eden Treaty - remove `websocket` from `ElysiaAdapter` - remove `inference.request` ## Afterword Hi? It’s been a while. Life can be confusing, can’t it? One day you’re chasing your dream, working hard toward it. Before you know it, you look back and realize that you are far ahead of your goal. Someone looks up to you, and you become their inspiration. A role model for someone. It sounds amazing, right? But I don’t think I would be a good role model for others. ### I want to live an honest life Sometimes, things just get exaggerated. I may appear to be a genius who can create anything but I’m not. I just try my best. I hang out playing video games with friends, listening to weird songs, and watching movies. I even meet my friends at cosplay conventions. Just like a normal person. All this time, I’ve just been hugging tightly to *your* arm. **I’m just like you, nothing special.** I try my best but I also act like a fool from time to time. Even if I don’t think I have anything that makes me a role model, I want you to let me say that I’m grateful. My boring and slightly lonely life, please don’t beautify it too much. *\~ I’m glad you’re evil too.* --- Source: [blog/elysia-14/index.md](blog/elysia-14/index.md) # Elysia 1.4 - Supersymmetry Named after the song [Supersymmetry](https://youtu.be/NYyjQjtbteA) by Sta, a Tone Sphere ending theme. The highlight of Elysia 1.4 is the introduction of Standard Schema and **“Type Soundness”**. - [Standard Schema](#standard-schema) - [Macro](#macro) - [Lifecycle Type Soundness](#lifecycle-type-soundness) - [Group standalone schema](#group-standalone-schema) ## Standard Schema For 3 years, Elysia used TypeBox as the only validator. It became one of Elysia’s most loved features due to its performance and type inference. However, one of the most requested features from our community since the very beginning ([elysia#20](https://github.com/elysiajs/elysia/issues/20)) was to support validators other than TypeBox. Because Elysia was deeply tied to TypeBox, adding support for each validator individually required a lot of effort and ongoing maintenance to keep up with changes. Fortunately, a new proposal called [Standard Schema](https://github.com/standard-schema/standard-schema) defines a standard way to use different schemas with the same API. This allowed us to support multiple validators without writing custom integrations for each one. Elysia now supports Standard Schema, allowing you to use your favorite validators like: - Zod - Valibot - Effect Schema - ArkType - Joi - and more! You can provide the schema in a way similar to TypeBox, and it will just work out of the box: ```ts import { Elysia, t } from 'elysia' import { z } from 'zod' import * as v from 'valibot' const app = new Elysia() .post( '/user/:id', ({ body, params }) => { body params }, { params: z.object({ id: z.coerce.number() }), body: v.object({ name: v.literal('lilith') }) } ) ``` You can use multiple validators in a single route, and they will work together seamlessly with correct type inference. ### OpenAPI There is a request to support JSON Schema for OpenAPI generation with Standard Schema, but it is not implemented yet. However, we provide a custom `mapJsonSchema` to `openapi` to allow you to provide a custom function to map a schema to JSON Schema as a workaround. This allows you to have beautiful OpenAPI documentation with your favorite validator. ![Zod with OpenAPI support](/blog/elysia-14/openapi-zod.webp) > Using Zod’s native OpenAPI schema support with **describe** to add a description to the schema But if your validator does not support JSON Schema, we provide [OpenAPI type gen](/blog/openapi-type-gen/) to generate OpenAPI schema directly from TypeScript types from your validator. This means Elysia supports OpenAPI generation for all validators that support Standard Schema, even if they don’t directly support JSON Schema. ![Valibot with OpenAPI support](/blog/elysia-14/openapi-valibot.webp) > Valibot doesn’t directly support JSON Schema, but we use OpenAPI type gen to handle it Not only will it generate correct input types, but OpenAPI type gen will also generate all possible output types, including error responses. This is truly a unique feature for Elysia, and we are very proud to offer it. ### Standalone Validator You can also use multiple schemas to validate a single input using a standalone validator: ```ts import { Elysia, t } from 'elysia' import { z } from 'zod' import * as v from 'valibot' const app = new Elysia() .guard({ schema: 'standalone', body: z.object({ id: z.coerce.number() }) }) .post( '/user/:id', ({ body }) => body, { body: v.object({ name: v.literal('lilith') }) } ) ``` > This example uses both Zod and Valibot to validate the body, allowing you to use existing schemas in your codebase from different validators together. This works by using each validator to parse a part of the input, then storing each result as a snapshot that merges together to form a single output, ensuring type integrity. ![Using multiple validators to validate part of a body](/blog/elysia-14/standard-schema.webp) > Using TypeBox, Zod, Valibot, Joi, Yup, ArkType, Effect Schema, TypeMap, and ReScript Schema to validate different parts of the body We tested 8 validators at the same time, validating each part of the input, and it just works flawlessly. We are proud to support Standard Schema out of the box. This is a big step for Elysia to not be tied to a single validator, and we are excited to see what you will build with it. ## Macro Macro is one of the most powerful and flexible features of Elysia. It allows you to define custom properties that can modify and extend the functionality of Elysia, enabling you to create your own “sub-framework” to your liking. The versatility of Macro is truly amazing, letting you do things that are nearly impossible with other frameworks, effortlessly. With Elysia 1.4, we bring several improvements to make macros even more versatile. ### Macro Schema You can now define a schema for your macro, allowing you to add custom validation directly from your macro. ![Macro with schema](/blog/elysia-14/macro-schema.webp) > Macro with schema support Macro with schema will automatically validate and infer types to ensure type safety, and it can coexist with existing schemas as well. You can also stack multiple schemas from different macros, or even from Standard Schema, and they will work together seamlessly. Macro schema also supports type inference for **lifecycle within the same macro** **BUT** only with a named single macro due to a TypeScript limitation. ![Macro with extension](/blog/elysia-14/macro-schema-lifecycle.webp) > Using a named single macro to infer types into lifecycle within the same macro If you want to use lifecycle type inference within the same macro, you should use a named single macro instead of multiple stacked macros. > Not to be confused with using macro schema to infer types into a route’s lifecycle event. That works just fine—this limitation only applies to using lifecycle within the same macro. ### Macro Extension You can now extend existing macros, allowing you to build upon existing functionality. ![Macro with extension](/blog/elysia-14/macro-extension.webp) > Macro with extension support This allows you to build upon existing macros and add more functionality to them. It also works recursively with automatic deduplication, allowing you to extend existing macros that already extend other macros without any issues. However, if you ever accidentally create a circular dependency, Elysia has a stack limit of 16 to prevent infinite loops in both runtime and type inference. ### Macro Detail You can now define OpenAPI detail for your macro, allowing you to add more detail to your OpenAPI documentation directly from your macro. If the route already has OpenAPI detail, it will merge the detail together but prefer the route detail over macro detail. ## Lifecycle Type Soundness Since the introduction of [OpenAPI Type Gen](/blog/openapi-type-gen), which generates OpenAPI schema directly from types, we realized it would be great to have type soundness for every lifecycle event. This way, we can accurately document the return type for each lifecycle event and macro, representing all the possibilities of what a single route can return. By refactoring over 3,000+ lines of pure types, reconciling response status types, including unit tests at the type level for all lifecycle APIs to ensure type integrity, and optimizing type performance, we made sure that type inference doesn’t get slower. All of these complex achievements allow us to document all possibilities of what a single route can return. ![Type Soundness](/blog/elysia-14/type-soundness.webp) > Documenting all the possibilities of what a single route can return Not only does this improve the developer experience, but it also improves the reliability of your codebase by ensuring that all possibilities are accounted for in both the API documentation and the client with Eden Treaty. > Type Soundness covers all lifecycle events and macros, allowing you to have complete documentation of your API. The only exception is inline lifecycle events due to performance. We also managed to improve type inference performance by \~9-11% and decrease type instantiation by 11.5%, despite the massive increase in type complexity. ![Type inference](/blog/elysia-14/type-inference.webp) > Type instantiation is reduced by 11.57% from our internal benchmark ## Group standalone schema Previously, `group` with schema used an overwrite strategy, meaning that if you defined a schema in `group`, it would overwrite the existing schema in the route. If you wanted to define a new schema, you had to include the existing schema manually. This was not very ergonomic, and it could lead to errors if you forgot to include the existing schema. Starting from 1.4, `group` with schema uses a standalone strategy, meaning that if you define a schema in `group`, it will not overwrite but will coexist with the route schema. ![group standalone](/blog/elysia-14/group-standalone.webp) > `group` with schema will coexist with the route schema This allows you to define a new schema in `group` without having to include the existing schema manually. ## Notable changes We closed around 300 issues in 1.3.9, so there aren’t many bug fixes in 1.4—we’ve addressed most known issues. ### Improvements - [#861](https://github.com/elysiajs/elysia/issues/861) automatically add HEAD method when defining GET route - [#1389](https://github.com/elysiajs/elysia/pull/1389) NoValidate in reference model ### Changes - ObjectString/ArrayString no longer produce default values due to security reasons - Cookie now dynamically parses when format is likely JSON - export `fileType` for external file type validation for accurate response ### Breaking Changes - remove macro v1 due to lack of type soundness - remove `error` function; use `status` instead - deprecation notice for `response` in `mapResponse`, `afterResponse`; use `responseValue` instead ## Afterword This is the first time we have featured our mascot, Elysia chan, as part of the release note cover image! This will become a tradition for future release notes as well! Our cover art mirrors the theme of the Supersymmetry (music) cover art, where ElysiaJS chan is mirroring Weirs with a similar pose. ![Elysia chan mirroring Supersymmetry](/blog/elysia-14/elysia-supersymmetry.webp) > Elysia chan mirroring the same pose as Weirs from the Supersymmetry cover art [(pixiv)](https://www.pixiv.net/en/artworks/134997229) Isn’t she so cute? I really love how she turned out! I personally worked hard to improve my art skills to be able to draw her. Hope you like it! Anyway, I hope you like this release! We are excited to see what you will build with it! Have a nice day! > I am all > > In this tiny micro universe > > Every morsel of your bittersweet heart > > I loved all > > > > You know > > This game of life is our “riverrun,” > > You were my lucky friend for this journey > > > > There is not much time left > > The sky is showing its fade > > Stars parade > > Time to change our fate --- Source: [blog/elysia-20-rc/index.md](blog/elysia-20-rc/index.md) # Elysia 2 RC - またおいで > Welcome back\~ After a year of development, Elysia 2 RC is finally here. Elysia 2 is still named [DayDream](https://youtu.be/3qRd7Ok0NVY), but the development took a long time, and the improvements from beta to RC were big enough to justify calling it Elysia 2.1. We decided to name this release as well. Elysia 2 RC is named after [またおいで (Please come again)](https://youtu.be/ezOrPwjaHZo) by South& to honor the improvements we’ve made. We recommended reading the [beta release note](/blog/elysia-20) first, as this release builds upon it. *** ## Memory usage First, Elysia 2 is about a better foundation. We entirely rewrote Elysia to shift its focus from throughput alone to efficiency across all axes, starting with memory usage. We invested in understanding the internal memory structures of JSC and V8 and how to optimize Elysia’s memory usage. ![Static Route Memory usage](/blog/elysia-20-rc/mem-static-route.webp) ![Dynamic Route Memory usage](/blog/elysia-20-rc/mem-dynamic-route.webp) This means each route should use less memory than before. Even after a load test, peak memory shouldn’t increase as much and should stay close to its pre-test level. ![Return status Memory usage](/blog/elysia-20-rc/mem-return-status.webp) ![Set Headers Memory usage](/blog/elysia-20-rc/mem-set-headers.webp) An actual codebase almost always uses `set.headers` and `status` on the response, so this improvement is relevant to real codebases. ![Inline Response Memory usage](/blog/elysia-20-rc/mem-inline-response.webp) ![Inline Guard Memory usage](/blog/elysia-20-rc/mem-inline-guard.webp) This means that response schemas and guards use much less memory than before. This is relevant to a larger codebase rather than a “Hello World” test or benchmark. You wouldn’t notice these improvements in a small “Hello World” demo benchmark. They become relevant in a real codebase, especially as the number of routes grows. ### Startup memory by route count Here’s the measured startup memory usage for 25 and 400 routes. Elysia 2 - No validator - 20/2425 routes: 20 MB; 400 routes: 24 MB - - TypeBox - 31/3725 routes: 31 MB; 400 routes: 37 MB - - Zod - 28/4425 routes: 28 MB; 400 routes: 44 MB - Elysia 2 + AOT - No validator - 19/2325 routes: 19 MB; 400 routes: 23 MB - - TypeBox - 25/3525 routes: 25 MB; 400 routes: 35 MB - - Zod - 28/4425 routes: 28 MB; 400 routes: 44 MB - Hono - No validator - 17/2325 routes: 17 MB; 400 routes: 23 MB - - TypeBox - 33/4925 routes: 33 MB; 400 routes: 49 MB - - Zod - 26/4325 routes: 26 MB; 400 routes: 43 MB - H3 - No validator - 19/2525 routes: 19 MB; 400 routes: 25 MB - - TypeBox - 31/4925 routes: 31 MB; 400 routes: 49 MB - - Zod - 28/4525 routes: 28 MB; 400 routes: 45 MB - Express - No validator - 38/4225 routes: 38 MB; 400 routes: 42 MB - - TypeBox - 44/6125 routes: 44 MB; 400 routes: 61 MB - - Zod - 42/5825 routes: 42 MB; 400 routes: 58 MB - Fastify - No validator - 41/5125 routes: 41 MB; 400 routes: 51 MB - - TypeBox - 65/10025 routes: 65 MB; 400 routes: 100 MB - - Zod - 45/6725 routes: 45 MB; 400 routes: 67 MB - Scroll horizontally to see all frameworks. **No validator**TypeBox**Zod Startup memory in MB. Values above each pair read 25/400 routes: the lighter left bar is 25 routes, and the stronger right bar is 400 routes. All bars share a 0–100 MB scale. ### Average memory usage per route Here’s the average memory usage per route when scaling from 25 to 400 routes. The value is calculated by subtracting the 25-route measurement from the 400-route measurement, and then dividing by the 375 added routes. Elysia 2 - No validator - 10.7 kB per route - - TypeBox - 16 kB per route - - Zod - 42.7 kB per route - Elysia 2 + AOT - No validator - 10.7 kB per route - - TypeBox - 26.7 kB per route - - Zod - 42.7 kB per route - Hono - No validator - 16 kB per route - - TypeBox - 42.7 kB per route - - Zod - 45.3 kB per route - H3 - No validator - 16 kB per route - - TypeBox - 48 kB per route - - Zod - 45.3 kB per route - Express - No validator - 10.7 kB per route - - TypeBox - 45.3 kB per route - - Zod - 42.7 kB per route - Fastify - No validator - 26.7 kB per route - - TypeBox - 93.3 kB per route - - Zod - 58.7 kB per route - Scroll horizontally to see all frameworks. **No validator**TypeBox**Zod The change in startup memory from 25 to 400 routes, divided by the 375 added routes, shown in decimal kB per route (1 MB = 1,000 kB). Subtracting the 25-route measurement largely removes the fixed startup footprint. **Lower is better.** Because Elysia 2 includes a JIT and static code analysis, its bundle size is larger than Hono’s and h3’s, so it has a larger fixed startup footprint. But as the route count increases, Elysia 2 should be one of the most memory-efficient framework. *** ## Throughput It wouldn’t be Elysia if we didn’t improve throughput. Elysia 2 RC is still the fastest framework on Bun and very close to Fastify on Node.js. [](/blog/elysia-20-rc/throughput.mp4) See [HTTP Benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/ebc9d5e7dcd1a1786ac77d77e8a2f1a84b026038) for the latest benchmark. ### Memory usage after load test Even after a load test, Elysia 2’s peak memory usage is still very low and predictable compared to other frameworks. **Before**After 1. ElysiaBun **42.2 MB****53.5 MB** 2. h3Bun **38.6 MB****55 MB** 3. HonoBun **38.6 MB****55 MB** 4. EffectBun **52.9 MB****81.4 MB** 5. ElysiaNode **109.4 MB****178.1 MB** 6. ExpressBun **69 MB****183.9 MB** 7. EffectNode **109.9 MB****190.5 MB** 8. h3Node **101.5 MB****202.7 MB** 9. AdonisNode **122.6 MB****216.8 MB** 10. KoaNode **111.6 MB****217.4 MB** 11. NestNode **135.4 MB****221.7 MB** 12. HonoNode **114.5 MB****226 MB** 13. FastifyNode **110.6 MB****226.5 MB** 14. ExpressNode **104.3 MB****238.4 MB** The chart shows server RSS for the frameworks in the throughput video. **Before** is measured after startup validation, and **After** is measured after all benchmark workloads. We have improved validation and coercion latency when using an `Elysia.t` schema. ![validation-coercion-latency.webp](/blog/elysia-20-rc/validation-coercion-latency.webp) *** ## Deeper type inference Previously, we optimized Elysia’s types to be as fast as possible within TypeScript’s limits, so we had to limit what Elysia could do to keep type-checking time reasonable. But now that TypeScript 7 has been released with much faster inference, we can give Elysia 2 much deeper type inference while still being conscious of type-checking speed. We have expanded type inference to cover more cases, especially in error handling. ![Elysia infer error from all possible type](/blog/elysia-20-rc/error-infer.webp) Elysia can now statically infer all possible types from macros, lifecycle hooks, and inline hooks. Even error responses can be mapped or overridden and remain statically analyzable. This makes it easy to identify all possible errors and makes Eden Treaty and OpenAPI type generation more useful, as they can now generate much more accurate types for error responses. ![OpenAPI type gen showing all possible error](/blog/elysia-20-rc/openapi.webp) With OpenAPI type generation, you no longer have to manually annotate errors. It lists all possible errors that the route can throw and generates a proper OpenAPI spec for them. This is somewhat similar to neverthrow and covers a lot of Effect.ts error handling, but with Elysia’s ergonomics. See [Error Handling](/patterns/error-handling) to learn how to structure your code to make Elysia infer errors. *** ## Reverting breaking changes to the lifecycle API Previously, in the beta, we renamed the lifecycle API to remove the `on` prefix. But after a lot of feedback, and to make migrating to Elysia 2 smoother, we decided to revert the lifecycle API to the original `on` prefix. Now Elysia 1 plugins should just work with Elysia 2 without any modifications. ![Error hook](/blog/elysia-20-rc/error-hook.webp) However, the two-parameter `onError` hook still has one breaking change: `code` is now an `Error` instance. Most plugins should still work. *** ## Custom macro types We have a rule to reduce explicit types as much as possible by making Elysia infer them for you. But sometimes, the cases are too limited, and giving you complete control is better than trying to infer the types for you. With Elysia 2, you can now annotate and control macro types explicitly. ![Macro Type](/blog/elysia-20-rc/macro-type.webp) This is less ergonomic than we want, but it gives you more control. Consider it an opt-in advanced feature for type wizards and plugin developers. See [Macro Type Lambda](/patterns/macro#type-lambda) for how to use it. *** ## Eden Plugin Along with macro types, we are now introducing Eden Plugin with an explicit type. ![Eden Plugin](/blog/elysia-20-rc/eden-plugin.webp) This lets you control the plugin’s type so you can add custom fields or create new extensions to Eden Treaty. See [Eden Treaty Plugin](/eden/treaty/plugin/) for how to use it. *** ## Node.js improvements Elysia is a Web Standard framework similar to Hono. This means Elysia should be able to run on any runtime that supports Web Standard APIs. Because Node.js doesn’t provide a Web Standard HTTP server out of the box, we have to provide a compatibility layer to make Elysia run on Node.js, similar to other frameworks like h3, Hono, Next.js, Nuxt, and Astro. We are now using `srvx` and `crossws` on Node.js, which also form the foundation of `h3`. This allows Elysia to maintain good performance and stability on Node.js while still supporting Web Standard APIs. [](/blog/elysia-20-rc/node.mp4) *** ## Compression plugin We have added an officially maintained compression plugin to Elysia 2. It is a simple plugin that compresses the response body using gzip or brotli. ```ts import { compress } from '@elysia/compress' new Elysia() .use(compress()) ``` It will use Bun’s fast path when possible and fall back to the runtime’s native compression. See [Compression Plugin](/plugins/compress/) for more details. *** ## Stability Elysia 2 RC was supposed to be released about two months ago. But because we wanted to make sure the release was stable and smooth, we decided to delay it for intensive testing. The number of test cases has tripled since the beta release and is around 10 times what it was in 1.4, with several performance, memory-usage, and type-inference gates to make sure Elysia doesn’t regress in the future. We developed Elysia 2 while integrating it with our internal servers to make sure it’s ready for production. We are quite confident that Elysia 2 RC is stable and ready for production. What you will see in Elysia 2 is not better performance, but much lower memory usage and startup times. *** We recommend starting new projects with Elysia 2 RC and planning to migrate existing projects to Elysia 2 RC. The migration should be relatively easy with the codemod we provide: Terminal window ```bash bunx @elysia/codemod@latest ``` You can create a new project with Elysia 2 RC using: Terminal window ```bash bun create elysia && bun add elysia@rc ``` We also update Elysiajs skills for LLM with a much better structure as well. We recommend updating your skills to the latest version with: ```plaintext bunx skills add elysiajs/skills ``` *** ## Documentation This release also comes with a new documentation site (you are currently viewing it). The new documentation is revised to be more beginner-friendly and updated for Elysia 2. We have rewrite the entire from Vitepress to Astro Starlight for more control over what we can do with the documentation. We write a lot of new component like [lifecycle playground](/essential/life-cycle) with our in-house design language. We recommended you checking it out from the [documentation home page](/). Many pages has been summarized and rewritten to be more beginner-friendly, and we have added several new content. *** ## Afterword I have never thought that it would be so hard to create something over a year and not release it. From the outside, Elysia must seem like it has been stale for a year, even though we have been working harder than on any other version of Elysia. It’s hard not to rush it. I wake up every day wanting to release it. This reminds me of the song [またおいで (Please come again)](https://youtu.be/ezOrPwjaHZo) by South&. The song is about waiting for someone to come back. I know that there are a lot of people who have been waiting. I want to quickly go home. I want to say that I have been working so hard every day. I want to say that I have come back. I want to give you the embrace you deserve. But the right time for that will come. It might be a year from now but I’ll bring my memory back. Until then, I’ll let you have a piece of me in your heart. *** Coincidentally, Elysia 2 RC is ready just before the end of the rainy season here, as the song keeps repeating: > “I’ll be back before the rainy season is over” > Cover art for Elysia 2.0 RC\ > \ > As promise I will be here before rainy season over\ > \ > Please wait just a little while longer > > — SaltyAom (@saltyAom) [October 9, 2026](https://x.com/saltyAom/status/2108602528497893405?ref_src=twsrc%5Etfw) This wouldn’t be possible without all the support from the community and our GitHub sponsors, especially Jarred Sumner (Bun). Their support allows us to work on Elysia full-time. For you it might have been another software release but for me it’s a year of my life. I hope you can feel the love and care I put into it. > Please don’t cry, please don’t cry > > I’ll be back when the rainy season is over > > Please come again, please come again > > May the scent of hollyhocks guide you here I’ll stay within your heart. Thank you for waiting for Elysia 2 RC. \- SaltyAom --- Source: [blog/elysia-20/index.md](blog/elysia-20/index.md) # Elysia 2 beta - DayDream Following our convention, this release is named after the song [DayDream](https://youtu.be/3qRd7Ok0NVY) from Guns Girl Z 9th anniversary song. Elysia 2 is a **complete rewrite** of Elysia, restarting from zero and focusing on other areas, not just throughput. The name is to honor the struggle Kiana went through, a long journey to make her wish come true. *** We tried to make Elysia the fastest JavaScript framework. But we have sacrificed a lot in the process. Memory usage, bundle size, and startup time were secondary concerns. The scope goes further than we originally designed it to be and the features we added keep piling up like technical debt. We never expected Elysia to be where it is today so we didn’t really design it for a really big scale. We thought it would be a niche framework that solves specific problems so we never marketed or advertised Elysia other than just posting on a personal X/Twitter profile. But we were wrong. Today, **Elysia is the 9th most used JavaScript backend framework** and highlighted at [State of JavaScript 2025](https://2025.stateofjs.com/en-US/libraries/back-end-frameworks/). We genuinely believe in building decent software, and if it’s good then people will follow and you proved that to us. Much more than we thought. From your feedback, we now realize that there are more important things to consider other than the runtime performance. So we rethink all the scope and features it has. We read Elysia’s codebase and rearchitect it with all of the features we have from the ground up while keeping performance as a priority. Elysia 2 beta is, well, **beta**. It’s not a stable release yet. You can try it with `elysia@next`. Terminal window ```bash # auto migration bunx @elysia/codemod@latest # manual migration bun add elysia@next ``` - [Bundle Size](#bundle-size) - [Startup Time](#startup-time) - [Memory Usage](#memory-usage) - [TypeBox](#typebox) - [Ahead of Time Compilation](#ahead-of-time-compilation) - [Startup Time](#startup-time) - [Adapter and Runtime](#adapter-and-runtime) - [Faster APIS](#faster-apis) - [Defer](#defer) - [Breaking Change](#breaking-change) - [Stability](#stability) - [What to expect](#what-to-expect-from-elysia-2) - [Afterwords](#afterwords) ## Bundle Size We have made Elysia 2 smaller than Elysia 1.4 by **more than 50%** and we can make it even smaller with a build plugin. ### Tree Shake TypeBox Elysia 2 is designed to be **modular**. We added Standard Schema support in 1.4 but because of the structure, Elysia still bundles TypeBox whether you use it or not. Elysia 2 can now fully tree-shake TypeBox out entirely if you don’t use it. **Elysia 1**Elysia 2 1. None **736 KB****344 KB** 2. TypeBox **736 KB****847 KB** 3. Valibot **742 KB****350 KB** 4. Zod **1,260 KB****868 KB** We can get it down even more by using an AOT build plugin to analyze and remove unused parts of Elysia’s features. **Elysia 2**Elysia 2 + AOT 1. None **344 KB****226 KB** 2. TypeBox **847 KB****288 KB** 3. Valibot **350 KB****266 KB** 4. Zod **868 KB****784 KB** With an AOT build plugin, TypeBox Compiler can be removed entirely if all schemas can be precompiled. 226KB sounds big but it is an un-minified result so if we minify it, it becomes **141KB**. And if we compare it to other frameworks, it’s actually quite small. 1. Hono4.12.28 **21 KB** 2. h32.0.1-rc.26 **103 KB** 3. Elysia2.0.0-exp.60 **141 KB** 4. Effect HTTP Node4.0.0-beta.102 **357 KB** 5. Express5.2.1 **603 KB** 6. Koa3.2.1 **554 KB** 7. Fastify5.10.0 **729 KB** 8. Adonis5.8.0 **1.2 MB** 9. Nest10.2.8 **1.3 MB** The result is a “hello world” app built using `bun build --minify`, see [HTTP benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/ba1520ccacfb4a1783a3211b1c9682e8c8e43870). Roughly \~50% of Elysia 2’s footprint is Sucrose (Static Code Analysis) and an Ahead of Time compiler. Even with these, Elysia still manages to be much smaller than traditional frameworks like Express, Fastify and Koa by a mile. ## Startup Time Besides bundle size, startup time is also a big concern for Serverless and Edge functions like Cloudflare Worker, Vercel Edge Function. We have improved the startup time of Elysia 2 by **more than 30%** especially with TypeBox. **Elysia 1**Elysia 2 1. None **37 ms****26 ms** 2. TypeBox **37 ms****28 ms** 3. Zod **46 ms****36 ms** And we can measure a server’s startup time compared to other frameworks. ### At 25 Routes The server is registered with 25 routes with distinct 5-field schemas. Startup time is measured from starting the server until the first request hits. 1. Hono **21 ms** 2. Elysia **24 ms** 3. Elysia + TypeBox **28 ms** 4. Hono + Zod **33 ms** 5. Elysia + Zod **38 ms** 6. Express **61 ms** 7. Express + Zod **72 ms** 8. Fastify **90 ms** 9. Fastify + Zod **93 ms** Startup time can vary. The measurement is the average result of 5 runs. Expect +- 3-4ms difference. ### At 1,000 Routes Each app registers 1,000 POST routes, each with a distinct 5-field schema. 1. Hono **23 ms** 2. Elysia **29 ms** 3. Elysia + TypeBox **43 ms** 4. Hono + Zod **56 ms** 5. Express **60 ms** 6. Elysia + Zod **67 ms** 7. Express + Zod **103 ms** 8. Fastify **108 ms** 9. Fastify + Zod **154 ms** Elysia is using full-compile reconstruct mode so it has already paid all compilation costs, not lazily compiled. ## Memory usage Using the same benchmark as before, ### At 25 Routes Same condition as before. The server is registered with 25 routes with distinct 5-field schemas. Startup time is measured from starting the server until the first request hits. 1. Hono **35 MB** 2. Elysia **36 MB** 3. Elysia + TypeBox **42 MB** 4. Hono + Zod **47 MB** 5. Elysia + Zod **48 MB** 6. Express **55 MB** 7. Fastify **56 MB** 8. Express + Zod **61 MB** 9. Fastify + Zod **63 MB** ### Before/After loadtest To measure accurate memory usage, we measure the memory **before and after** running HTTP loadtest. The source can be found at [HTTP benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/ba1520ccacfb4a1783a3211b1c9682e8c8e43870). **Before**After 1. Elysia2.0.0-exp.60 **37 MB****45.7 MB** 2. h32.0.1-rc.26 **43.3 MB****66.4 MB** 3. Hono4.12.28 **32.4 MB****73.9 MB** 4. Effect HTTP Node4.0.0-beta.102 **91.3 MB****132.3 MB** 5. Express5.2.1 **60.5 MB****150.8 MB** 6. Adonis5.8.0 **123.5 MB****160 MB** 7. Fastify5.10.0 **98.5 MB****192.4 MB** 8. Koa3.2.1 **94.8 MB****200.4 MB** 9. Nest10.2.8 **118 MB****213.7 MB** Elysia clearly has the lowest memory usage after loadtest with the most stable and predictable memory usage. This is important for 2 things: 1. Serverless and Edge Function where memory usage is billed by the second and the memory usage is limited 2. Cluster mode will duplicate the process and the memory usage will be **roughly** multiplied by the number of clusters This means if you run an HTTP server in cluster mode with 10 CPU cores using 10 workers, **the memory usage will be \~10x of the original.** - If you run Elysia with 10 workers, the memory usage will be \~450 MB. - If you run Hono with 10 workers, the memory usage will be \~740 MB. - If you run Fastify with 10 workers, the memory usage will be \~2,100 MB. ## TypeBox We have updated from TypeBox 0.34 to TypeBox 1.3. Please refer to [TypeBox 1.0 migration guide](https://github.com/sinclairzx81/typebox/blob/main/changelog/1.0.0-migration.md). ### Cookie Schema per field Elysia now supports setting Cookie schema per field instead of setting it as a whole. ```ts new Elysia() .get( '/', { cookie: t.Object({ a: t.Cookie(t.String(), { sign: true }) }) }, () => 'ok' ) ``` ### t.Accelerate Convert schema to TypeBox Compiler via Standard JSON Schema for best performance. ```ts import * as z from 'zod' new Elysia() .post( '/', { body: t.Accelerate( z.object({ name: z.string() }) ) }, () => 'ok' ) ``` This API is flagged as alpha. Proceed with caution. ### Ref:Rain We didn’t re-export TypeBox but we built a small abstraction over it. **“Ref:Rain”** is a nickname for that small abstraction of TypeBox. In a real-world app, not all schemas are distinct. Some repeated schemas like params or query could be **shared** and reused. Starting from a primitive type, many schemas would just use an empty primitive type like this. ```ts t.Object({ name: t.String(), age: t.Number() }) ``` If these primitive types are called 100k times, they would create new allocations. So instead of allocating 100k schemas, we simply **reuse** them by reference. 1. Elysia 1.4 **33.86 MB** 2. Elysia 2 **17.69 MB** ### Compilation We notice that there are some areas where schemas could be repeated, eg. query, params or even reference schema. We have added Schema Compilation Cache to prevent unnecessary recompilation. It will omit metadata like `description`, `examples`, `tags` before checking the cache, so duplicated schema with OpenAPI metadata would be cached as well. ### Compile 100k routes with schema \- stress/compile-with-schema.ts **Time taken**Memory usage 1. Elysia1.4 **174,603 ms****1,158 MB** 2. Elysia2 **310 ms****228 MB** The result is not a typo. And Elysia 2 can completely offload the compilation process to build time. ### 100k complex schemas \- stress/schema.ts Create 100k TypeBox schemas with 45 types each. **Time taken**Memory usage 1. Elysia1.4 **227 ms****319 MB** 2. Elysia2 **209 ms****228 MB** All of these result in better overall memory usage by 33%. ## Ahead of Time Compilation Elysia has a “compiler”. It analyzes your handler to produce the optimal route handler. This is what makes Elysia so fast but it came with a cost. This process happens once at server start and once for each route, which adds a micro-delay until the route can run. Each compilation alone is very fast, but it adds up especially for a real-world app. *** We rewrite Elysia’s “compiler” to be able to offload to build-time instead, or so-called **Ahead of Time Compilation**. build.ts ```ts import { aot } from 'elysia/plugin/aot/bun' const { outputs } = await Bun.build({ entrypoints: ['src/index.ts'], outdir: 'dist', target: 'bun', plugins: [aot('src/index.ts')] }) ``` You can opt-in by using `export` for your Elysia instance, then set up a build script and point the plugin to the main Elysia instance you want. AOT plugin does all of the following: 1. Compile handler code 2. Compile TypeBox schema 3. Compile Exact mirror schema 4. Precompute `default` property merger 5. Statically analyze unused Elysia modules 6. Create optimal code for each target platform 7. If all TypeBox schemas can be compiled, stub TypeBox entirely Elysia will then attempt to dry-run the app to reproduce the most accurate result. At runtime, Elysia will simply use the built schema and skip the compilation entirely. ## Startup Time Elysia 1.4 needs to compile `fetch` entry handler. So every first request to the server is 2 compilations away. In Elysia 2, we have removed the compilation in the fetch entry entirely, and offloaded request compilation to AOT, which results in far better startup time. 1. Elysia 1.4 **0.851 ms** 2. Elysia 2 AOT **0.046 ms** ### Memory Usage An app with 100k distinct schemas with eager compilation can expect a far better result: **Startup time**Memory usage 1. Elysia2 **4,110 ms****1.65 GB** 2. Elysia2 AOT **775 ms****400 MB** ### Synthetic Memory benchmark **Identical handlers**Distinct handlers (real app) 1. Elysia 1 **32.1 KB/route****36.9 KB/route** 2. Elysia 2 (JIT) **2.3 KB/route****5.8 KB/route** 3. Elysia 2 + AOT **4.6 KB/route****4.7 KB/route** In the real world, an “identical handler” is very unlikely to occur but we include it to address the result of synthetic benchmark. ### Partial Default Merge Elysia 2 can now allocate static default values ahead of time, and can apply partial merging much faster. 1. Elysia 1.4 **21.6 μs** 2. Elysia 2 **2.5 μs** ### Target Build plugin is not limited to Bun. We have support and regression tests for the following bundlers: - Bun.build - vite - esbuild - rspack - **unplugin** Each bundler plugin can be imported by its respective name under `elysia/plugin/aot/` and used like a regular plugin. build.ts ```ts import { aot } from 'elysia/plugin/aot/vite' export default defineConfig({ plugins: [aot('src/index.ts')] }) ``` **Unplugin** can also be expanded to additional bundlers like: - rollup - rolldown - webpack - farm ### Should I use Ahead of Time Compilation? Yes if applicable. We almost always see a great result in all apps that opt-in to AOT build mode. The actual cost of AOT mode is that it will produce inline code for each route and schema into your final bundle. This might slightly increase bundle size and the small amount of memory retained at app start but **greatly reduces peak memory usage**. ### Build Time Environment Because AOT plugin will dry run your app, there are 2 things that you have to look out for. 1. Long running process, eg. Database connection like Postgres Pool, Redis Pool. This will prevent your app from exiting in the build environment so it’s recommended to add `process.exit(0)` once your bundler is done packing up your code. ```ts Bun.build({ entrypoints: ['src/index.ts'], outdir: 'dist', target: 'bun', plugins: [aot('src/index.ts')] }).then(() => { process.exit(0) }) ``` 2. Conditional flag Apps that opt-in to Elysia AOT should call Elysia APIs exactly the same way at build time and runtime to prevent an invalid manifest. Elysia should be able to detect if that’s the case and panic upfront before starting the server and notify you of the reason and a suggestion. Because it’s a dry-run, you can conditionally skip some parts of your app by `inAotBuild`: ```ts import { inAotBuild } from 'elysia' if (inAotBuild()) console.log('Do something in AOT dry-run') ``` ### Cloudflare Worker If you are using Cloudflare Worker, we recommend opting-in to AOT to prevent paying the startup time cost upfront. Because Cloudflare Worker doesn’t allow `new Function`, you have to either compile all routes upfront and pay for all compilation at start or simply move that process to build time. Cloudflare Worker already recommends using Vite plugin to build before publishing your worker code, so the process is simply adding one more plugin to your Vite config. ```ts import { defineConfig } from 'vite' import { cloudflare } from '@cloudflare/vite-plugin' import { aot } from 'elysia/plugin/vite' export default defineConfig({ plugins: [cloudflare(), aot('src/index.ts')] }) ``` ## Adapter and Runtime Elysia has always been using Web Standard Request/Response similar to Hono from day 1. This means **Elysia is as portable as Hono** if the runtime supports Web Standard Request. For runtimes that don’t support Web Standard, Elysia offers an Adapter API to run HTTP servers in various environments. The catch phrase of Elysia is “optimize for Bun” **NOT “only run on Bun”**, it has never been the case especially when it is built on the same foundation as Hono, which is advertised as “portable”. ### Adapter v2 Adapter has now been reworked and is publicly available instead of being private. Previously, Elysia adapter used stringified code that would be passed to the compiler. This made it hard to create or contribute to one. In Elysia 2, all fields are now just functions. ```ts import { createAdapter } from 'elysia/adapter' const adapter = createAdapter({ name: 'bangboo', // map value to response, not limited to Web Standard response: { map: mapResponse }, setup(app: Elysia) { // do something with Elysia // run before listen }, listen() { // bind to runtime HTTP server }, // an so on ...rest }) ``` All fields are type-safe, and the function basically returns an object that you can easily modify or swap as you see fit. ### Node runtime We are using Adapter v2 to wire Elysia Node adapter to use srvx and crossws under the hood. ```ts import { Elysia } from 'elysia' import { node } from '@elysia/node' new Elysia({ adapter: node() }) .listen(3000) ``` We have updated srvx to `0.12` with `FastResponse` which should greatly improve performance to almost the same as normal Node HTTP library. 1. ElysiaBun 2.0.0-exp.60 **210,411 req/s** 2. HonoBun 4.12.28 **168,275 req/s** 3. FastifyNode 5.10.0 **101,360 req/s** 4. ElysiaNode 2.0.0-exp.60 **93,845 req/s** 5. HonoNode 4.12.28 **86,129 req/s** 6. Effect HTTPBun 4.0.0-beta.102 **88,936 req/s** 7. Effect HTTPNode 4.0.0-beta.102 **59,958 req/s** 8. KoaNode 3.2.1 **43,294 req/s** 9. ExpressNode 5.2.1 **42,974 req/s** 10. AdonisNode 5.8.0 **36,557 req/s** 11. NestNode 10.2.8 **31,675 req/s** See [HTTP benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/ba1520ccacfb4a1783a3211b1c9682e8c8e43870). ### Node Websocket We have separated the WebSocket from normal node adapter to reduce bundle size. If you want to use websocket on Node, simply import from websocket sub-folder. ```ts import { Elysia } from 'elysia' import { node } from '@elysia/node/websocket' new Elysia({ adapter: node() }) .listen(3000) ``` ## Faster APIs HTTP throughput, post memory usage and startup time are not the only metrics that matter. We need to consider how real world apps are used, e.g., plugin, lifecycle-event. Here we gather some metrics on several Elysia APIs to see how they perform. You can find the source code of the benchmark in [elysia/example/stress](https://github.com/elysiajs/elysia/tree/kiana/example/stress). ### Adding 100k Routes Repeatedly add 100k static routes to the app and measure the time taken to finish. \- stress/route.ts **Time taken**Memory usage 1. Elysia2 **7 ms****31 MB** 2. Elysia1.4 **23 ms****317 MB** 3. Hono4.12.32 **172 ms****203 MB** ### Apply 100k Routers \- stress/apply-plugin.ts Repeatedly apply 100k Elysia instances that have 1 route to the main app and measure the time taken to finish. **Time taken**Memory usage 1. Elysia2 **5 ms****9 MB** 2. Elysia1.4 **116 ms****116 MB** 3. Hono4.12.32 **192 ms****503 MB** ### Apply 100k Plugins with Hooks \- stress/lifecycle.ts Repeatedly apply 100k Elysia instances that contain 1 lifecycle event with **scope/plugin** encapsulation scope or simply apply middleware in Hono. **Time taken**Memory usage 1. Elysia2 **10 ms****15 MB** 2. Elysia1.4 **58 ms****126 MB** 3. Hono4.12.32 **252,847 ms****21,558 MB** \*Hono result is not a typo. ## Defer Elysia now supports programmatically adding callback functions to run after a response is sent. ```ts new Elysia() .get(({ defer }) => { const disconnect = connect() // run after response is sent defer(() => { disconnect() }) }) ``` `defer` will execute in a queue after the `afterResponse` hook. ## Breaking Change We have updated some APIs and removed several soft deprecated APIs in this version. To make migration to Elysia 2 as smooth as possible, we offer Elysia codemod which should automate 95% of the code to Elysia 2. ```ts bunx @elysia/codemod@latest ``` *** ### Route Parameter Swap We have run the survey and found that many developers would prefer a hook/schema to come **before** handler. ```ts new Elysia() .post('/', { isAdmin: true, body: t.Object({ name: t.String() }) }, () => { // handler }) ``` This way, it’s more ergonomic to read and review code instead of: 1. See route and handler 2. Scroll down to hook metadata 3. Scroll back up to read the code The old API makes you go up-down-up-down more than you should have, so we changed that so it’s easier to read the code. This migration can be fully automated by codemod. *** ### @elysia scope We have disputed and obtained the `@elysia` scope from `npm`. We will be using `@elysia` to publish officially maintained Elysia plugins for Elysia 1.4+ and leave `@elysiajs` to support Elysia 1.x plugins. *** ### Error Handling We have completely changed the error handling API. Previously, we were using `code` to differentiate an error. But we mainly have 3 problems with that. ##### 1. Redundant boilerplate To add `code`, we have to register all errors with `error`. The instance will carry the type inference for the code name as follows: And to safely infer code and error type, you would need to import a base instance that carries that type inference: ```ts const ErrorDictionary = new Elysia() .error('MyError', MyError) const ErrorHandler = new Elysia() .use(ErrorDictionary) .onError(({ code, error }) => { switch(code) { case 'MyError': // correctly infer return error } }) const WithoutDictionary = new Elysia() .onError(({ code, error }) => { switch(code) { // No infer case 'MyError': // Generic Error return error } }) ``` This adds additional boilerplate and doesn’t support circular dependency. ##### 2. Adding Prototype To make this possible, `error` would add a new prototype to input Error class. Then Elysia will look up the error prototype and match that. ##### 3. Type Inference It is not possible to infer response types because it’s not statically possible to analyze the error handling. #### New API To solve this, we remove error code entirely and introduce a new API that takes an Error class directly. ```ts new Elysia() .onError(MyError, () => { // do something }) ``` This solves all 3 problems we previously had while making it much more concise. You can imagine this is like `mapResponse` but exclusively for error handling. Because of this change, it is possible to infer Error and mapped value directly into route response type. This means any error will be able to be collected correctly and make Eden Error handling much more expressive. ![New Error Handling API](/blog/elysia-20/error-infer.webp) #### Elysia Error To use Elysia specific errors like `notFound`, `validation`, simply import the respective error name from Elysia. ```ts import { NotFound, ValidationError } from 'elysia' new Elysia() .onError(NotFound, () => { }) .onError(ValidationError, () => { // do something }) ``` #### Fallback To handle fallback case where error is not statically analyzable, simply use `instanceof Error`. ```ts import { NotFound, ValidationError } from 'elysia' new Elysia() .onError(({ error }) => { if (error instanceof MyError) // do something }) ``` *** ### Problem Error Elysia now uses [RFC 9457](https://datatracker.ietf.org/doc/html/rfc9457): Problem Details API for describing errors. All Elysia errors now return problem detail JSON instead of bare strings and align to use problem detail structure instead. Additionally, Elysia now uses `application/problem+json` headers to send error instead of `application/json`. To opt in, you can use `problem` instead of `status` which handles the necessary work instead for RFC 9457. ```ts import { Elysia, problem } from 'elysia' new Elysia() .get('/', () => problem(418, { detail: 'I love teapot' })) .onError(MyError, () => problem(418, { detail: 'I love teapot' })) ``` Eden already handles new headers and format but for other clients, make sure to double check your frontend to see if it works. *** ### return or throw This behavior has caused a lot of confusion for many users. All errors, whether returned or thrown, will now be able to be intercepted by `onError`. *** ### Macros `.macro(name, definition)` is now removed. It was introduced as a workaround to fix macro with resolve and/or schema. ```ts // Elysia 1.x new Elysia() .macro('auth', { resolve: ({ headers }) => ({ user: auth(headers) }) }) .get('/', ({ auth }) => auth) // Doesn't work in Elysia 1.x but work in 2.0 new Elysia() .macro({ auth: { derive: ({ headers }) => ({ user: auth(headers) }) } }) .get('/', ({ auth }) => auth) ``` Now that this is fixed, we now remove the workaround method to simplify macro API. *** ### WebSocket WebSocket is now an opt-in feature and has a similar API to `.get`, `.post` and other normal APIs. ```ts import { websocket } from 'elysia/websocket' new Elysia() // add only once in any part of an app .use(websocket()) .ws('/', ({ params: { id } }) => id) .ws('/stream', function* () { // instead of ws.send(1) yield 1 yield 2 yield 3 }) ``` The original WebSocket is modeled after Bun. However, we found that there’s really no reason for an end-user to understand what Bun websocket looks like to use WebSocket. So instead, we replace it with already familiar Elysia APIs for a better mental model and type inference. With a generator function and `yield`, Elysia can now infer an actual WebSocket response directly instead of using a secondary source like a response schema. #### Changes to watch out for - `.ws()` now accepts 2-3 arguments - 2-arg forms (`.ws('/ws', handler)`, `.ws('/ws', options)`) are unchanged - 3-arg form is now `.ws('/ws', options, handler)` - `ws.data` is now inlined to `ws` directly - generator functions and `yield` are now the preferred way of sending data instead of `ws.send` for type safety *** ### `resolve` is now `derive` We found that most people are confused about when to use `derive` and `resolve` and often mistake one for another. We figured out that most people would actually need `derive` to run on `beforeHandle` not `transform`. So old `resolve` is the new `derive`: - `derive` now runs on `beforeHandle` (previous `resolve` behavior) - `resolve` is now removed ```ts // 1.x app.resolve(({ headers }) => ({ user: auth(headers) })) // 2.0 app.derive(({ headers }) => ({ user: auth(headers) })) ``` *** ### ‘scoped’ scope is now ‘plugin’ scope - The `{ as: 'scope' }` object form is **deprecated**. Hook methods still accept it with a one-time warning and map `'scoped'` to `'plugin'`, but `.guard({ as })` throws - Pass the scope as the first argument instead. A positional `'scoped'` throws - `.decorate()` / `.state()` `{ as: 'append' | 'override' }` ```ts // 1.x app.onBeforeHandle({ as: 'scoped' }, fn) app.as('scoped') app.guard({ as: 'scoped' }, fn) app.decorate({ as: 'override' }, 'db', db) // 2.0 app.onBeforeHandle('plugin', fn) app.as('plugin') app.guard('plugin', fn) app.decorate('override', 'db', db) ``` *** ### Guard/group default to ‘override’ **Every** `.guard()` and `.group()` defaults to **override** - the closer to the route, the more power, the nearer schema **replaces** an inherited one - `schema: 'standalone'` is explicitly required across all APIs **Migration:** if you use `guard`, add `schema: 'standalone'`: ```ts // 1.x string-scope & run forms were implicitly additive (standalone) app.guard({ body: t.Object({ a: t.String() }) }) // 2.0 now overrides by default; prefer closer schema first app.guard({ schema: 'standalone', body: t.Object({ a: t.String() }) }) ``` *** ### `aot: false` is removed Dynamic mode is now removed entirely and replaced by AOT mode. *** ### Remove file-type Elysia 2 now doesn’t include the `file-type` package to verify a file’s mime type by default. It’s only used with `t.File` and `t.Files` with specified type. Any app that doesn’t use this carries a heavy module, so now we remove it by default. To migrate, simply call `setFileTypeDetector` and pass `fileTypeFromFile` or your own implementation in any part of an Elysia app. ```ts import { setFileTypeDetector } from 'elysia' import { fileTypeFromFile } from 'file-type' setFileTypeDetector(fileTypeFromFile) ``` ## Stability Elysia now covers more unit tests, increased from 2800 expect calls to 10,000+ expect calls. This includes performance and memory usage reports and a bundle size hard guard per commit and pull request to prevent and track performance regressions. ### Additional Change You can find additional internal or smaller changes that shouldn’t affect most apps here: [migration guide](https://github.com/elysiajs/elysia/pull/1873#issuecomment-4734573873) #### Behavior Change - `context.path` is now readonly - Validation error `payload.expected` values are shared and deeply frozen - Schemas are cloned on first registration and reused by identity - Signed-cookie verification now defaults to `verify: 'lazy'` - Values returned early from `onRequest()` hooks now pass through `mapResponse` before they are sent. - \[Type] instance-level `.onParse()` and `.onTransform()` no longer inherit ANY guard input schema — its context is typed as raw, pre-validation values (path params from the instance prefix are kept) - `afterHandle` will skip the rest when short-circuiting - `Error.summary` now uses default TypeBox message instead - `Error.summary` now has support for Standard Schema - Validator runs `Convert` and reorders to `Convert -> Check -> DecodeUnsafe` for codec schemas - `streamResponse` (adapter utils) now yields raw body chunks - a bodyless `GET`/`HEAD` route no longer runs `parse` lifecycle hooks - the default validation (422) response now scopes the echoed `found` value: a request body whose JSON exceeds 4KB is no longer reflected back in full - only register static content to Bun.serve.routes - Bun native static-route dispatch is no longer disabled by the presence of an `error` hook - `mapResponse` hook or a request schema now correctly fall through to the JS path instead of being served natively unmapped/unvalidated in Bun.serve.routes - unhandled errors no longer leak their `message` to the client when `NODE_ENV=production` - thrown/returned generic `Error` or any `ElysiaError` with status >= 500 without explicit `response`, now responds with `Internal Server Error` instead of the raw error message for security reasons - the JSON error response produced by the response mapper for a returned/mapped `Error` no longer includes the `cause` field, and its `message` is replaced with `Internal Server Error` when `NODE_ENV=production`. Non-production output is unchanged (`{ name, message, cause }`) - `file()` responses resolve their `content-type` from a case-insensitive file extension - synchronous Standard Schema validators no longer force async route emission - Bun native static-route `Response` objects are no longer retained on the base Elysia instance during router build #### Improvement - `t.File({ type })` / `t.Files({ type })` content-detection failures now report the offending property path (`property: '/avatar'`, `/files/0`) instead of an empty path — the validated value is identity-walked only when a detection fails - adapter v2 - sub type validator - shared validator cached - shared schema reference - Cookie schema field - plain `t.File()` / `t.Files()` (no `type` option) no longer force the async validation path #### Bug fix - return 415 when unsure about content-type - `context.server` now receives the active Bun server for socket requests and is `null` for direct `app.handle()` - non-Bun (Node / web-standard) adapters, a plain-string response now sets `content-type: text/plain` - cookie values were percent-decoded twice (`parse()` already decodes), corrupting any value that decodes to something still containing `%xx` (e.g. `100%2520off` -> `100 off` instead of `100%20off`) - `.compile()` overwrote WebSocket upgrade handlers with a generic HTTP handler, breaking upgrades after eager/AOT compilation - `normalize: false` returned the precomputed default object by reference, so one request’s handler mutation of a defaulted body/query leaked into subsequent requests - a standalone `response` schema without a `200` entry threw `'~kind' in undefined` - dynamic (parameterized) routes did not match a trailing slash under the default `strictPath: false` - per-app `loosePath`/decoded-path caches grew without bound on attacker-controlled request paths - an `onError` returning a `File`/`Blob` threw a `TypeError` - response headers were dropped on non-Bun runtimes when re-streaming a returned `Response` - a `wrap()` HOC was skipped for native static routes on Bun - signed-cookie HMAC comparison fell back to a non-constant-time `===` off Bun (timing side channel) - cookie secret rotation with a `null` (“allow unsigned”) entry threw `Secret key must be provided` for any value containing a dot - `application/xml` was parsed as `x-www-form-urlencoded` - Sucrose: handlers using optional chaining on the context (`ctx?.query`) did not infer the field - an unguarded `JSON.parse` in query array parsing turned malformed input into a request-controlled 500 - Bun native static routes were never installed when every static response was synchronous - error-path `mapResponse` codegen assigned an undeclared `tmp`, leaking the mapped response onto `globalThis` - schema-less body routes now treat `Transfer-Encoding` as body-present before touching `request.body`, preserving the fast framing-header path for chunked/proxy-framed requests without `Content-Length` ## What to expect from Elysia 2 It’s not likely that you will see significantly faster throughput with Elysia 2. Elysia 2 is a better foundation of Elysia with all of the scope we have and will have in the future with a broader goal than just pure throughput. What you should look out for is a reduced peak memory usage and faster startup time, especially with Serverless environments like Cloudflare Worker. Although this version is a bit rough because of all the breaking changes and the rewrite, you should be able to expect a much more solid experience with the future version of Elysia. ## Should I update to Elysia 2 beta? Elysia 2 is well tested in synthetic environments and some of our internal servers without critical problems. Codemod can handle almost all of the cases and we use it to migrate all Elysia plugins with \~95% coverage without much effort. Terminal window ```bash bunx @elysia/codemod@latest ``` We label the current version “beta” as a graceful transition duration for plugin developers to update their plugin to Elysia 2. It’s stable enough on our end that we are confident enough to migrate our own apps to Elysia 2 beta. But because it’s an entire rewrite, there might be some quirks compared to the previous version but we think it’s stable enough. Far more stable than any beta Elysia version we have shipped. We will keep the beta label until significant portions of plugins have been migrated to Elysia 2. ## Afterwords DayDream is a big release for Elysia with more than 8 months in the making. This wouldn’t be possible without all of the [sponsors](https://github.com/sponsors/saltyaom), especially Jarred Sumner. They make it possible for me to maintain Elysia full-time. Not without all of the time and resources the community has given us not just financially but emotionally as well. “Daydream” as a song got me every single time, especially with the story of where it comes from. GGZ was probably one of the very first mobile games I got to play. Seeing the story of how your favorite character struggles through so much hardship just to make her wish come true in the end makes me want to do the same. 8 months for me feels like it passed in the blink of an eye. Yesterday I was writing the Elysia 1.4 release notes and now I’m writing Elysia 2. And there is still a much longer road ahead. But even if the road is infinitely long, there will be one day we reach the end eventually. > **Per Ardua Ad Astra** > > Through struggles to the stars. *** I saved this version name because it has a special place in my heart. For a very special occasion, especially now that it fits today’s Elysia occasion perfectly. With Elysia now, we literally just wake up to see that everything we did is just a dream. A slightly different but better reality to wake up in. I have been dreaming since I was a kid that I want to create something for people to use to build great things. I tried so hard to make Elysia as great as it could be. I try my best every day to make the best tool in the world, and sometimes I’m a bit too hard on myself. Sometimes I wonder why I spent so much time in Open Source. Is it really worth it? Financially, it’s terrible. Long term sounds terrible. Nothing sounds wise about working in non-self sufficient Open Source Software at all. But still, I keep going. When I’m tired and don’t want to do anything anymore, sometimes I just stop. And I secretly look at how people use Elysia without interacting. I see a lot of Japanese, Korean, Spanish blog posts/videos about Elysia even if I don’t understand it. Seeing how people use your tool that you spent so much effort building for years, a story, an experience of how Elysia helps you build your tool, is something I’m not sure how to describe. It’s a treasure only for me to cherish. It’s something that keeps me going every day. To realize that what I do has a meaning. To realize that I’m not alone. > How many wishes do I have to make for the dawn to break > > Will there ever come a day when all of this ends? > > If I wipe away my tears, my longing will definitely become my strength > > Even though I didn’t close my eyes > > I saw a dream, it was such a noisy dream > > I really want to see you, run to you, and say “Long time no see” Even if I know it’s just a stupid dream, I still want to keep on dreaming. I still have so many more plans for Elysia that I want to share with you. > Even though I didn’t close my eyes, I saw a dream > > It was such a noisy dream > > I want to see you, I want to meet you Thank you for staying with me. \- SaltyAom --- Source: [blog/elysia-supabase/index.md](blog/elysia-supabase/index.md) # Elysia with Supabase. Your next backend at sonic speed Supabase, an Open Source alternative to Firebase, has become one of the developers’ favorite toolkits known for rapid development. It features PostgreSQL, ready-to-use user authentication, Serverless Edge function, Cloud Storage, and more, ready to use. Supabase has already pre-built and composed the features you find yourself redoing for the 100th time into less than 10 lines of code. For example, authentication, which would require you to rewrite a hundred lines of code for every project, is reduced to just: ```ts supabase.auth.signUp(body) supabase.auth.signInWithPassword(body) ``` Then Supabase will handle the rest, confirming email by sending a confirmation link, or authentication with a magic link or OTP, securing your database with row-level authentication, you name it. Things that take many hours to redo in every project are now a matter of a minute to accomplish. ## Elysia If you haven’t heard, Elysia is a Bun-first web framework built with speed and Developer Experience in mind. Elysia outperforms Express by nearly \~20x, while having almost the same syntax as Express and Fastify. ###### (Performance may vary per machine. We recommend you run [the benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark) on your machine before assessing the performance) Elysia has a very snappy Developer Experience. Not only can you define a single source of truth for types, but Elysia also detects and warns when you accidentally create a change in data. All done in a declaratively small line of code. ## Setting things up You can use Supabase Cloud for a quick start. Supabase Cloud will handle setting up the database, scaling, and all things you need in the Cloud in a single click. ![Supabase landing page](/blog/elysia-supabase/supabase-web.webp) When creating a project, you should see something like this. Fill in all the details you need, and if you’re in Asia, Supabase has servers in Singapore and Tokyo. ##### (sometimes this is a tie-breaker for developers living in Asia because of latency) ![Creating new Supabase project](/blog/elysia-supabase/supabase-create-project.webp) After creating a project, you should be greeted with a welcome screen where you can copy the project URL and service role. Both are used to indicate which Supabase project you’re using in your project. And if you missed the welcome page, you can navigate to **Settings > API** and copy **Project URL** and **Project API keys**. ![Supabase Config Page](/blog/elysia-supabase/supabase-config.webp) Now in your command line, you can start creating the Elysia project by running: Terminal window ```bash bun create elysia elysia-supabase ``` The last argument is our folder name for Bun to create, feel free to change the name to anything you like. Now, **cd** into our folder, as we are going to use a newer feature in Elysia 0.3 (RC), we need to install the Elysia RC channel first, and let’s grab a cookie plugin and Supabase client that we are going to use later here too. Terminal window ```bash bun add elysia@rc @elysiajs/cookie@rc @supabase/supabase-js ``` Let’s create a **.env** file to load our Supabase service role as a secret. .env ```bash supabase_url=https://********************.supabase.co supabase_service_role=**** **** **** **** ``` You don’t have to install any plugin to load the env file as Bun loads the **.env** file by default. Now let’s open our project in our favorite IDE, and create a file inside `src/libs/supabase.ts`. src/libs/supabase.ts ```ts import { createClient } from '@supabase/supabase-js' const { supabase_url, supabase_service_role } = process.env export const supabase = createClient(supabase_url!, supabase_service_role!) ``` And that’s it! That’s all you need to set up Supabase and an Elysia project. Now let’s dive into implementation! ## Authentication Now let’s create authentication routes separated from the main file. Inside `src/modules/authen.ts`, let’s create an outline for our routes first. src/modules/authen.ts ```ts import { Elysia } from 'elysia' const authen = (app: Elysia) => app.group('/auth', (app) => app .post('/sign-up', () => { return 'This route is expected to sign up a user' }) .post('/sign-in', () => { return 'This route is expected to sign in a user' }) ) ``` And now, let’s apply Supabase to authenticate our user. src/modules/authen.ts ```ts import { Elysia } from 'elysia' import { supabase } from '../../libs' const authen = (app: Elysia) => app.group('/auth', (app) => app .post('/sign-up', async ({ body }) => { const { data, error } = await supabase.auth.signUp(body) if (error) return error return data.user return 'This route is expected to sign up a user' }) .post('/sign-in', async ({ body }) => { const { data, error } = await supabase.auth.signInWithPassword( body ) if (error) return error return data.user return 'This route is expected to sign in a user' }) ) ``` And- done! That’s all it needs to create **sign-in** and **sign-up** routes for our user. But we have a little problem here, you see, our route can accept **any** body and put it into a Supabase parameter, even an invalid one. So, to make sure that we put the correct data, we can define a schema for our body. src/modules/authen.ts ```ts import { Elysia, t } from 'elysia' import { supabase } from '../../libs' const authen = (app: Elysia) => app.group('/auth', (app) => app .post( '/sign-up', async ({ body }) => { const { data, error } = await supabase.auth.signUp(body) if (error) return error return data.user }, { schema: { body: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) } } ) .post( '/sign-in', async ({ body }) => { const { data, error } = await supabase.auth.signInWithPassword(body) if (error) return error return data.user }, { schema: { body: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) } } ) ) ``` And now we declare a schema in both **sign-in** and **sign-up**, Elysia is going to make sure that an incoming body is going to have the same form as we declare, preventing an invalid argument from passing into `supabase.auth`. Elysia also understands the schema, so instead of declaring TypeScript’s type separately, Elysia types the `body` automatically as the schema you defined. So if you accidentally create a breaking change in the future, Elysia is going to warn you about the data type. The code we have is great. It does the job that we expected, but we can step it up a little bit further. You see, both **sign-in** and **sign-up** accept the same shape of data, in the future, you might also find yourself duplicating a long schema in multiple routes. We can fix that by telling Elysia to memorize our schema, then we can use it by telling Elysia the name of the schema we want to use. src/modules/authen.ts ```ts import { Elysia, t } from 'elysia' import { supabase } from '../../libs' const authen = (app: Elysia) => app.group('/auth', (app) => app .setModel({ sign: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) }) .post( '/sign-up', async ({ body }) => { const { data, error } = await supabase.auth.signUp(body) if (error) return error return data.user }, { schema: { body: 'sign', body: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) } } ) .post( '/sign-in', async ({ body }) => { const { data, error } = await supabase.auth.signInWithPassword(body) if (error) return error return data.user }, { schema: { body: 'sign', body: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) } } ) ) ``` Great! We have just used name reference on our route! Tip If you find yourself with a long schema, you can declare it in a separate file and re-use it in any Elysia route to put the focus back on business logic instead. ## Storing user session Great, now the last thing we need to do to complete the authentication system is to store the user session. After a user is signed in, the tokens are known as `access_token` and `refresh_token` in Supabase. access\_token is a short-lived JWT access token. It is used to authenticate a user in a short amount of time. refresh\_token is a one-time-used never-expired token to renew access\_token. So as long as we have this token, we can create a new access token to extend our user session. We can store both values inside a cookie. Now, some might not like the idea of storing the access token inside a cookie and might use Bearer instead. But for simplicity, we are going to use a cookie here. Tip We can set a cookie as **HttpOnly** to prevent XSS, **Secure**, **Same-Site**, and also encrypt a cookie to prevent a man-in-the-middle attack. src/modules/authen.ts ```ts import { Elysia, t } from 'elysia' import { cookie } from '@elysiajs/cookie' import { supabase } from '../../libs' const authen = (app: Elysia) => app.group('/auth', (app) => app .use( cookie({ httpOnly: true, // If you need cookie to deliver via https only // secure: true, // // If you need a cookie to be available for same-site only // sameSite: "strict", // // If you want to encrypt a cookie // signed: true, // secret: process.env.COOKIE_SECRET, }) ) .setModel({ sign: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) }) // rest of the code ) ``` And— That’s all it takes to create **sign-in** and **sign-up** routes for Elysia and Supabase! ![Using Rest Client to sign in](/blog/elysia-supabase/lagrange-sign-in.webp) ## Refreshing a token Now, as mentioned, access\_token is short-lived, and we might need to renew the token now and then. Luckily, we can do that with a one-liner from Supabase. src/modules/authen.ts ```ts import { Elysia, t } from 'elysia' import { supabase } from '../../libs' const authen = (app: Elysia) => app.group('/auth', (app) => app .setModel({ sign: t.Object({ email: t.String({ format: 'email' }), password: t.String({ minLength: 8 }) }) }) .post( '/sign-up', async ({ body }) => { const { data, error } = await supabase.auth.signUp(body) if (error) return error return data.user }, { schema: { body: 'sign' } } ) .post( '/sign-in', async ({ body }) => { const { data, error } = await supabase.auth.signInWithPassword(body) if (error) return error return data.user }, { schema: { body: 'sign' } } ) .get( '/refresh', async ({ setCookie, cookie: { refresh_token } }) => { const { data, error } = await supabase.auth.refreshSession({ refresh_token }) if (error) return error setCookie('refresh_token', data.session!.refresh_token) return data.user } ) ) ``` Finally, add routes to the main server. ```ts import { Elysia, t } from 'elysia' import { auth } from './modules' const app = new Elysia() .use(auth) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` And that’s it! ## Authorization route We have just implemented user authentication which is fun and games, but now you might find yourself in need of authorization for each route, and duplicating the same code to check for cookies all over the place. Luckily, we can re-use the function in Elysia. Let’s paint the example by saying that we might want a user to create a simple blog post that can have the database schema as the following: Inside the Supabase console, we are going to create a Postgres table named ‘post’ as the following: ![Creating table using Supabase UI, in the public table with the name of 'post', and columns of 'id' with type of 'int8' as a primary value, 'created\_at' with type of 'timestamp' with default value of 'now()', 'user\_id' linked to Supabase's user schema linked as 'user.id', and 'post' with type of 'text'](/blog/elysia-supabase/supabase-create-table.webp) **user\_id** is linked to Supabase’s generated **auth** table linked as **user.id**, using this relation, we can create row-level security to only allow the owner of the post to modify the data. ![Linking the 'user\_id' field with Supabase's user schema as 'user.id'](/blog/elysia-supabase/supabase-create-table-link.webp) Now, let’s create a new separated Elysia route in another folder to separate the code from the auth route, inside `src/modules/post/index.ts`. src/modules/post/index.ts ```ts import { Elysia, t } from 'elysia' import { supabase } from '../../libs' export const post = (app: Elysia) => app.group('/post', (app) => app.put( '/create', async ({ body }) => { const { data, error } = await supabase .from('post') .insert({ // Add user_id somehow // user_id: userId, ...body }) .select('id') if (error) throw error return data[0] }, { schema: { body: t.Object({ detail: t.String() }) } } ) ) ``` Now, this route can accept the body and put it into the database, the only thing we are left to do is handle authorization and extract the `user_id`. Luckily we can do that easily, thanks to Supabase, and our cookies. ```ts import { Elysia, t } from 'elysia' import { cookie } from '@elysiajs/cookie' import { supabase } from '../../libs' export const post = (app: Elysia) => app.group('/post', (app) => app.put( '/create', async ({ body }) => { let userId: string const { data, error } = await supabase.auth.getUser( access_token ) if(error) { const { data, error } = await supabase.auth.refreshSession({ refresh_token }) if (error) throw error userId = data.user!.id } const { data, error } = await supabase .from('post') .insert({ // Add user_id somehow // user_id: userId, ...body }) .select('id') if (error) throw error return data[0] }, { schema: { body: t.Object({ detail: t.String() }) } } ) ) ``` Great! Now we can extract `user_id` from our cookie using **supabase.auth.getUser**. ## Derive Our code works fine for now, but let’s paint a little picture. Let’s say you have so many routes that require authorization like this, requiring you to extract the `userId`, it means that you will have a lot of duplicated code here, right? Luckily, Elysia is specially designed to tackle this problem. *** In Elysia, we have something called a **scope**. Imagine it’s like a **closure** where a variable can only be used inside a scope, or ownership if you’re from Rust. Any life-cycle declared in scope like **group**, **guard** is going to be only available in scope. This means that you can declare a specific life cycle to specific routes inside the scope. For example, a scope of routes that need authorization while others do not. So, instead of reusing all that code, we defined it once and applied it to all the routes you need. *** Now, let’s move this retrieving **user\_id** into a plugin and apply it to all routes in the scope. Let’s put this plugin inside `src/libs/authen.ts`. ```ts import { Elysia } from 'elysia' import { cookie } from '@elysiajs/cookie' import { supabase } from './supabase' export const authen = (app: Elysia) => app .use(cookie()) .derive( async ({ setCookie, cookie: { access_token, refresh_token } }) => { const { data, error } = await supabase.auth.getUser( access_token ) if (data.user) return { userId: data.user.id } const { data: refreshed, error: refreshError } = await supabase.auth.refreshSession({ refresh_token }) if (refreshError) throw error return { userId: refreshed.user!.id } } ) ``` This code attempts to extract userId and add `userId` to `Context` of the route. Otherwise, it will throw an error and skip the handler, preventing an invalid error from being put into our business logic, aka **supabase.from.insert**. Tip We can also use **onBeforeHandle** to create a custom validation before entering the main handler too. **.derive** also does the same but anything returned from **derived** will be added to **Context** while **onBeforeHandle** doesn’t. Technically, **derive** uses **transform** under the hood. And with a single line, we apply all routes inside the scope into authorized-only routes, with type-safe access to **userId**. ```ts import { Elysia, t } from 'elysia' import { authen, supabase } from '../../libs' export const post = (app: Elysia) => app.group('/post', (app) => app .use(authen) .put( '/create', async ({ body, userId }) => { let userId: string const { data, error } = await supabase.auth.getUser( access_token ) if(error) { const { data, error } = await supabase.auth.refreshSession({ refresh_token }) if (error) throw error userId = data.user!.id } const { data, error } = await supabase .from('post') .insert({ user_id: userId, ...body }) .select('id') if (error) throw error return data[0] }, { schema: { body: t.Object({ detail: t.String() }) } } ) ) ``` Great right? We don’t even see that we handled the authorization by looking at the code like magic. Putting our focus back on our core business logic instead. ![Using Rest Client to create post](/blog/elysia-supabase/lagrange-create-post.webp) ## Non-authorized scope Now let’s create one more route to fetch the post from the database. ```ts import { Elysia, t } from 'elysia' import { authen, supabase } from '../../libs' export const post = (app: Elysia) => app.group('/post', (app) => app .get('/:id', async ({ params: { id } }) => { const { data, error } = await supabase .from('post') .select() .eq('id', id) if (error) return error return { success: !!data[0], data: data[0] ?? null } }) .use(authen) .put( '/create', async ({ body, userId }) => { const { data, error } = await supabase .from('post') .insert({ // Add user_id somehow // user_id: userId, ...body }) .select('id') if (error) throw error return data[0] }, { schema: { body: t.Object({ detail: t.String() }) } } ) ) ``` We are using success to indicate if the post exists or not. ![Using Rest Client to get post by id](/blog/elysia-supabase/lagrange-get-post-success.webp) If not, we are going to return `success: false` and `data: null` instead. ![Using Rest Client to get post by id but failed](/blog/elysia-supabase/lagrange-get-post-failed.webp) As we mentioned before, the `.use(authen)` is applied to the scope **but** only to the routes declared after itself, which means that anything before isn’t affected, and what comes after is now an authorized-only route. And one last thing, don’t forget to add routes to the main server. ```ts import { Elysia, t } from 'elysia' import { auth, post } from './modules' const app = new Elysia() .use(auth) .use(post) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` ## Bonus: Documentation As a bonus, after all of what we have created, instead of telling exactly route by route, we can create documentation for our frontend devs in 1 line. With the Swagger plugin, we can install: Terminal window ```bash bun add @elysiajs/swagger@rc ``` And then just add the plugin: ```ts import { Elysia, t } from 'elysia' import { swagger } from '@elysiajs/swagger' import { auth, post } from './modules' const app = new Elysia() .use(swagger()) .use(auth) .use(post) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Tada 🎉 We got well-defined documentation for our API. ![Swagger documentation generated by Elysia](/blog/elysia-supabase/elysia-swagger.webp) And if anything more, you don’t have to worry that you might forget a specification of OpenAPI Schema 3.0, we have auto-completion and type-safety too. We can define our route detail with `schema.detail` that also follows OpenAPI Schema 3.0, so you can properly create documentation. ![Using auto-completion with \`schema.detail\`](/blog/elysia-supabase/swagger-auto-complete.webp) ## What’s next For the next step, we encourage you to try out and explore more with the [code we have just written in this article](https://github.com/saltyaom/elysia-supabase-example) and try adding an image-uploading post, to explore both Supabase and Elysia ecosystems. As we can see, it’s super easy to create a production-ready web server with Supabase, many things are just one-liners and handy for rapid development. Especially when paired with Elysia, you get excellent Developer Experience, declarative schema as a single source of truth, and a very well-thought-out design choice for creating an API, high-performance server while using TypeScript, and as a bonus, we can create documentation in just one line. Elysia is on a journey for creating a Bun-first web framework with new technology, and a new approach. If you’re interested in Elysia, feel free to check out our [Discord server](https://discord.gg/eaFJ2KDJck) or see [Elysia on GitHub](https://github.com/elysiajs/elysia). Also, you might want to check out [Elysia Eden](/eden), a fully type-safe, no-code-gen fetch client like tRPC for Elysia server. --- Source: [blog/elysia-v-encore/index.md](blog/elysia-v-encore/index.md) # 2x faster than Encore - 1 year later On Jun 17 2024, Encore published a blog post, [9x faster than Express.js, 3x faster than ElysiaJS & Hono](https://encore.dev/blog/event-loops) claiming that Encore was faster than Elysia & Hono by 3x. Today, after 1.5 years of development, Elysia is now 2x faster than Encore in the same benchmark. ## A little introduction EncoreTS is a framework powered by a *multi-threaded Rust runtime* with bindings for Node.js. It leverages Rust’s performance and safety features to build high-performance web applications. Encore claims to be faster than Elysia & Hono by utilizing Rust for performance-critical tasks, while still providing a familiar JavaScript/TypeScript interface for developers. ![Encore benchmark on EncoreTS homepage](/blog/elysia-v-encore/encore-benchmark.webp) > Benchmark on EncoreTS homepage 1.5 years later, we revisited the benchmark to see how Elysia performs against Encore. ## Revisiting the benchmark The benchmark is publicly available on [Encore’s GitHub](https://github.com/encoredev/ts-benchmarks). [We forked the repository](https://github.com/saltyaom/encore-ts-benchmarks) to run the benchmark with the latest version of Elysia. After some inspection, we noticed that the original benchmark for Elysia was not optimized for production. We made the following changes to ensure a fair comparison. Here are some of the changes we made: 1. Add `bun compile` to Elysia script to optimize for production 2. Update Elysia bare request to use static resources 3. Due to machine specifications, we updated oha concurrency from `150` to `450` to scale the upper limit Lastly, we updated all the necessary dependencies to their latest versions. - Encore: 1.5.17 - Rust: 1.91.1 - Elysia: 1.4.16 - Bun: 1.3.2 Here’s the machine specification we used for the benchmark: - Benchmark Date: 14 Nov 2025 - Machine specification: Intel i7-13700K, DDR5 32GB 5600MHz - OS: Debian 11 (bullseye) on WSL - 5.15.167.4-microsoft-standard-WSL2 ## Benchmark results After running the benchmark, we obtained the following results. ![Benchmark result](/blog/elysia-v-encore/benchmark-result.webp) > Benchmark result: Elysia is 2x faster than Encore | Framework | Without Validation | With Validation | | --------- | ------------------ | --------------- | | Encore | 139,033 | 95854 | | Elysia | 293,991 | 223,924 | Using the original benchmark, Elysia outperforms Encore in all categories, achieving double the requests per second in all tests. Here’s a [video-proof step-by-step](https://x.com/saltyAom/status/1989218344969580838) on how to run the benchmark. ## How did we achieve this? In the original benchmark, the Elysia version was 1.1.16. Since then, we have made significant improvements to Elysia’s performance through various optimizations and enhancements. But to summarize, in a single year, both Elysia and Bun have made significant performance improvements. ### Exact Mirror In the original Encore benchmark, Elysia performs data normalization on every request, which adds significant overhead to the request processing time. The bottleneck is not in data validation itself, but due to the normalization process that involves dynamic data mutation. In [Elysia 1.3](/blog/elysia-13/#exact-mirror), we introduce [Exact Mirror](https://github.com/elysiajs/exact-mirror) to accelerate data normalization using JIT compilation instead of dynamic data mutation. This improves the performance of data normalization significantly, up to **\~30x faster for medium size payloads**. ![Exact Mirror run on medium size payload in 29.46x and 31.6x in order](/blog/elysia-13/exact-mirror-large.webp) > Exact Mirror run on medium size payload resulting in 30x faster This significantly improves validation performance in Elysia. ### General JIT optimizations Between Elysia 1.1 (upstream) and 1.4 (current), we have made several JIT optimizations to improve the overall performance of Elysia especially with Sucrose, our *JIT compiler* for Elysia. These optimizations include: - Constant folding, inlining lifecycle events - Reducing validation, and coercion overhead - Minimizing the overhead of middleware and plugins - Improving the efficiency of internal data structures - Reducing memory allocations during request processing - Various other micro-optimizations In Bun 1.2.3, Bun also offers a built-in routing in native code for better performance. In Elysia 1.3, we leverage Bun’s native routing when possible to improve routing performance. ### Bun compile In the original benchmark, Elysia was not compiled for production. Using `bun compile` optimizes the Elysia application for production, resulting in significant performance improvements, and reduced memory usage. ## Conclusion Through constant improvement, Elysia has optimized its performance significantly over the past 1.5 years, resulting in a framework that is now 2x faster than Encore in the same benchmark. Benchmarks are not the only factor to consider when choosing a framework. Developer experience, ecosystem, and community support are also important factors to consider. We have detailed our steps on how to run the benchmark, and recommend you run [the benchmark](https://github.com/saltyaom/encore-ts-benchmarks) on your own machine for the most accurate results for your use case. --- Source: [blog/openapi-type-gen/index.md](blog/openapi-type-gen/index.md) # Introducing OpenAPI Type Gen for Elysia API documentation plays a crucial role in the development of an API. Contacting teams, vendors, or third-party services often requires a well-documented API to ensure smooth integration and collaboration. Most frameworks today leave the burden of manual OpenAPI annotation on developers. This can be time-consuming, error-prone, and difficult to maintain as the API evolves. ### But Elysia takes OpenAPI seriously We believe that API documentation should be effortless and automatic, allowing developers to focus on building great APIs without worrying about the documentation. That’s why we built around OpenAPI from the ground up with Elysia. - We make sure that schema can be used for data validation, type inference, and OpenAPI annotation all from a single source of truth. - We provide an integration with Standard Schema (Zod, Valibot, etc.), and turn it into OpenAPI documentation when possible. - We have a 1-liner OpenAPI plugin that adds a beautiful UI to interact with your API with Scalar. ![Scalar Preview](/blog/openapi-type-gen/scalar-preview-light.webp) > Elysia running with Scalar UI from Elysia OpenAPI plugin from 1 line But even with an already exceptional experience, we want to push it even further. Today, we are excited to announce the release of **OpenAPI Type Gen** to generate OpenAPI documentation from your Elysia code without any manual annotation. ## OpenAPI Type Gen We dream of a world where you just write your code, and the documentation is created automatically, and accurately without any manual annotation at all. The closest thing we have is with **Python’s FastAPI** that can generate OpenAPI documentation from pydantic models. But it is limited to only pydantic models, and cannot be used with other libraries or types. Elysia Type Gen brings a similar experience to TypeScript, without that limitation, allowing you to turn **any TypeScript type** into OpenAPI documentation automatically **from any library** not limited to Elysia. ![Elysia Type Gen](/blog/openapi-type-gen/type-gen.webp) > Elysia references the body type in a response schema automatically, listing all the possible response status codes without any manual schema annotation at all. It takes **1 line** of code to generate OpenAPI documentation from your Elysia code directly **from TypeScript type** without any annotation at all. ### This is truly ground-breaking For the first time ever, you can now document your API automatically without any manual annotation for real. It just works, out of the box with any library. It works with an existing Elysia codebase and schema definitions without any breaking changes or additional configuration like type transformer at all (eg. Typia). Type Gen co-exists with existing schema definitions by prioritizing the existing schema definition first before falling back to inferring from types. ![Using Drizzle with type gen Elysia Type Gen](/blog/openapi-type-gen/drizzle-typegen.webp) > Returning Drizzle query from Elysia route handler will be automatically inferred into OpenAPI schema. It’s also compatible with any TypeScript library even complex types from modern libraries like **Drizzle**, and **Prisma** out of the box. ### Type Soundness OpenAPI Type Gen also supports complex scenarios like multiple statuses from lifecycle/macro that overlap with each other, separated by multiple response status codes. Each status code can return multiple values, which will be handled by Elysia as a union type for every possible response under the same status code, listing all the possible return values accurately. ![Union response](/blog/openapi-type-gen/union.webp) > Listing multiple response status codes from a union type automatically. This is something profound, and can hardly be replicated by any other framework. ## Adopt OpenAPI Type Gen To add OpenAPI Type Gen to your codebase, simply: 1. export an Elysia instance 2. provide root Elysia file path (if not provided, Elysia will use `src/index.ts`) ```ts import { Elysia } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use( openapi({ references: fromTypes() }) ) ``` Elysia Type Gen will analyze your Elysia instance and generate the OpenAPI documentation automatically on the fly, no build step required. The documentation for OpenAPI Type Gen can be found at [Patterns: OpenAPI](/patterns/openapi#openapi-from-types). *** ### We believe that this feature is truly unique to Elysia While most web frameworks require a lot of effort and manual annotation to create API documentation, Elysia can write API documentation for you, and there are no other frameworks that come close to this experience. This is only possible thanks to **Elysia’s spectacular support for end-to-end type safety**. We are excited to see how it will help you to create and maintain high-quality API documentation with minimal effort with Elysia. You can try it out today by updating `@elysia/openapi` to the latest version or experimenting with our example setup from [GitHub repository](https://github.com/saltyaom/elysia-typegen-example). --- Source: [blog/with-prisma/index.md](blog/with-prisma/index.md) # Accelerate your next Prisma server with Elysia Prisma is a renowned TypeScript ORM for its developer experience. It has a type-safe and intuitive API that allows us to interact with databases using a fluent and natural syntax. Writing a database query is as simple as writing a shape of data with TypeScript auto-completion, then Prisma takes care of the rest by generating efficient SQL queries and handling database connections in the background. One of the standout features of Prisma is its seamless integration with popular databases like: - PostgreSQL - MySQL - SQLite - SQL Server - MongoDB - CockroachDB So we have the flexibility to choose the database that best suits our project’s needs, without compromising on the power and performance that Prisma brings to the table. This means you can focus on what really matters: building your application logic. Prisma is one of the inspirations for Elysia. Its declarative API and fluent developer experience are an absolute joy to work with. Now we can bring the long-awaited imagination to life with [the release of Bun 0.6.7](https://bun.sh/blog/bun-v0.6.7). Bun now supports Prisma out of the box. ## Elysia Elysia is one of the answers that would come to mind when you ask what framework you should use with Bun. Although you can use Express with Bun, Elysia is built specifically for Bun. Elysia can outperform Express by nearly \~19x, supercharged with a declarative API for creating a unified type system and end-to-end type safety. Elysia is also known for having a fluent Developer Experience especially as Elysia is designed to be used with Prisma since its early days. With Elysia’s strict-type validation, we can integrate Elysia and Prisma with ease using declarative API. In other words, Elysia will ensure that runtime types and TypeScript’s types will always be in sync, making it behave like a Type Strict Language where you can completely trust the type system, look ahead for any type errors, and more easily debug errors relating to types. ## Setting up All we need to get started is to run `bun create` to set up an Elysia server. Terminal window ```bash bun create elysia elysia-prisma ``` `elysia-prisma` is our project name (folder destination). Feel free to change the name to anything you like. Now in our folder, let’s install Prisma CLI as a dev dependency. ```ts bun add -d prisma ``` Then we can set up a prisma project with `prisma init`. ```ts bunx prisma init ``` `bunx` is a bun command equivalent to `npx`, which allows us to execute package bin. Once set up, we can see that Prisma will update `.env` file and generate a folder named **prisma** with **schema.prisma** as a file inside. **schema.prisma** is a database model defined with Prisma’s schema language. Let’s update our **schema.prisma** file like this for a demonstration: ```ts generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) username String @unique password String } ``` This tells Prisma that we want to create a table named **User** with these columns: | Column | Type | Constraint | | -------- | ------ | ------------------------------- | | id | Number | Primary Key with auto increment | | username | String | Unique | | password | String | - | Prisma will then read the schema, and DATABASE\_URL from an `.env` file, so before syncing our database we need to define the `DATABASE_URL` first. Since we don’t have any database running, we can set up one using docker: Terminal window ```bash docker run -p 5432:5432 -e POSTGRES_PASSWORD=12345678 -d postgres ``` Now go into the `.env` file at the root of our project then edit: ```plaintext DATABASE_URL="postgresql://postgres:12345678@localhost:5432/db?schema=public" ``` Then we can run `prisma migrate` to sync our database with Prisma schema: Terminal window ```bash bunx prisma migrate dev --name init ``` Prisma then generates strongly-typed Prisma Client code based on our schema. This means we get autocomplete and type checking in our code editor, catching potential errors at compile time rather than runtime. ## Into the code In our **src/index.ts**, let’s update our Elysia server to create a simple user sign-up endpoint. ```ts import { Elysia } from 'elysia' import { PrismaClient } from '@prisma/client' const db = new PrismaClient() const app = new Elysia() .post( '/sign-up', async ({ body }) => db.user.create({ data: body }) ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` We have just created a simple endpoint to insert a new user into our database using Elysia and Prisma. Tip **It’s important** that when returning a Prisma function, you should always mark a callback function as async. As a Prisma function doesn’t return a native Promise, Elysia cannot dynamically handle the custom promise type. But with Static Code Analysis, by marking the callback function as async, Elysia will try to await the return type of a function, thus allowing us to map Prisma results. Now the problem is that the body could be anything, not limited to our expected defined type. We can improve that by using Elysia’s type system. ```ts import { Elysia, t } from 'elysia' import { PrismaClient } from '@prisma/client' const db = new PrismaClient() const app = new Elysia() .post( '/sign-up', async ({ body }) => db.user.create({ data: body }), { body: t.Object({ username: t.String(), password: t.String({ minLength: 8 }) }) } ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` This tells Elysia to validate the body of an incoming request to match the shape, and update TypeScript’s type of the `body` inside the callback to match the exact same type: ```ts // 'body' is now typed as the following: { username: string password: string } ``` This means that if the shape doesn’t interoperate with the database table, it would warn you immediately. This is effective when you need to edit a table or perform a migration. Elysia can log the error immediately line by line because of a type conflict before reaching production. ## Error Handling Since our `username` field is unique, sometimes Prisma can throw an error because there could be an accidental duplication of `username` when trying to sign up like this: ```ts Invalid `prisma.user.create()` invocation: Unique constraint failed on the fields: (`username`) ``` Elysia’s default error handler can handle the case automatically but we can improve that by specifying a custom error using Elysia’s local `onError` hook: ```ts import { Elysia, t } from 'elysia' import { PrismaClient } from '@prisma/client' const db = new PrismaClient() const app = new Elysia() .post( '/', async ({ body }) => db.user.create({ data: body }), { error({ code }) { switch (code) { // Prisma P2002: "Unique constraint failed on the {constraint}" case 'P2002': return { error: 'Username must be unique' } } }, body: t.Object({ username: t.String(), password: t.String({ minLength: 8 }) }) } ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Using the `error` hook, any error thrown inside a callback will be propagated to the `error` hook, allowing us to define a custom error handler. According to [Prisma documentation](https://www.prisma.io/docs/reference/api-reference/error-reference#p2002), error code ‘P2002’ means that the query violates a unique constraint. Since this table only has a single `username` field that is unique, we can infer that the error is caused because username is not unique, so we return a custom error message of: ```ts { error: 'Username must be unique' } ``` This will return a JSON equivalent of our custom error message when a unique constraint fails. This allows us to seamlessly define any custom error from a Prisma error. ## Bonus: Reference Schema When our server grows complex and types become more redundant and turn into boilerplate, inlining an Elysia type can be improved by using **Reference Schema**. To put it simply, we can name our schema and reference the type by using the name. ```ts import { Elysia, t } from 'elysia' import { PrismaClient } from '@prisma/client' const db = new PrismaClient() const app = new Elysia() .model({ 'user.sign': t.Object({ username: t.String(), password: t.String({ minLength: 8 }) }) }) .post( '/', async ({ body }) => db.user.create({ data: body }), { error({ code }) { switch (code) { // Prisma P2002: "Unique constraint failed on the {constraint}" case 'P2002': return { error: 'Username must be unique' } } }, body: 'user.sign', body: t.Object({ username: t.String(), password: t.String({ minLength: 8 }) }) } ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` This works the same as using an inline schema, but instead you define it once and refer to the schema by name to remove redundant validation code. TypeScript and validation code will work as expected. ## Bonus: Documentation As a bonus, Elysia’s type system is also OpenAPI Schema 3.0 compliant, which means that it can generate documentation with tools that support OpenAPI Schema like Swagger. We can use Elysia Swagger plugin to generate API documentation in a single line. Terminal window ```bash bun add @elysiajs/swagger ``` And then just add the plugin: ```ts import { Elysia, t } from 'elysia' import { PrismaClient } from '@prisma/client' import { swagger } from '@elysiajs/swagger' const db = new PrismaClient() const app = new Elysia() .use(swagger()) .post( '/', async ({ body }) => db.user.create({ data: body, select: { id: true, username: true } }), { error({ code }) { switch (code) { // Prisma P2002: "Unique constraint failed on the {constraint}" case 'P2002': return { error: 'Username must be unique' } } }, body: t.Object({ username: t.String(), password: t.String({ minLength: 8 }) }), response: t.Object({ id: t.Number(), username: t.String() }) } ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` And that’s all it takes to create well-defined documentation for our API. ![Swagger documentation generated by Elysia](/blog/with-prisma/swagger.webp) And thanks to defining a strict type for the documentation, we found that we accidentally return the `password` field from our API, which is not a good idea as it returns private information. Thanks to Elysia’s type system, we define that the response shouldn’t contain `password`, which automatically warns us that our Prisma query is returning a password, allowing us to fix that ahead of time. And if anything more, we don’t have to worry that we might forget a specification of OpenAPI Schema 3.0, as we have auto-completion and type safety too. We can define our route detail with `detail` that also follows OpenAPI Schema 3.0, so we can properly create documentation effortlessly. ## What’s next With the support of Prisma with Bun and Elysia, we are entering a new era of a new level of developer experience. With Prisma we can accelerate our interaction with databases, while Elysia accelerates our creation of backend web servers in terms of both developer experience and performance. > It’s an absolute joy to work with. Elysia is on a journey to create a new standard for a better developer experience with Bun for high performance TypeScript servers that can match the performance of Go and Rust. If you’re looking for a place to start learning about Bun, consider taking a look at what Elysia can offer especially with an [end-to-end type safety](/eden) like tRPC but built on REST standard without any code generation. If you’re interested in Elysia, feel free to check out our [Discord server](https://discord.gg/eaFJ2KDJck) or see [Elysia on GitHub](https://github.com/elysiajs/elysia). --- Source: [design-system/index.md](design-system/index.md) # Design system Every component in the Spica system, live and interactive — poke at them. They’re the real thing. ## Button Create workerCreate workerCreate worker ## IconButton ## Tabs ## Waveform timing How dependencies become available Play `auth`Provides `Auth`Class `user`Model `.use(auth)` `main`No dependency `/profile`Auth missing `Auth.getProfile()` 4s 1/ 4 `main` handles `/profile` and reads `Auth` from its context. Nothing provides `Auth` yet. **Decorate. **`auth.decorate('Auth', Auth)` attaches the class to the `auth` instance, and `.model(Auth.models)` registers its `user` model. **Dependency. **`main.use(auth)` declares the dependency. `main` now has `auth` in its tree. **Dependency. **That's all it takes: `Auth` is in `main`'s context and its `user` model is registered on `main`, so `/profile` can call `Auth.getProfile()`. Without `.use(auth)`, it stays missing. ## Checkbox \[x]Email me updates\[ ]Select all ## Select Version1.4.28 ## SelectMenu StatusHealthyhealthy ## Dropdown Actions ## InputGroup SearchSearch docs… ## Tooltip Hover me ## Callout Tip Hooks you add before a route only apply to the routes below it. ## Card Essential ### Routing Map a method and a path to a handler, and Elysia does the rest. ## NumberedCards 1. ### Install Run bun create elysia and you have a server in one command. 2. ### Add a route Chain .get, .post, and friends — types follow you all the way to the client. ## FileTree - src - index.ts - routesNew - types.ts1 kB - test - readme.md2 kB - package.json ## Table Deployments7 rows | \[ ] | Name▲ | Status | Region | p95 ms | | ---- | ----- | ------ | ------ | ------ | | | | | | | | ---- | ----------- | -------- | ---- | --- | | \[ ] | api-gateway | Healthy | iad1 | 42 | | \[ ] | auth-worker | Healthy | sin1 | 68 | | \[ ] | billing | Degraded | fra1 | 214 | | \[ ] | docs-site | Healthy | cdg1 | 19 | 1–4 of 712 --- Source: [eden/fetch/index.md](eden/fetch/index.md) # Eden Fetch A fetch-like alternative to Eden Treaty. With Eden Fetch, you can interact with an Elysia server in a type-safe manner using the Fetch API. *** First export your existing Elysia server type: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/hi', () => 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: t.Object({ id: t.Number(), name: t.String() }) }, ({ body }) => body) .listen(3000) export type App = typeof app ``` Then import the server type, and consume the Elysia API on the client: ```ts import { edenFetch } from '@elysia/eden' import type { App } from './server' const fetch = edenFetch('http://localhost:3000') // response type: 'Hi Elysia' const { data: pong } = await fetch('/hi', {}) // response type: 1895 const { data: id } = await fetch('/id/:id', { params: { id: '1895' } }) // response type: { id: 1895, name: 'Skadi' } const { data: nendoroid } = await fetch('/mirror', { method: 'POST', body: { id: 1895, name: 'Skadi' } }) ``` ## Error handling You can handle errors the same way as with Eden Treaty: ```ts import { edenFetch } from '@elysia/eden' import type { App } from './server' const fetch = edenFetch('http://localhost:3000') // response type: { id: 1895, name: 'Skadi' } const { data: nendoroid, error } = await fetch('/mirror', { method: 'POST', body: { id: 1895, name: 'Skadi' } }) if(error) { switch(error.status) { case 400: case 401: throw error.value break case 500: case 502: throw error.value break default: throw error.value break } } const { id, name } = nendoroid ``` ## When should I use Eden Fetch over Eden Treaty Choose the request syntax that fits your project. Neither client is universally faster to type-check. The preference is based on your agreement with your team; however, we recommend using [Eden Treaty](/eden/treaty/overview) instead. For a large API, such as **more than 500 routes** consumed by one frontend, compare TypeScript checking and editor responsiveness in your own project. Eden Fetch can be worth trying when Treaty inference becomes costly; route count alone is not a performance guarantee. --- Source: [eden/index.md](eden/index.md) # End-to-end type safety Imagine you have a toy train set. Each piece of the train track has to fit perfectly with the next one, like puzzle pieces. End-to-end type safety is like making sure all the pieces of the track match up correctly so the train doesn’t fall off or get stuck. For a framework, having end-to-end type safety means that you can connect the client and server in a type-safe manner. Elysia provides end-to-end type safety **without code generation** out of the box with an RPC-like connector, **Eden**. [Download the Eden Treaty demonstration](/eden/eden-treaty.mp4). Other frameworks that support e2e type safety: - tRPC - Remix - SvelteKit - Nuxt - TS-Rest Elysia allows you to change the type on the server, and it will be instantly reflected on the client, helping with auto-completion and type enforcement. ## Eden Eden is an RPC-like client to connect Elysia with **end-to-end type safety** using only TypeScript’s type inference instead of code generation. It shares the server’s types with the client through type-only imports. No generated client needs to be kept in sync. Eden consists of 2 modules: 1. Eden Treaty **(recommended)**: an improved RPC version of [Eden Treaty 1](/eden/treaty/legacy) (edenTreaty) 2. Eden Fetch: A fetch-like client with type safety Below is an overview of each module. ## Eden Treaty (recommended) Eden Treaty is an object-like representation of an Elysia server providing end-to-end type safety and a significantly improved developer experience. With Eden Treaty, we can interact with an Elysia server with full-type support and auto-completion, error handling with type narrowing, and create type-safe unit tests. Example usage of Eden Treaty: **server.ts** ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', 'hi') .get('/users', () => 'Skadi') .put('/nendoroid/:id', { body: t.Object({ name: t.String(), from: t.String() }) }, ({ body }) => body) .get('/nendoroid/:id/name', () => 'Skadi') .listen(3000) export type App = typeof app ``` **index.ts** ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') // Call [GET] at '/' const { data } = await app.get() // Call [PUT] at '/nendoroid/:id' const { data: nendoroid, error } = await app.nendoroid({ id: 1895 }).put({ name: 'Skadi', from: 'Arknights' }) ``` ## Eden Fetch A fetch-like alternative to Eden Treaty for developers who prefer fetch syntax. For this example, export a server with the matching route: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/name/:name', { body: t.Object({ branch: t.String(), type: t.String() }) }, ({ params, body }) => ({ name: params.name, ...body })) .listen(3000) export type App = typeof app ``` ```ts import { edenFetch } from '@elysia/eden' import type { App } from './server' const fetch = edenFetch('http://localhost:3000') const { data } = await fetch('/name/:name', { method: 'POST', params: { name: 'Saori' }, body: { branch: 'Arius', type: 'Striker' } }) ``` Note Unlike Eden Treaty, Eden Fetch doesn’t provide a WebSocket implementation for the Elysia server. --- Source: [eden/installation/index.md](eden/installation/index.md) # Eden installation Start by installing Eden on your frontend: Terminal window ```bash bun add @elysia/eden@next # Need Elysia to infer utility types bun add -d elysia@rc ``` First, export your existing Elysia server type: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', () => 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: t.Object({ id: t.Number(), name: t.String() }) }, ({ body }) => body) .listen(3000) export type App = typeof app ``` Then consume the Elysia API on the client side: **client.ts** client.ts ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const client = treaty('localhost:3000') // response: Hi Elysia const { data: index } = await client.get() // response: 1895 const { data: id } = await client.id({ id: 1895 }).get() // response: { id: 1895, name: 'Skadi' } const { data: nendoroid } = await client.mirror.post({ id: 1895, name: 'Skadi' }) ``` ## Gotcha Sometimes, Eden may not infer types from Elysia correctly. The following are the most common workarounds to fix Eden type inference. ### Type strict Make sure to enable strict mode in **tsconfig.json**. ```json { "compilerOptions": { "strict": true } } ``` ### Mismatched Elysia version Eden depends on the Elysia class to import the Elysia instance and infer types correctly. Make sure that both the client and server have matching Elysia versions. You can check it with the [`npm why`](https://docs.npmjs.com/cli/v10/commands/npm-explain) command: Terminal window ```bash npm why elysia ``` Check that client, server, and plugin dependencies agree on the same Elysia v2 release. The exact output depends on your dependency tree. Look for duplicate Elysia versions, incompatible peer ranges, or a frontend version that differs from the server. ### TypeScript version Elysia uses newer features and syntax of TypeScript to infer types in the most performant way. Features like Const Generics and Template Literals are heavily used. Use a TypeScript version supported by your Elysia v2 release. The local v2 packages use modern TypeScript features; use TypeScript 5.9 or newer for these examples. ### Method chaining To make Eden work, Elysia must use **method chaining** Elysia’s type system is complex. Methods usually introduce a new type to the instance. Using method chaining will help save that new type reference. For example: ```ts import { Elysia } from 'elysia' new Elysia() .state('build', 1) // Store is strictly typed .get('/', ({ store: { build } }) => build) .listen(3000) ``` Using this, **state** now returns a new **Elysia** type, introducing **build** into the store and replacing the current type. Without method chaining, Elysia doesn’t save the new type when introduced, leading to no type inference. ```ts // Expected type error: the earlier call did not retain its new type. import { Elysia } from 'elysia' const app = new Elysia() app.state('build', 1) app.get('/', ({ store: { build } }) => build) app.listen(3000) ``` ### Type definitions If you are using a Bun-specific feature, like `Bun.file` or a similar API, and return it from a handler, you may need to install Bun type definitions for the client as well. Terminal window ```bash bun add -d @types/bun ``` ### Path alias (monorepo) If you are using path aliases in your monorepo, make sure that the frontend is able to resolve the path the same way as the backend. Tip Setting up path aliases in a monorepo can be tricky. You can fork our example template: [Kozeki Template](https://github.com/SaltyAom/kozeki-template) and modify it to your needs. For example, if you have the following path alias for your backend in **tsconfig.json**: ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } } ``` And your backend code is like this: ```ts import { Elysia } from 'elysia' import { a, b } from '@/controllers' const app = new Elysia() .use(a) .use(b) .listen(3000) export type app = typeof app ``` You **must** make sure that your frontend code is able to resolve the same path alias. Otherwise, type inference will be resolved as any. ```ts import { treaty } from '@elysia/eden' import type { app } from '@/index' const client = treaty('localhost:3000') // This should be able to resolve the same module in both frontend and backend, and not `any` import { a, b } from '@/controllers' ``` To fix this, you must make sure that the path alias is resolved to the same file in both the frontend and backend. So, you must change the path alias in the frontend’s **tsconfig.json** (`apps/frontend`) to: ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["../backend/src/*"] } } } ``` If configured correctly, you should be able to resolve the same module in both the frontend and backend. ```ts // This should be able to resolve the same module in both frontend and backend, and not `any` import { a, b } from '@/controllers' ``` #### Namespace We recommend adding a **namespace** prefix for each module in your monorepo to avoid any confusion and conflicts that may occur. ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@frontend/*": ["./apps/frontend/src/*"], "@backend/*": ["./apps/backend/src/*"] } } } ``` Then, you can import the module like this: ```ts // Should work in both frontend and backend and not return `any` import { a, b } from '@backend/controllers' ``` We recommend creating a **single tsconfig.json** that defines a `baseUrl` as the root of your repo, provides a path according to the module location, and creates a **tsconfig.json** for each module that inherits the root **tsconfig.json** which has the path alias. You may find a working example in this [path alias example repo](https://github.com/SaltyAom/elysia-monorepo-path-alias) or [Kozeki Template](https://github.com/SaltyAom/kozeki-template). --- Source: [eden/treaty/config/index.md](eden/treaty/config/index.md) # Config Eden Treaty accepts 2 parameters: - **urlOrInstance** - URL endpoint or Elysia instance - **options** (optional) - Customize fetch behavior ## urlOrInstance Accepts either a URL endpoint as a string or a literal Elysia instance. Eden will change the behavior based on the type as follows: ### URL endpoint (string) If a URL endpoint is passed, Eden Treaty will use `fetch` or `config.fetcher` to create a network request to an Elysia instance. ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const api = treaty('localhost:3000') ``` You may or may not specify a protocol for the URL endpoint. Eden Treaty will prepend a protocol automatically as follows: 1. If a protocol is specified, use the URL directly 2. If the URL identifies localhost or a supported local address, use http 3. Otherwise, use https This also applies to WebSocket for choosing between **ws\://** and **wss\://**. *** ### Elysia instance If an Elysia instance is passed, Eden Treaty will create a `Request` class and pass it to `Elysia.handle` directly without creating a network request. This allows us to interact with the Elysia server directly without request overhead, or the need to start a server. ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/hi', 'Hi Elysia') const api = treaty(app) ``` If an instance is passed, a generic is not needed as Eden Treaty can infer the type from the parameter directly. This pattern is recommended for performing unit tests, or creating a type-safe reverse proxy server or microservices. ## Options The optional 2nd parameter lets Eden Treaty customize fetch behavior, accepting parameters as follows: - [fetch](#fetch) - add default parameters to fetch initialization (RequestInit) - [fetcher](#fetcher) - custom function with the same interface as `fetch` - [headers](#headers) - define default headers - [onRequest](#onrequest) - intercept and modify the fetch request before firing - [onResponse](#onresponse) - intercept and modify fetch’s response - [parseDate](#parsedate) - automatically parse a date string to a Date object - [throwHttpError](#throwhttperror) - automatically throw an error if the response status is not ok (2xx) ## Fetch Set default `RequestInit` options such as credentials or caching. Set headers through `headers`; the current config type excludes `headers` and `method` from `fetch`. The following examples assume `treaty` and your exported `App` type are imported. ```ts import { treaty } from '@elysia/eden' import type { App } from './server' treaty('localhost:3000', { fetch: { credentials: 'include' } }) ``` All parameters that are passed to fetch will be passed to the fetcher, which is equivalent to: ```ts fetch('http://localhost:3000', { credentials: 'include' }) ``` ## Fetcher Provide a custom fetcher function instead of using an environment’s default fetch. ```ts treaty('localhost:3000', { fetcher(url, options) { return fetch(url, options) } }) ``` A custom fetcher must accept the same arguments as `fetch` and return a Web Standard `Response`. Clients such as Axios need an adapter; they cannot be passed directly. ## Headers Provide default request headers with `options.headers`. ```ts treaty('localhost:3000', { headers: { 'X-Custom': 'Griseo' } }) ``` All parameters that are passed to fetch will be passed to the fetcher, which is equivalent to: ```ts fetch('http://localhost:3000', { headers: { 'X-Custom': 'Griseo' } }) ``` Headers may accept the following as parameters: - Object - Function ### Headers object If an object is passed, then it will be passed to fetch directly. ```ts treaty('localhost:3000', { headers: { 'X-Custom': 'Griseo' } }) ``` ### Function You may specify headers as a function to return custom headers based on conditions. ```ts treaty('localhost:3000', { headers(path, options) { if(path.startsWith('/user')) return { authorization: 'Bearer 12345' } } }) ``` You may return an object to append its value to fetch headers. The headers function accepts 2 parameters: - path `string` - path which will be sent to the parameter - note: hostname will be **excluded**, e.g., (/user/griseo) - options `RequestInit`: Parameters passed through the 2nd parameter of fetch ### Array You may define a headers function as an array if multiple conditions are needed. ```ts treaty('localhost:3000', { headers: [ (path, options) => { if(path.startsWith('/user')) return { authorization: 'Bearer 12345' } } ] }) ``` Eden Treaty will **run all functions** even if the value is already returned. ## Headers priority Eden Treaty will prioritize duplicate headers as follows: 1. Inline method - Passed in method function directly 2. headers - Passed in `config.headers` - If `config.headers` is an array, parameters that come after will be prioritized For example: ```ts const api = treaty('localhost:3000', { headers: { authorization: 'Bearer Aponia' } }) api.profile.get({ headers: { authorization: 'Bearer Griseo' } }) ``` This will result in: ```ts fetch('http://localhost:3000', { headers: { authorization: 'Bearer Griseo' } }) ``` If the inline function doesn’t specify headers, then the result will be “**Bearer Aponia**” instead. ## onRequest Intercept and modify the fetch request before firing. You may return an object to append the value to **RequestInit**. ```ts treaty('localhost:3000', { onRequest(path, options) { if(path.startsWith('/user')) return { headers: { authorization: 'Bearer 12345' } } } }) ``` If a value is returned, Eden Treaty will **shallow merge** it into the current `RequestInit`, merging its `headers` with the existing headers. **onRequest** accepts 2 parameters: - path `string` - path which will be sent to the parameter - note: hostname will be **excluded**, e.g., (/user/griseo) - options `RequestInit`: Parameters passed through the 2nd parameter of fetch ### Array You may define an onRequest function as an array if multiple conditions are needed. ```ts treaty('localhost:3000', { onRequest: [ (path, options) => { if(path.startsWith('/user')) return { headers: { authorization: 'Bearer 12345' } } } ] }) ``` Eden Treaty will **run all functions** even if the value is already returned. ## onResponse Intercept fetch’s response. The first non-null return value becomes `data`. ```ts treaty('localhost:3000', { onResponse(response) { if(response.ok && response.headers.get('content-type')?.includes('application/json')) return response.json() } }) ``` **onResponse** accepts 1 parameter: - response `Response` - Web Standard Response normally returned from `fetch` ### Array You may define an onResponse function as an array if multiple conditions are needed. ```ts treaty('localhost:3000', { onResponse: [ (response) => { if(response.ok && response.headers.get('content-type')?.includes('application/json')) return response.json() } ] }) ``` Unlike [headers](#headers) and [onRequest](#onrequest), Eden Treaty will loop through functions until a returned value is found or an error is thrown. The first non-null return value becomes `data`. ## parseDate - default: `true` Automatically parse a date string to a Date object. ```ts treaty('localhost:3000', { parseDate: true }) ``` ## throwHttpError - default: `false` Automatically throw an error if the response status is not ok (2xx). ```ts treaty('localhost:3000', { throwHttpError: true }) ``` By default, Eden will not throw an error and will return `{ error }` instead if the response status is not ok (2xx). You can also supply a predicate receiving an `EdenFetchError`: ```ts treaty('localhost:3000', { throwHttpError: (error) => { return error.status === 418 } }) ``` If `throwHttpError` returns `true`, Eden will throw an error. Otherwise, it will return `{ error }` instead. --- Source: [eden/treaty/legacy/index.md](eden/treaty/legacy/index.md) # Eden Treaty legacy Note This is the legacy Eden Treaty 1 (`edenTreaty`) client API. The server examples use Elysia v2; `$query`, `$fetch`, and dynamic property access belong to this older client interface. For a new project, we recommend starting with [Eden Treaty 2](/eden/treaty/overview) (treaty) instead. Eden Treaty is an object-like representation of an Elysia server. It provides accessors like a normal object with types directly from the server, providing autocomplete and type checks for client calls. *** To use Eden Treaty, first export your existing Elysia server’s type: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', () => 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: t.Object({ id: t.Number(), name: t.String() }) }, ({ body }) => body) .listen(3000) export type App = typeof app ``` Then import the server type, and consume the Elysia API on the client: client.ts ```ts import { edenTreaty } from '@elysia/eden' import type { App } from './server' const app = edenTreaty('http://localhost:3000') // response type: 'Hi Elysia' const { data: pong, error: pongError } = await app.get() // response type: 1895 const { data: id, error: idError } = await app.id['1895'].get() // response type: { id: 1895, name: 'Skadi' } const { data: nendoroid, error } = await app.mirror.post({ id: 1895, name: 'Skadi' }) ``` Tip Eden Treaty is fully type-safe with auto-completion support. ## Anatomy Eden Treaty will transform all existing paths to an object-like representation that can be described as: ```text EdenTreaty.<1>.<2>..({ ...body, $query?: {}, $fetch?: RequestInit }) ``` ### Path Eden will transform `/` into `.` which can be called with a registered `method`, for example: - **/path** -> .path - **/nested/path** -> .nested.path ### Path parameters Path parameters will be mapped automatically by their name in the URL. - **/id/:id** -> .id.`` - eg: .id.hi - eg: .id\[‘123’] Tip If a path doesn’t support path parameters, TypeScript will show an error. ### Query You can append queries to the path with `$query`: ```ts app.get({ $query: { name: 'Eden', code: 'Gold' } }) ``` ### Fetch Eden Treaty is a fetch wrapper. You can add any valid [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) parameters to Eden by passing them to `$fetch`: ```ts app.post({ $fetch: { headers: { 'x-organization': 'MANTIS' } } }) ``` ## Error handling Eden Treaty will return a value of `data` and `error` as a result, both fully typed. ```ts // response type: { id: 1895, name: 'Skadi' } const { data: nendoroid, error } = await app.mirror.post({ id: 1895, name: 'Skadi' }) if(error) { switch(error.status) { case 400: case 401: warnUser(error.value) break case 500: case 502: emergencyCallDev(error.value) break default: reportError(error.value) break } throw error } const { id, name } = nendoroid ``` Both **data** and **error** will be typed as nullable until you can confirm their statuses with a type guard. To put it simply, if the fetch is successful, data will have a value and error will be null, and vice versa. Tip The error is wrapped with an `Error`, and its value returned from the server can be retrieved from `Error.value` ### Error type based on status Both Eden Treaty and Eden Fetch can narrow down an error type by status code, as inferred from the server’s `status()` responses. server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .model({ nendoroid: t.Object({ id: t.Number(), name: t.String() }), error: t.Object({ message: t.String() }) }) .get('/', () => 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: 'nendoroid', response: { 200: 'nendoroid', 400: 'error', 401: 'error' } }, ({ body }) => body) .listen(3000) export type App = typeof app ``` And on the client side: ```ts const { data: nendoroid, error } = await app.mirror.post({ id: 1895, name: 'Skadi' }) if(error) { switch(error.status) { case 400: case 401: // narrow down to type 'error' described in the server warnUser(error.value) break default: // other declared error statuses reportError(error.value) break } throw error } ``` ## WebSocket Eden supports WebSocket using the same API as a normal route. ```ts // Server import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' const app = new Elysia() .use(websocket()) .ws('/chat', { message({ body }) { return body }, body: t.String(), response: t.String() }) .listen(3000) export type App = typeof app ``` To start listening to real-time data, call the `.subscribe` method: ```ts // Client import { edenTreaty } from '@elysia/eden' import type { App } from './server' const app = edenTreaty('http://localhost:3000') const chat = app.chat.subscribe() chat.subscribe((message) => { console.log('got', message) }) chat.on('open', () => { chat.send('hello from client') }) ``` We can use [schema](/integrations/cheat-sheet#schema) to enforce type-safety on WebSockets, just like a normal route. *** `subscribe()` returns an `EdenWS` wrapper with typed message helpers such as `send`, `subscribe`, and `on`. Use `chat.ws` to access the underlying native WebSocket, including APIs not exposed by the wrapper. ## File upload You may pass any of the following to the field to attach a file: - **File** - **FileList** - **Blob** Attaching a file will result in **content-type** being **multipart/form-data** Suppose we have the server as follows: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/image', { body: t.Object({ title: t.String(), image: t.Files(), }) }, ({ body: { image, title } }) => title) .listen(3000) export type App = typeof app ``` We can use the client as follows: client.ts ```ts import { edenTreaty } from '@elysia/eden' import type { App } from './server' export const client = edenTreaty('http://localhost:3000') const id = (id: string) => document.getElementById(id)! as T const { data } = await client.image.post({ title: "Misono Mika", image: id('picture').files!, }) ``` --- Source: [eden/treaty/overview/index.md](eden/treaty/overview/index.md) # Eden Treaty Eden Treaty is an object representation used to interact with a server and features type safety, auto-completion, and error handling. ## Read a route A path becomes properties on the client, followed by its HTTP method. `api.hi.get()` calls `GET /hi` and returns `Hi Elysia`. Here, passing the instance directly keeps the call in the same process. No listening server is needed. ```typescript import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia().get('/hi', 'Hi Elysia') const api = treaty(app) const { data, error } = await api.hi.get() if (error) throw error console.log(data) ``` ## Pass a path parameter Call a dynamic segment with an object containing its parameter name. `api.id({ id: 1895 }).get()` calls `GET /id/1895`. TypeScript knows which parameter the route accepts. ```typescript import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/hi', 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) const api = treaty(app) const { data, error } = await api.id({ id: 1895 }).get() if (error) throw error console.log(data) ``` ## Send a body Pass a body to a method such as `post`. The route’s schema guides the client: this request needs a numeric `id` and a string `name`. The server returns the same object. Check `error` before using `data`. ```typescript import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/hi', 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: t.Object({ id: t.Number(), name: t.String() }) }, ({ body }) => body) const api = treaty(app) const { data, error } = await api.mirror.post({ id: 1895, name: 'Skadi' }) if (error) throw error console.log(data) ``` To call a separately running server, first export your existing Elysia server’s type: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/hi', () => 'Hi Elysia') .get('/id/:id', ({ params: { id } }) => id) .post('/mirror', { body: t.Object({ id: t.Number(), name: t.String() }) }, ({ body }) => body) .listen(3000) export type App = typeof app ``` Then import the server type and consume the Elysia API on the client: **client.ts** client.ts ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') // response type: 'Hi Elysia' const { data, error } = await app.hi.get() ``` ## Tree-like syntax An HTTP path is a resource indicator for a file system tree. A file system consists of multiple levels of folders, for example: - /documents/elysia - /documents/kalpas - /documents/kelvin Each level is separated by **/** (slash) and a name. However, in JavaScript, instead of using **”/”** (slash), we use **”.”** (dot) to access deeper resources. Eden Treaty turns an Elysia server into a tree-like file system that can be accessed from the JavaScript frontend instead. | Path | Treaty | | ------------ | ------------ | | / | | | /hi | .hi | | /deep/nested | .deep.nested | Combined with the HTTP method, we can interact with the Elysia server. | Path | Method | Treaty | | ------------ | ------ | ------------------- | | / | GET | .get() | | /hi | GET | .hi.get() | | /deep/nested | GET | .deep.nested.get() | | /deep/nested | POST | .deep.nested.post() | ## Dynamic path Dynamic path parameters need a name as well as a value. For an `api` client whose server registers `/item/:name`, use a function call to identify the parameter. ```ts // Unclear what the value is supposed to represent? api.item['skadi'].get() ``` This property-access form belongs to the [legacy Eden Treaty 1](/eden/treaty/legacy) client; in Eden Treaty 2 it is a type error. To handle this, we can specify a dynamic path using a function to provide a key value instead. ```ts // Clear that the value is dynamic and the path is 'name' api.item({ name: 'Skadi' }).get() ``` | Path | Treaty | | -------------- | ----------------------------- | | /item | .item | | /item/:name | `.item({ name: 'Skadi' })` | | /item/:name/id | `.item({ name: 'Skadi' }).id` | --- Source: [eden/treaty/parameters/index.md](eden/treaty/parameters/index.md) # Parameters We need to send a payload to the server eventually. To handle this, Eden Treaty’s methods accept 2 parameters to send data to the server. Both parameters are type-safe and will be guided by TypeScript automatically: 1. body 2. additional parameters - query - headers - fetch ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, ({ body }) => body) .listen(3000) const api = treaty('localhost:3000') // works api.user.post({ name: 'Elysia' }) // also works api.user.post({ name: 'Elysia' }, { // This is optional as not specified in schema headers: { authorization: 'Bearer 12345' }, query: { id: 2 } }) ``` If the method is **“GET”** or **“HEAD”**: 1. Additional parameters - query - headers - fetch ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/hello', () => 'hi') .listen(3000) const api = treaty('localhost:3000') // works api.hello.get({ // This is optional as not specified in schema headers: { hello: 'world' } }) ``` ## Empty body If the body is optional or not needed, but query or headers are required, you may pass the body as `null` or `undefined` instead. ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/user', { query: t.Object({ name: t.String() }) }, () => 'hi') .listen(3000) const api = treaty('localhost:3000') api.user.post(null, { query: { name: 'Ely' } }) ``` ## Fetch parameters Eden Treaty is a fetch wrapper; we may add any valid [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) parameters to Eden by passing them to `fetch`: ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/hello', () => 'hi') .listen(3000) const api = treaty('localhost:3000') const controller = new AbortController() const cancelRequest = setTimeout(() => { controller.abort() }, 5000) await api.hello.get({ fetch: { signal: controller.signal } }) clearTimeout(cancelRequest) ``` ## File upload We may pass any of the following to attach file(s): - **File** - **File\[]** - **FileList** - **Blob** Attaching a file will result in **content-type** being **multipart/form-data**. The route schema still controls the accepted TypeScript type. For `t.Files()`, convert an input’s `FileList` to `File[]`; use a `File` when the schema requires a file rather than a plain `Blob`. Suppose we have the server as follows: server.ts ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/image', { body: t.Object({ title: t.String(), image: t.Files() }) }, ({ body: { image, title } }) => title) .listen(3000) export type App = typeof app ``` Then upload from the client: client.ts ```ts import { treaty } from '@elysia/eden' import type { App } from './server' const api = treaty('localhost:3000') const images = document.getElementById('images') as HTMLInputElement const { data } = await api.image.post({ title: "Misono Mika", image: Array.from(images.files ?? []), }) ``` --- Source: [eden/treaty/plugin/index.md](eden/treaty/plugin/index.md) # Eden plugin Eden plugins add custom methods and mutation hooks to an Eden Treaty client. The plugin API is available in Eden v2 beta. Follow [Eden installation](/eden/installation/) to install the matching Eden and Elysia versions. ## Add a custom method `createPlugin` infers each verb’s call signature and adds it to every route. Its context argument includes `path`, `paths`, `domain`, and `config`. It also includes `elysia` when you pass an Elysia instance. ### Control method types with `PluginTypeFn` Define a `PluginTypeFn` and use it as the `TreatyPlugin` type parameter to add methods only to matching routes. The type controls availability; `verbs` implements the method at runtime. This example adds `routePath` to GET routes marked with `live: true`: ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' import type { PluginTypeFn, TreatyPlugin } from '@elysia/eden' const app = new Elysia() .macro({ live: { meta: { live: true } as const } }) .get('/users/:id', { live: true }, ({ params }) => params.id) .get('/health', () => 'ok') interface RouteInfoTypeFn extends PluginTypeFn { output: this['node'] extends { get: { meta: { live: true } } } ? { routePath: () => string } : {} } const routeInfo: TreatyPlugin = { name: 'route-info', verbs: { routePath(context) { return context.path } } } const api = treaty(app).use(routeInfo) api.users({ id: 1 }).routePath() // '/users/1' api.health.get() // @ts-expect-error This route is not marked live. api.health.routePath() ``` Keep the client returned by `.use(plugin)`; the original client does not include the plugin. Custom methods run plugin code and do not send HTTP requests. ## Observe mutations `before` runs before a mutation request. `after` receives the result and the same call context. ```ts import { Elysia, t } from 'elysia' import { createPlugin, treaty } from '@elysia/eden' const app = new Elysia() .post('/message', { body: t.Object({ text: t.String() }) }, ({ body }) => body) const logger = createPlugin({ name: 'mutation-logger', before(context) { console.log('Sending', context.method, context.paths) }, after(result, context) { console.log('Received', context.method, result.status) } }) const api = treaty(app).use(logger) await api.message.post({ text: 'Hello' }) // Sending post [ 'message' ] // Received post 200 ``` These hooks run for `post`, `put`, `patch`, and `delete`, not for reads, WebSocket subscriptions, or custom methods. The call context has `paths`, lowercase `method`, request `options`, `config`, and `domain`. The result has `data`, `error`, `status`, `response`, and `headers`; the last two can be `undefined` if the request does not reach the server. ## Registration rules A plugin needs a non-empty `name` and a `verbs` object, `before`, or `after`. Plugin methods cannot replace Treaty HTTP methods, `subscribe`, `use`, `~path`, `then`, `catch`, or `finally`. Registering a reserved name or a method already registered by another plugin throws an error. Chain `.use` to combine plugins. Mutation hooks run in registration order. Eden’s `.use` extends the client. To add routes or hooks to the server, use [Elysia plugins](/essential/plugin/) instead. --- Source: [eden/treaty/response/index.md](eden/treaty/response/index.md) # Response Once the fetch method is called, Eden Treaty returns a `Promise` containing an object with the following properties: - data - returned value of the response (2xx) - error - `{ status, value }` for a non-2xx response, with the server’s returned value in `error.value` - response `Response` - Web Standard Response class - status `number` - HTTP status code - headers `Headers` - response headers Once returned, you must provide error handling to ensure that the response data value is unwrapped; otherwise, the value will be nullable. Elysia provides a `status()` helper to return a status-specific response, and Eden will provide type narrowing for the error value. ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, ({ body: { name }, status }) => { if(name === 'Otto') return status(400) return name }) .listen(3000) const api = treaty('localhost:3000') const submit = async (name: string) => { const { data, error } = await api.user.post({ name }) // type: string | null console.log(data) if (error) switch(error.status) { case 400: // Error type will be narrow down throw error.value default: throw error.value } // Once the error is handled, type will be unwrapped // type: string return data } ``` By default, Elysia infers `error` and `response` types to TypeScript automatically, and Eden will be providing auto-completion and type narrowing for accurate behavior. Tip If the server responds with an HTTP status >= 300, then `data` will be `null`, and `error` will be `{ status, value }`, with the server’s returned value in `error.value`. Otherwise, the parsed response is passed to `data`. Fetch follows redirects by default, so a redirect may resolve to its destination response first. ## Stream response Eden will interpret a stream response or [Server-Sent Events](/essential/handler#server-sent-events) as an `AsyncGenerator`, allowing us to use a `for await` loop to consume the stream. **Stream** ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/ok', function* () { yield 1 yield 2 yield 3 }) const { data, error } = await treaty(app).ok.get() if (error) throw error for await (const chunk of data) console.log(chunk) ``` **Server-Sent Events** ```ts import { Elysia, sse } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/ok', function* () { yield sse({ event: 'message', data: '1' }) yield sse({ event: 'message', data: '2' }) yield sse({ event: 'end' }) }) const { data, error } = await treaty(app).ok.get() if (error) throw error for await (const chunk of data) console.log(chunk) ``` ## Utility types Eden Treaty provides utility types `Treaty.Data` and `Treaty.Error` to extract the `data` and `error` types from the response. ```ts import { Elysia, t } from 'elysia' import { treaty, type Treaty } from '@elysia/eden' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, ({ body: { name }, status }) => { if(name === 'Otto') return status(400) return name }) .listen(3000) const api = treaty('localhost:3000') type UserData = Treaty.Data // Alternatively, you can also pass a response const response = await api.user.post({ name: 'Saltyaom' }) type UserDataFromResponse = Treaty.Data type UserError = Treaty.Error ``` --- Source: [eden/treaty/unit-test/index.md](eden/treaty/unit-test/index.md) # Unit test According to [Eden Treaty config](/eden/treaty/config#urlorinstance) and [Unit Test](/patterns/unit-test), we may pass an Elysia instance to Eden Treaty directly to interact with the Elysia server directly without sending a network request. We may use this pattern to create a unit test with end-to-end type safety and type-level tests all at once. test/index.test.ts ```ts import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia().get('/hello', 'hi') const api = treaty(app) describe('Elysia', () => { it('returns a response', async () => { const { data } = await api.hello.get() expect(data).toBe('hi') }) }) ``` ## Type safety test Include your test folder in `tsconfig.json`, then run TypeScript with the project’s compiler options. Terminal window ```bash bunx tsc --noEmit ``` This is useful to ensure type integrity for both client and server, especially during migrations. --- Source: [eden/treaty/websocket/index.md](eden/treaty/websocket/index.md) # WebSocket Eden Treaty supports WebSocket using the `subscribe` method. ```ts import { Elysia, t } from "elysia"; import { websocket } from 'elysia/websocket' import { treaty } from "@elysia/eden"; const app = new Elysia() .use(websocket()) .ws("/chat", { body: t.String(), response: t.String(), message({ body }) { return body }, }) .listen(3000); const api = treaty("localhost:3000"); const chat = api.chat.subscribe(); chat.subscribe((message) => { console.log("got", message.data); }); chat.on("open", () => { chat.send("hello from client"); }); ``` `subscribe()` accepts query parameters through its options. Browser WebSockets cannot send arbitrary request headers; use cookies or a server-approved authentication flow. ## Response `subscribe()` returns an `EdenWS` wrapper. It provides typed `send`, `subscribe`, `on`, `off`, and `close` helpers. Use `chat.ws` to access the underlying native WebSocket. The wrapper is not a subclass with every native WebSocket property. --- Source: [essential/best-practice/index.md](essential/best-practice/index.md) # Best practice Elysia doesn’t enforce a particular coding pattern. This guide uses a feature-based structure inspired by MVC and separation of concerns. A controller handles HTTP, a service implements business rules, and a model describes the data accepted and returned by the API. For a JSON API, there usually isn’t a separate view layer. Here, `model.ts` means request and response schemas, not a database model. This is a convention for organizing Elysia code without losing type inference. Adapt it to your application. ## Folder structure Elysia is unopinionated about folder structure, leaving you to decide how to organize your code yourself. If you don’t have a specific structure in mind, we recommend a feature-based structure. - src - index.tsServer entry point, starts listening - app.tsElysia instance composition, used by index.ts and tests - env.tsValidated environment variables - libsShared clients and helpers, independent of request context - dbDatabase client and schema (eg. Prisma, Drizzle, etc.) - message.tsPure helpers with no Elysia import - pluginsShared Elysia plugins and request lifecycle behavior - macro.tsCustom macro plugin for Elysia - routesRoutes grouped by feature - auth - index.tsHTTP to business logic - service.tsBusiness rules and data access, independent of request context - model.tsDTOs, validation schemas, and TypeScript types - user - index.tsHTTP to business logic - service.tsBusiness rules and data access, independent of request context - model.tsDTOs, validation schemas, and TypeScript types - test - auth.test.tsController and service tests for the feature - user.test.ts ## Application setup `src/index.ts` is the process entry point. Import the application, start listening, and report startup information here. src/index.ts ```ts import { app } from './app' import { env } from './env' app.listen(env.PORT) ``` `src/app.ts` creates and exports the Elysia application. Compose feature controllers and application-wide plugins here, such as CORS or OpenAPI. Keep feature handlers and business rules in their route folders. src/app.ts ```ts import { Elysia } from 'elysia' import { auth } from './routes/auth' import { user } from './routes/user' export const app = new Elysia().use(auth).use(user) export type App = typeof app ``` Keeping `.listen()` in the entry point lets tests import `app` without opening a port, and lets [Eden](/eden/) import its type. Importing the app can still initialize its dependencies, such as configuration and database clients. ## Shared code A code file belongs in `libs/` or `plugins/` only when it is shared across features. Otherwise, keep it with the feature that owns it. ### Libraries Use `libs/` for shared code that isn’t related to Elysia. | File | Contains | | ------------------ | ---------------------------------------------------------- | | `libs/db/index.ts` | create and export a database client. | | `libs/db/table.ts` | define tables and relations for your ORM or query builder. | | `libs/message.ts` | ordinary shared helpers, such as message formatting. | Create the database client once and import it where needed. Feature-specific queries belong with the feature’s business logic. `libs/db/index.ts` shouldn’t become a collection of every query in the application. Keep a helper inside its feature until another feature needs it. A file doesn’t belong in `libs/` just because it contains a function. ### Plugins Use `plugins/` for reusable Elysia behavior that is related to HTTP/lifecycle events, like an authentication macro or request metadata. src/plugins/macro.ts ```ts import { Elysia } from 'elysia' export const metadata = new Elysia({ name: 'request-metadata' }).macro({ requestId: { derive: () => ({ requestId: crypto.randomUUID() }) } }) ``` The reason is to separate an HTTP and Elysia concern from business logic. A service or `libs` doesn’t need to know about request IDs, cookies, or headers. ## Routes Each folder under `routes/` owns a feature, such as `auth` or `user`. Keep its routes, business logic, and API schemas together so a change to one feature doesn’t require searching through application-wide controller, service, and model folders. The three files have different responsibilities: | File | Contains | Elsewhere | | ------------ | ---------------------------------------- | ----------------------------------------- | | `index.ts` | Elysia controller, HTTP-related behavior | Detailed technical implementation | | `service.ts` | Business operations, transactions | Request context, cookie handling | | `model.ts` | DTOs, schemas, TypeScript types | Database connections, business operations | Start with what the feature needs. A small route can stay in `index.ts`, and a feature that needs no shared schemas can omit `model.ts`. The walkthrough below follows a greeting feature through its controller, model, and service, then shows how the main application composes feature controllers. A greeting doesn’t require three files just to return a string. ### Controller (index.ts) An Elysia instance. Its purpose is to handle HTTP-related concerns and map them to business logic from Models and Services. Anything related to HTTP should be done here, eg. set headers, cookies, redirects. src/routes/greet/index.ts ```typescript import { Elysia } from 'elysia' import { greet } from './service' import * as GreetModel from './model' export const greeting = new Elysia({ prefix: '/greet' }) .post('/', { body: GreetModel.Post.Body }, ({ body }) => greet(body)) ``` ### Model (model.ts) Set of schemas and TypeScript types used by `Controller` and derived by `Service`. It is used to validate runtime schemas and derive TypeScript types from them instead of maintaining a matching interface by hand. src/routes/greet/model.ts ```typescript import { t, type UnwrapModels } from 'elysia' export const Post = { Body: t.Object({ name: t.String() }) } export type Post = UnwrapModels ``` ### Service (service.ts) Implementation details of business rules, data access and transactions. It should be independent of HTTP context except for Elysia’s `status` or an `HTTPError` class for setting HTTP status codes when necessary. src/routes/greet/service.ts ```typescript import type * as GreetModel from './model' export function greet({ name }: GreetModel.Post['Body']) { return 'Hello ' + name } ``` ### Main instance (app.ts) Zoom out to `src/app.ts`, where each feature is added with `.use()`. The main instance composes controllers while their handlers, services, and models stay in their route folders. Here, `auth` and `user` stand in for other feature controllers with the same structure as `greeting`. Each `.use()` adds a different controller. Export the app here and start listening in `src/index.ts`. src/app.ts ```typescript import { Elysia } from 'elysia' import { greeting } from './routes/greet' import { auth } from './routes/auth' import { user } from './routes/user' export const app = new Elysia() .use(greeting) .use(auth) .use(user) ``` ## Controller Map HTTP-related concerns to business logic. Anything related to HTTP should be done here. The following example reads a public user profile. Its controller selects the route, binds validation schemas, sets a response header, and passes the user ID to a service. src/routes/user/index.ts ```ts import { Elysia } from 'elysia' import { metadata } from '../../plugins/macro' import * as UserModel from './model' import { getUser } from './service' export const user = new Elysia({ prefix: '/user' }) .use(metadata) .get( '/:id', { requestId: true, params: UserModel.ByIdGet.Params, response: UserModel.ByIdGet.Response }, ({ set, params, requestId }) => { set.headers['x-request-id'] = requestId return getUser(params.id) } ) ``` Cookies, headers, redirects, and other HTTP details belong here or in a request-aware plugin. For example, a sign-in controller can set a session cookie after its service creates a session. The service doesn’t need the cookie object to do its work. - Pass only the values a service needs, such as `params.id`, `body`, `user.id`. - Avoid passing the whole context or annotating an extracted handler with a bare `Context` type. It’s recommended to supply the Elysia plugin (from the `plugins` folder) for type inference, similar to `requestId: true` above. The plugin adds a property to the context, and the controller can use it without a type assertion. Why This pattern is enforced by Elysia. It makes an Elysia instance know only the Context properties it actually needs, forcing you to provide an actual implementation for type soundness. An Elysia instance already groups routes and preserves their types. A base controller class or a second controller that only forwards arguments to a service adds no useful separation. ## Model Set of schemas and TypeScript types used by Controller and derived by Service. It is used to validate runtime schemas for `Controller` and provide TypeScript types for `Service`. The model here often consists of DTOs defined by `t` (a named export from `elysia`) or Standard Schema (Zod, Valibot, etc.) to validate data at runtime and derive TypeScript types from them instead of maintaining a matching interface by hand. ### Naming Convention The naming convention should be decided by you (and your team) and be consistent across the project. But if you don’t have a specific naming convention in mind, we recommend the following: #### Group by route path and HTTP method - Export an object for each route, named `[Pathname][Method]` in PascalCase. - Use the route path relative to the controller prefix, then append the HTTP method - Exclude the instance prefix, which is already represented by the model namespace. For the root path `/`, use the method alone. Replace each dynamic segment with `By[ParameterName]`. | Controller prefix | Local route | Model group | | ----------------- | -------------- | ------------- | | `/greet` | `POST /` | `Post` | | `/user` | `GET /:id` | `ByIdGet` | | None | `GET /:userId` | `ByUserIdGet` | #### Group by schema type - Name each schema property in PascalCase, such as `Body`, `Params`, `Query`, or `Response` - Export a type with the same name as each schema group using `UnwrapModels`. src/routes/user/model.ts ```ts import { t, type UnwrapModels } from 'elysia' export const ByIdGet = { Params: t.Object({ id: t.String({ format: 'uuid' }) }), Response: { 200: t.Object({ id: t.String({ format: 'uuid' }), name: t.String() }), 404: t.Object({ type: t.String(), code: t.Literal('USER_NOT_FOUND'), title: t.String(), status: t.Literal(404), detail: t.String() }) } } export type ByIdGet = UnwrapModels ``` #### Usage - Import the file as `import * as UserModel from './model'`, using the route folder name followed by `Model`. - Access a schema through that namespace, for example `UserModel.ByIdGet.Params`. ```ts import { Elysia } from 'elysia' import * as GreetModel from './model' function greet({ name }: GreetModel.Post['Body']) { return 'Hello ' + name } export const greeting = new Elysia({ prefix: '/greet' }) .post('/', { body: GreetModel.Post.Body }, ({ body }) => greet(body)) ``` This makes everything type-aware and avoids duplication of types. When the schema is updated, the TypeScript type is automatically updated and the service will be type-checked against it and Elysia will also validate the request body at runtime. Note that: - Type annotations alone don’t validate runtime data. - Response schemas are optional. Use them if you need a strict contract for the response. (Elysia also handles normalization and validation for the response if you provide a schema.) A schema used by only one route can stay next to that route. Use [reference models](/patterns/openapi/#reference-model) if you need registered schema names; registration isn’t required to share a schema between files. ### Model and database schema `Database schema` describes how the application stores data while `Model` describes what the API accepts and returns. They have different jobs but sometimes they overlap. Modern libraries like Prisma and Drizzle can convert a database schema to TypeBox or Standard Schema that can be reused in Elysia. So instead of duplicating the schema, you can reuse the database schema as your model. This would make Elysia validate data against the database schema but be careful to not expose sensitive fields or fields that are not relevant to the API. See [Prisma](/integrations/prisma/) and [Drizzle](/integrations/drizzle/) on how to reuse database schemas in Elysia. ## Service Ordinary functions with explicit inputs. A service may query the database directly, check ownership, coordinate other services, and run a transaction when several writes must succeed together. You don’t need a repository layer just to wrap each database call. Keep services independent of request context. For errors your API owns, use `HTTPError.id(code, status)` to give each error a stable identifier and an explicit HTTP status. src/routes/user/service.ts ```ts import { HTTPError } from 'elysia' import { db } from '../../libs/db' import type * as UserModel from './model' export class UserNotFound extends HTTPError.id('USER_NOT_FOUND', 404) {} export async function getUser(id: UserModel.ByIdGet['Params']['id']) { const user = await db.users.findById(id) if (!user) return new UserNotFound('User not found') return { id: user.id, name: user.name } satisfies UserModel.ByIdGet['Response'][200] } ``` Reuse the model’s derived types for service parameters and returned data, as above, so schema changes also update the service contract. ### Prefer custom class over status If you have a service that can return the same error in multiple places, prefer a custom error class over `status()` from the handler context. A class can be reused and extended, and it gives the error a stable identifier for clients to check. ```ts import { HTTPError } from 'elysia' import { db } from '../../libs/db' class UserNotFound extends HTTPError.id('USER_NOT_FOUND', 404) { detail: string constructor(public username: string) { super() this.detail = `User ${username} not found` } } function getUser(name: string) { if (!db.users.has(name)) return new UserNotFound(name) return db.users.get(name) } ``` `HTTPError.id` would handle HTTP-related properties while giving you full control. See [error handling](/patterns/error-handling) for more details. It’s recommended to `return` an error when possible for better type inference. If you throw an error, TypeScript wouldn’t be able to infer it, causing an unknown type for Eden and OpenAPI Type Gen. Why Although we said “service shouldn’t handle HTTP-related information”, it’s necessary here for accurate type inference. You can think of both `status` and `HTTPError` as a “Result” type that Elysia can translate to an HTTP response. ### Growing a feature When `service.ts` becomes difficult to navigate, split it by responsibility within the same folder. For example, an asset feature can keep `service.ts` coordinating image processing, storage, metadata, and permissions: - src/routes/asset - index.tsController - model.tsRequest and response schemas - service.tsCoordinates asset operations - image.tsImage processing - storage.tsFile storage - metadata.tsAsset metadata - permissions.tsAccess rules ## Dependency Injection Elysia is **not** a Dependency Injection framework but more like type-safe **“Context Propagation”**. Or in technical terms: > Elysia is a composition root with typed context, not a container. ```ts import { Elysia } from 'elysia' class Dependency { doThing() { return 'Hi!' } } class Service { constructor(protected dependency: Dependency) {} doSomething() { return this.dependency.doThing() } } new Elysia() .decorate({ dependency: new Dependency() }) // Singleton, alive for the entire app .decorate((deps) => ({ ...deps, service: new Service(deps.dependency) })) .get('/', ({ service }) => service.doSomething()) ``` Or use `derive` to create a new `Service` for each request: ```ts new Elysia() .decorate({ dependency: new Dependency() }) // Per request, alive for each request .derive(({ dependency }) => ({ service: new Service(dependency) })) .get('/', ({ service }) => service.doSomething()) ``` - `decorate` is for singleton dependencies, alive for the entire app. - `derive` is for per-request dependencies, alive for each request. What Elysia doesn’t do is manage the dependency graph for you. You need to explicitly provide that yourself. ### Resource Cleanup A `derive` value with `Symbol.asyncDispose` or `Symbol.dispose` is released once the request is done. ```ts class Service { constructor(protected dependency: Dependency) {} async [Symbol.asyncDispose]() { // Cleanup logic here } } new Elysia() .decorate({ dependency: new Dependency() }) .derive(({ dependency }) => ({ service: new Service(dependency) })) ``` Only what the derive created is disposed: `dependency` is a `decorate` singleton and stays alive. `decorate` and `state` are never disposed, release them in `onStop`. See [dispose](/essential/life-cycle/#dispose) for details. ## Testing Keep tests organized by feature, as in `test/user.test.ts`. - Use `Service` to test business rules without constructing HTTP requests. - Use `app.handle(url, RequestInit?)` to handle a server/integration test. See [unit testing](/patterns/unit-test/) for the test setup and `.handle()` examples. --- Source: [essential/handler/index.md](essential/handler/index.md) # Handler A **handler** decides what to send back when a request matches a route. Give Elysia a value to return, or a function that reads the request and builds a response. Let’s start with a greeting, then give it something to respond to. ## Return a value Pass a literal value when every request should get the same response. A `GET` request to `/` returns `Hello Elysia`. Elysia can prepare this response ahead of time. This also works for static resources such as files. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .listen(3000) ``` ## Use a function Pass a function when you need to do some work for each request. Elysia calls it when the route matches and turns its return value into a response. This function still returns `Hello Elysia`. Same greeting, now with room to grow. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'Hello Elysia') .listen(3000) ``` ## Read the request The handler’s first argument is **context**. It contains the request information and helpers you’ll use to respond. Here, we take `query` from context. Visiting `/?name=Nagisa` returns `Nagisa`; visiting `/` returns `Hello Elysia`. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ query }) => query.name ?? 'Hello Elysia') .listen(3000) ``` ## Choose a status Return `status(code, value)` to choose both the HTTP status and the response body. Now `/` returns `400` with `Tell me your name`. Add `?name=Nagisa` and you’ll get `Nagisa` with the usual `200` status. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ query, status }) => { if (!query.name) return status(400, 'Tell me your name') return query.name }) .listen(3000) ``` Static values aren’t a cache A literal handler gives Elysia a response to prepare ahead of time. Lifecycle hooks can still change the response, including its headers and status. ## Inspect a response Pick a route below to see its response body. `/path` returns its own pathname, and `/teapot` returns `I am a teapot` with HTTP status `418`. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/path', ({ path }) => path) .get('/teapot', ({ status }) => status(418, 'I am a teapot')) .listen(3000) ``` The panel shows the body only. To inspect status codes and headers too, run the example locally and request `http://localhost:3000/teapot` with your HTTP client. ## Context Each request gets its own context. Handlers and lifecycle hooks receive it, though which fields are ready depends on the lifecycle stage. You can take the whole object and read `context.path`, or destructure just what you need as `({ path }) => path`. | Property | What you’ll find | | --------- | -------------------------------------------------------------------------------- | | `body` | The parsed request body, such as JSON, form fields, or uploaded files. | | `query` | Query parameters. For `/?name=Nagisa`, `query.name` is `Nagisa`. | | `params` | Named path parameters. For `/user/42` matching `/user/:id`, `params.id` is `42`. | | `headers` | Incoming request headers, such as `content-type`. | | `cookie` | Cookie values for this request, with helpers to update outgoing cookies. | | `store` | Shared mutable state registered with `.state()`. | | `path` | The request pathname, without the query string. | | `request` | The original Web Standard `Request`. | | `server` | The running server instance, when the runtime provides one. | | Helper | What you can do | | ------------------------ | --------------------------------------------------- | | `status(code, value)` | Return a body with a specific HTTP status. | | `redirect(url, status?)` | Return a redirect response. | | `set` | Set outgoing response headers and a default status. | `store` is shared across requests, as are object references you add with `.decorate()`. Compute request-specific values with `.derive()` instead. See [validation](/essential/validation/) for schemas that check and type request fields. ## status Prefer returning `status()` for an expected error, such as a missing resource. Elysia carries the status code alongside the body’s type. ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/tea', { response: { 200: t.Object({ name: t.String() }), 404: t.String() } }, ({ query, status }) => { if (query.name !== 'earl-grey') return status(404, 'That tea is not on the menu') return { name: 'Earl Grey' } }) .listen(3000) ``` TypeScript can check each body against its status-specific response schema. [Eden](/eden/) uses these types to narrow client results and errors by status code too. Without a custom status or an error, a successful response uses `200`. ## Set response details Use `set` to change the outgoing response. It belongs to the current request. ### set.headers Assign response headers by name. You can also replace the headers object or use `delete` to remove an entry you’ve set. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', ({ set, status }) => { set.headers['x-teapot'] = 'true' return status(418, 'I am a teapot') }) .listen(3000) ``` Use lowercase names, such as `content-type`, to match Elysia’s header autocomplete. HTTP header names themselves are case-insensitive. ### set.status Use `set.status` when a hook or plugin should choose a default status while leaving the body to the handler. ```ts import { Elysia } from 'elysia' new Elysia() .onBeforeHandle(({ set }) => { set.status = 201 }) .post('/tea', () => ({ name: 'Earl Grey' })) .listen(3000) ``` The hook returns nothing, so the handler still runs. A `POST` request to `/tea` returns the object with status `201`. You can use a number or a status name, such as `set.status = 'Created'`. Returning `status()` overrides this default and preserves the relationship between status and body types; assigning `set.status` doesn’t. ## Cookie Read a cookie through its `.value` property. Assign a new value to send an updated cookie in the response. ```ts import { Elysia } from 'elysia' new Elysia() .get('/tea', ({ cookie: { tea } }) => { const previous = tea.value ?? 'none' tea.value = 'Earl Grey' return { previous, current: tea.value } }) .listen(3000) ``` On a request without a `tea` cookie, this returns `previous: 'none'` and sets the cookie to `Earl Grey`. These cookie values belong to the request, not a shared global store. See [cookie](/patterns/cookie/) for options, signing, and removing cookies. ## Redirect Return `redirect()` to send the client to another URL. It uses `302` by default; pass a second argument for a different redirect status. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', ({ redirect }) => redirect('/tea')) .get('/old-menu', ({ redirect }) => redirect('/tea', 301)) .get('/tea', 'Earl Grey') .listen(3000) ``` The client receives a redirect response with a `Location` header. Remember to return it from your handler. ## Form data Return `form()` to send multipart form data. Its fields can include text and files. ```ts import { Elysia, form, file } from 'elysia' new Elysia() .get('/album', () => form({ name: 'Tea Party', images: [file('nagisa.webp'), file('mika.webp')] })) .listen(3000) ``` Place the example files in your app’s working directory before running it. ### Return a file For a single file, return `file()` directly. ```ts import { Elysia, file } from 'elysia' new Elysia() .get('/portrait', file('nagisa.webp')) .listen(3000) ``` ## Stream a response Use a generator function and `yield` to send the response in chunks. Each yielded value becomes part of the stream. ```ts import { Elysia } from 'elysia' new Elysia() .get('/stream', function* () { yield 1 yield 2 yield 3 }) .listen(3000) ``` Use `async function*` when producing a chunk requires `await`. ### Server-sent events Wrap each value in `sse()` to format it as a server-sent event. Elysia sets the response’s content type to `text/event-stream`. ```ts import { Elysia, sse } from 'elysia' new Elysia() .get('/events', function* () { yield sse('Tea is brewing') yield sse({ event: 'ready', data: { tea: 'Earl Grey' } }) }) .listen(3000) ``` The first event carries text. The second uses a named `ready` event with JSON data. ### Set headers before streaming Before the first yield Set headers before yielding the first chunk. Elysia sends the headers when streaming begins, so changes after that won’t reach the client. ```ts import { Elysia } from 'elysia' new Elysia() .get('/stream', function* ({ set }) { set.headers['x-tea'] = 'Earl Grey' yield 1 yield 2 }) .listen(3000) ``` This applies to both regular streams and server-sent events. ### Return without streaming Return before the first `yield` to send a normal response instead. ```ts import { Elysia } from 'elysia' new Elysia() .get('/stream', function* ({ query }) { if (query.stream !== 'true') return 'Ready' yield 1 yield 2 yield 3 }) .listen(3000) ``` `/stream` returns `Ready`. `/stream?stream=true` streams the three values. ### Cancellation If the client cancels before the stream finishes, Elysia closes the generator. That doesn’t interrupt arbitrary work you’ve already started with `await`; pass the request’s abort signal to operations that support it when they need to stop too. ### Consume a stream with Eden Eden reads a stream as an async generator. Use `for await` to consume its chunks. ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .get('/stream', function* () { yield 1 yield 2 yield 3 }) const { data, error } = await treaty(app).stream.get() if (error) throw error for await (const chunk of data) console.log(chunk) ``` ## Request Use `request` when you need the original Web Standard `Request`, including its URL, headers, or abort signal. ```ts import { Elysia } from 'elysia' new Elysia() .get('/user-agent', ({ request }) => request.headers.get('user-agent') ?? 'Unknown' ) .listen(3000) ``` For a parsed body, use `body` from context. Request bodies can only be consumed once, and Elysia may have already read yours. ## Server On Bun, `server` gives you the running Bun server instance. It becomes available after `.listen()` starts the server. ```ts import { Elysia } from 'elysia' new Elysia() .get('/port', ({ server }) => server?.port) .get('/ip', ({ server, request }) => server?.requestIP(request)) .listen(3000) ``` `/port` returns the listening port. `server.requestIP(request)` returns the client’s socket address information when available. These server APIs are Bun-specific. The optional chaining also handles requests dispatched without a running server. ## Extend context Add your own values when your handlers need more than the built-in fields. | Method | Use it for | | ------------- | --------------------------------------------------------- | | `.state()` | Shared mutable state, available through `store`. | | `.decorate()` | Shared helpers or services you can read from context. | | `.derive()` | Values computed for each request before the handler runs. | ```ts import { Elysia } from 'elysia' new Elysia() .state('tea', 'Earl Grey') .decorate('greet', (name: string) => `Hello ${name}`) .derive(({ query }) => ({ name: query.name ?? 'friend' })) .get('/', ({ store, greet, name }) => ({ greeting: greet(name), tea: store.tea })) .listen(3000) ``` Visiting `/?name=Nagisa` returns a greeting of `Hello Nagisa` and the shared tea value, `Earl Grey`. See [derive](/essential/life-cycle/#derive) for more examples. In Elysia v2, `.derive()` runs during `beforeHandle` and replaces the former `.resolve()` API. --- Source: [essential/life-cycle/index.md](essential/life-cycle/index.md) # Life cycle The request pipeline is divided into stages called **lifecycle events**. Each stage has its own responsibility, from reading the request to preparing the response. Instead of using traditional middleware, Elysia’s lifecycle events are more fine-grained. You can run code before or after the handler, and even skip the handler entirely. | Stage | What happens | | --------------------------------- | ------------------------------------------------------ | | [Request](#request) | Receive a new request. | | [Parse](#parse) | Read the body into `body`. | | [Transform](#transform) | Change context before validation. | | Validation | Check request data against the route’s schemas. | | [Before handle](#before-handle) | Check access and compute request values with `derive`. | | Handler | Run the matched route’s handler. | | [After handle](#after-handle) | Inspect or replace the returned value. | | [Map response](#map-response) | Map the value to a Web Standard `Response`. | | [After response](#after-response) | Run post-response work, such as logging. | - An early return skips the handler. - Errors go through [error handling](#error-error-handling), rather than being another step after a successful response. - [Trace](/patterns/trace/) observes the time spent in lifecycle events. ### Why Let’s take a look into an example scenario. Say we want to send back some HTML. Normally, we’d set `content-type` to `text/html` so the browser can render it. But manually setting one for each route is tedious. A hook can inspect the returned value and choose the header for us, while the handler concentrates on what to return. ## Return HTML Our handler returns an HTML string, but nothing sets its `content-type` yet. Next, we’ll set it so the browser knows to render it as HTML. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => '

Hello World

') .listen(3000) ``` ## Add header We can use `mapResponse` to set the header for us. It’s specifically for mapping a return value to a `Response`. ```typescript import { Elysia } from 'elysia' new Elysia() .mapResponse(({ responseValue }) => { if (typeof responseValue === 'string' && /<[a-z][\s\S]*>/i.test(responseValue)) return new Response(responseValue, { headers: { 'content-type': 'text/html; charset=utf-8' } }) }) .get('/', () => '

Hello World

') .listen(3000) ``` ## Allow only preview `beforeHandle` is for checking before the handler runs. Ideal for authorization. Without `?preview=true` the request returns `403` with `Preview is closed`, and the route handler doesn’t run. ```typescript import { Elysia } from 'elysia' new Elysia() .mapResponse(({ responseValue }) => { if (typeof responseValue === 'string' && /<[a-z][\s\S]*>/i.test(responseValue)) return new Response(responseValue, { headers: { 'content-type': 'text/html; charset=utf-8' } }) }) .get('/', { beforeHandle({ query, status }) { if (query.preview !== 'true') return status(403, 'Preview is closed') } }, () => '

Hello World

') .listen(3000) ``` ## Hook Registration Split into 2 types: 1. [Local hook](#local-hook): runs on a specific route. 2. [Interceptor hook](#interceptor-hook): runs after registration in the current instance, subject to plugin scope. Tip Hooks receive context, just like a handler. Which fields are ready depends on the stage: `request` runs before body parsing, and [derive](#derive) runs after request validation. ### Local hook Put a hook in the route’s options to apply it to that route only: ```ts import { Elysia } from 'elysia' new Elysia() .get('/', { afterHandle({ set }) { set.headers['content-type'] = 'text/html; charset=utf-8' } }, () => '

Hello World

') .get('/hi', () => '

Hello World

') .listen(3000) ``` | Path | Content type | | ----- | -------------------------- | | `/` | `text/html; charset=utf-8` | | `/hi` | Not explicitly set | ### Interceptor hook Register a hook on the instance to apply it to the handlers that follow. The method is usually the event name with an `on` prefix, such as `.onAfterHandle()` or `.onBeforeHandle()`. `.mapResponse()` and `.derive()` are the exceptions: they have no `on` form. ```ts import { Elysia } from 'elysia' new Elysia() .get('/none', () => '

Hello World

') .onAfterHandle(({ responseValue, set }) => { if (typeof responseValue === 'string' && responseValue.startsWith('

')) set.headers['content-type'] = 'text/html; charset=utf-8' }) .get('/', () => '

Hello World

') .get('/hi', () => '

Hello World

') .listen(3000) ``` The check recognizes the heading markup used in this example. It isn’t a general HTML detector or sanitizer. | Path | Content type | | ------- | -------------------------- | | `/none` | Not explicitly set | | `/` | `text/html; charset=utf-8` | | `/hi` | `text/html; charset=utf-8` | Hooks from plugins may also apply. See [plugin scope](/essential/plugin/#scope) for which routes inherit them. ## Order of code Register a hook before the routes it should affect. ```ts import { Elysia } from 'elysia' new Elysia() .onBeforeHandle(() => { console.log('1') }) .get('/', () => 'hi') .onBeforeHandle(() => { console.log('2') }) .listen(3000) ``` A request to `/` logs only `1`. The second hook was registered after the route. Routes mounted with `.use()` follow the same rule. A parent hook reaches them only when it was registered before `.use()`: ```ts import { Elysia } from 'elysia' const someRouter = new Elysia().get('/', () => 'hi') new Elysia() .onBeforeHandle(() => { console.log('1') }) .use(someRouter) .onBeforeHandle(() => { console.log('2') }) .listen(3000) ``` For this mounted route, only `1` is logged. The hook registered after `.use(someRouter)` doesn’t reach its routes, whatever its scope. Keep shared parent hooks before `.use()`. Registering a hook on a plugin doesn’t automatically export it to every parent route; see [scope](/essential/plugin/#scope). *** ## Request `request` is the first lifecycle hook for a new request. Use it for work that doesn’t need parsed or validated data: - Caching - Rate limiting or IP/region checks - Analytics - Response headers, such as CORS ### Example A rate limiter can reject a request before parsing its body. Here, `rateLimiter` represents your own service, and `requestIP()` is Bun-specific: ```ts import { Elysia } from 'elysia' import { rateLimiter } from './rate-limiter' new Elysia() .onRequest(({ request, server, status }) => { const ip = server?.requestIP(request)?.address if (ip && rateLimiter.isLimited(ip)) return status(429, 'Too many requests') }) .get('/', () => 'hi') .listen(3000) ``` Returning a value responds early and skips the route’s parsing, validation, and handler. Unlike route-specific hooks, `request` runs before route matching and can cover routes declared before it. Register it near the start of the instance so this behavior is easy to see. ### Pre context The request hook receives the early request context. It includes: - `request` - `set` - `store` - `status` and `redirect` - values added with `.decorate()`, spread onto the context - `server` (if available) `body` and `headers` aren’t parsed yet in this stage. *** ## Parse `parse` is the body-parser stage. The value returned by the parser becomes `body`. Returning `undefined` will skip to the next parser. If no parser produces a value, `body` is `undefined`. Built-in parsers cover: - `text/plain` - `application/json` - `multipart/form-data` - `application/x-www-form-urlencoded` - `application/octet-stream` Use a custom parser when your content type isn’t supported. ### Example ```ts import { Elysia } from 'elysia' new Elysia() .onParse(({ request, contentType }) => { if (contentType === 'application/custom-type') return request.text() }) .post('/', ({ body }) => body) .listen(3000) ``` A `POST` with `content-type: application/custom-type` returns the request’s text. Once a parser produces a value, the remaining parsers are skipped. ### Context The parse hook adds `contentType`, the request’s content-type header, to context. The body is still being read and request validation hasn’t happened yet. ### Explicit parser Set `parse` to choose a parser explicitly: ```ts import { Elysia } from 'elysia' new Elysia() .post('/', { parse: 'json' }, ({ body }) => body) .listen(3000) ``` | Shorthand | Content type | | ------------- | ----------------------------------- | | `text` | `text/plain` | | `json` | `application/json` | | `formdata` | `multipart/form-data` | | `urlencoded` | `application/x-www-form-urlencoded` | | `arrayBuffer` | `application/octet-stream` | | `none` | Skip body parsing | You can use either the shorthand or the full content type. ### Skip body parsing A Web Standard `Request` body can only be consumed once. Libraries such as tRPC or oRPC may need to read the original body themselves. Use `parse: 'none'` to leave it for the library: ```ts import { Elysia } from 'elysia' import { library } from './library' new Elysia() .post('/', { parse: 'none' }, ({ request }) => library.handle(request)) .listen(3000) ``` Here, `library.handle` represents the third-party HTTP handler you’re integrating. ### Custom parser Register a named parser with `.parser()` and refer to it from a route: ```ts import { Elysia } from 'elysia' new Elysia() .parser('custom', ({ request, contentType }) => { if (contentType === 'application/elysia') return request.text() }) .post('/', { parse: ['custom', 'json'] }, ({ body }) => body) .listen(3000) ``` The custom parser handles `application/elysia`. When it returns `undefined`, the JSON parser gets a turn. *** ## Transform `transform` runs before request validation. Use it to change existing context values so they match the schema. ### Example ```ts import { Elysia, t } from 'elysia' import * as z from 'zod' new Elysia() .get('/id/:id', { params: z.object({ id: z.number() }), transform({ params }) { const id = Number(params.id) if (!Number.isNaN(id)) params.id = id } }, ({ params: { id } }) => id) .listen(3000) ``` The hook converts a numeric path parameter before validation. `/id/42` returns `42`; `/id/hello` fails validation. To add typed properties to context, use [derive](#derive) after validation instead. ### Coerce Schema `transform` was introduced when schema coercion was not a generally available feature among schema libraries. Today, it’s possible to use schema coercion to convert values directly for most schema libraries. For example, Zod has `z.coerce.number()` and `z.coerce.date()`. ```ts import { Elysia, t } from 'elysia' import * as z from 'zod' new Elysia() .get('/id/:id', { params: z.object({ id: z.coerce.number() }), params: z.object({ id: z.number() }), transform({ params }) { const id = Number(params.id) if (!Number.isNaN(id)) params.id = id } }, ({ params: { id } }) => id) .listen(3000) ``` *** ## Before handle Runs after request validation but before the route handler. Use it for additional checks after validation, e.g.: - Authorization - Session validation Returning a value skips the handler. [afterHandle](#after-handle) can still inspect or replace that early response. ### Example ```ts import { Elysia, t } from 'elysia' import { validateSession } from './user' new Elysia() .get('/', { cookie: t.Object({ session: t.String() }), beforeHandle({ cookie: { session }, status }) { if (!validateSession(session?.value)) return status(401, 'Unauthorized') } }, () => 'hi') .listen(3000) ``` ## Derive Adds a **typed** value to context for **each request** for handlers that follow. Use it to compute a value from request data, such as a user object from a session token. ```ts import { Elysia, t } from 'elysia' import { validateSession } from './user' new Elysia() .guard({ cookie: t.Object({ session: t.String() }) }) .derive(({ cookie: { session }, status }) => { const user = validateSession(session?.value) if (!user) return status(401, 'Unauthorized') return { user } }) .get('/', ({ user }) => user.id) .listen(3000) ``` ### Derive vs State/Decorate | Derive | State/Decorate | | --------------------- | --------------------------------- | | Unique per request | Singleton, Shared across requests | | Runs after validation | Runs once at server start | ### Queue `derive` and `beforeHandle` share the same queue and run in registration order. ```ts import { Elysia } from 'elysia' new Elysia() .onBeforeHandle(() => { console.log(1) }) .derive(() => { console.log(2) return {} }) .onBeforeHandle(() => { console.log(3) }) .get('/', () => 'hi') .listen(3000) ``` ### Dispose A `derive` value with `Symbol.asyncDispose` or `Symbol.dispose` is released once the response is sent. ```ts import { Elysia } from 'elysia' import { pool } from './db' class Transaction { constructor(private client = pool.connect()) {} async query(sql: string) { return (await this.client).query(sql) } async [Symbol.asyncDispose]() { (await this.client).release() } } new Elysia() .derive(() => ({ tx: new Transaction() })) .get('/', ({ tx }) => tx.query('SELECT 1')) .listen(3000) ``` - Runs after [after response](#after-response) and `defer`, in reverse order. `Symbol.asyncDispose` wins when both exist. - Also runs on throw, `beforeHandle` short-circuit and client disconnect. A stream is released when it ends. - Only values the derive created are disposed. A `decorate` or `store` value it returns or spreads is left alone. - Disposer errors are logged, the response is already sent. `decorate` and `state` are never disposed, release them in `onStop`. ## After handle Runs after the handler, or after a short-circuit from `beforeHandle`. Use it to inspect or replace the returned value. A non-`undefined` return replaces the response value. ### Example ```ts import { Elysia } from 'elysia' import { badWords } from './libs' new Elysia() .get('/', { afterHandle({ responseValue }) { if (typeof responseValue === 'string' && badWords.includes(responseValue)) return 'Cheek pinched!' } }, () => 'Erpin') .listen(3000) ``` Returning a value skips the remaining `afterHandle` hooks. Returning an `Error` works like returning it from the handler: it goes through [error handling](#error-error-handling), including `.onError(Class)` handlers. If no hook handles it, an error with a status, such as an `HTTPError`, is sent with that status. ### Context Read the current returned value through `responseValue`. It may be a string, object, `status()` value, or a `Response`, depending on the handler and earlier hooks. *** ## Map response Use it to convert the returned value into a Web Standard `Response`. ### Example ```ts import { Elysia } from 'elysia' new Elysia() .mapResponse(({ responseValue }) => { if (typeof responseValue === 'string' && /<[a-z][\s\S]*>/i.test(responseValue)) return new Response(responseValue, { headers: { 'content-type': 'text/html; charset=utf-8' } }) }) .get('/html', () => '

Hello World

') .listen(3000) ``` Returning a value skips the remaining `mapResponse` hooks. Elysia will merge `set.headers` into the response automatically. There is no need to pass it to the `Response` constructor. *** ## Error (error handling) Handle errors from request processing. You can provide a custom response, log the failure, or handle a specific error class. ### Example ```ts import { Elysia } from 'elysia' new Elysia() .onError(({ error, status }) => { console.error(error) return status(503, 'Server is under maintenance') }) .get('/', () => { return new Error('Maintenance window') }) .listen(3000) ``` Any `Error` returned or thrown from a handler will be caught by the error hook. Returning `status()` or `problem()` sends an ordinary response, while throwing one enters error handling. Use a returned status for an expected rejection. Returning an `Error` from an error hook sends it as if no hook handled it, at that error’s own status. An `HTTPError` sends its problem document, and an `Error` without a status becomes a `500`. It isn’t passed to other error hooks. ### Context Error hooks receive `error`, the thrown value alongside the request context available when the failure occurred. If you need to distinguish errors in a general hook, use `instanceof`. ### Catch specific errors We can catch specific errors by passing the error class to `.onError()`: ```ts import { Elysia, NotFound, ValidationError } from 'elysia' new Elysia() .onError(NotFound, ({ status }) => status(404, 'Not Found :(')) .onError(ValidationError, ({ status }) => status(400, 'Invalid request')) .listen(3000) ``` Here are some common failures and their error classes: | Error class | Reason | | --------------------- | -------------------------------- | | `NotFound` | No matching route | | `ParseError` | Invalid request body syntax | | `ValidationError` | A schema check failed | | `InternalServerError` | Explicit internal server failure | | `InvalidCookie` | Invalid signed cookie | You can use a catch-all error hook for handling unexpected failures. ```ts import { Elysia, InternalServerError } from 'elysia' new Elysia() .onError(({ error, status }) => { if (error instanceof InternalServerError) return status(503, 'Oh') }) .listen(3000) ``` ### Custom Error Elysia provides an `HTTPError` for declaring custom errors with a status code, headers, and a message. Any error that implements the `HTTPError` interface (without necessarily extending it) can be used in error handling. The interface requires a `type`; also give the error a `status`, otherwise the response defaults to `500`. ```ts import { Elysia, HTTPError } from 'elysia' // using syntax sugar class WithSyntaxSugar extends HTTPError.id('SUGAR_ERROR', 400) { // optional headers = { 'x-error': 'sugar' } // replaces the whole response body value() { return { message: 'With syntax sugar' } } } // No need to extend HTTPError, just implement the interface class CustomError extends Error implements HTTPError<'CUSTOM_ERROR'> { type = 'CUSTOM_ERROR' as const // status must be const (or statically analyzable) for type narrowing status = 409 as const headers = { 'x-error': 'custom' } // `detail` of the RFC 9457 problem document, in place of the message detail = 'Custom error' } ``` With the HTTPError interface, Elysia will use these properties for the response instead of the default `500 Internal Server Error`. This means you don’t need to register it to `.onError()` if you want to return a custom error response. *** ## After response Runs after the response is sent. Use it for cleanup, logging, and analytics. ### Example ```ts import { Elysia } from 'elysia' new Elysia() .onAfterResponse(() => { console.log('Response', performance.now()) }) .get('/', () => 'Hello') .listen(3000) ``` ### Response Read `responseValue` to inspect the value produced by the handler and response hooks: ```ts import { Elysia } from 'elysia' new Elysia() .onAfterResponse(({ responseValue }) => { console.log(responseValue) }) .get('/', () => 'Hello') .listen(3000) ``` This logs `Hello`. `responseValue` isn’t guaranteed to be a Web Standard `Response`; it can still be the original handler value. Read `set` for the status and headers assigned through context: ```ts import { Elysia } from 'elysia' new Elysia() .onAfterResponse(({ set }) => { console.log(set.status, set.headers) }) .get('/', ({ set }) => { set.status = 201 set.headers['x-example'] = 'life-cycle' return 'Hello' }) .listen(3000) ``` `set` shows the values assigned here. Don’t assume it is a complete snapshot of every header generated by the runtime or returned directly in a `Response`. --- Source: [essential/plugin/index.md](essential/plugin/index.md) # Plugin A **plugin** is a part that is decoupled from the main instance. Every Elysia instance can run independently or be used as part of another instance. Let’s put a small app inside another one. Here, the plugin decorates a `plugin` property, and both the plugin and the main app read it. Choose either route to see its response. ```ts import { Elysia } from 'elysia' const plugin = new Elysia() .decorate('plugin', 'hi') .get('/plugin', ({ plugin }) => plugin) const app = new Elysia() .use(plugin) .get('/', ({ plugin }) => plugin) .listen(3000) ``` ## Dependency Elysia is composed of multiple mini Elysia apps that can run independently, much like microservices that communicate with each other. When an instance needs another instance’s service, you **must explicitly declare the dependency**. ```ts import { Elysia, t } from 'elysia' class Auth { getProfile() { return { name: 'Elysia User' } } } const auth = new Elysia() .decorate({ auth: new Auth() }) const main = new Elysia() .use(auth) .get('/profile', ({ auth }) => auth.getProfile()) ``` Without `.use(auth)`, the handler doesn’t know about `Auth`. Registering it on some other instance doesn’t make it available here. Here’s the missing-then-available moment in motion. Step through it with the stage buttons. How dependencies become available Play `auth`Provides `Auth`Class `user`Model `.use(auth)` `main`No dependency `/profile`Auth missing `Auth.getProfile()` 4s 1/ 4 `main` handles `/profile` and reads `Auth` from its context. Nothing provides `Auth` yet. **Decorate. **`auth.decorate('Auth', Auth)` attaches the class to the `auth` instance, and `.model(Auth.models)` registers its `user` model. **Dependency. **`main.use(auth)` declares the dependency. `main` now has `auth` in its tree. **Dependency. **That's all it takes: `Auth` is in `main`'s context and its `user` model is registered on `main`, so `/profile` can call `Auth.getProfile()`. Without `.use(auth)`, it stays missing. This is similar to **dependency injection**: each instance declares what it needs. Being explicit makes dependencies easier to track and modules easier to reuse. ### Deduplication An unnamed plugin’s hooks can be registered more than once when the plugin is shared between instances. Give the plugin a unique `name` so Elysia can deduplicate its lifecycle hooks. ```ts import { Elysia } from 'elysia' const ip = new Elysia({ name: 'ip' }) .derive('global', ({ server, request }) => ({ ip: server?.requestIP(request) })) .get('/ip', ({ ip }) => ip) const router1 = new Elysia() .use(ip) .get('/ip-1', ({ ip }) => ip) const router2 = new Elysia() .use(ip) .get('/ip-2', ({ ip }) => ip) const server = new Elysia() .use(router1) .use(router2) ``` `'global'` shares the hook with every instance that uses the plugin, directly or through another plugin; see [scope](#scope). The shared `ip` hook runs once per request, even though both routers use it. `requestIP` is available when a supporting server adapter supplies `server`; otherwise `ip` is `undefined`. Here’s that example in motion. Watch `ip` join both routers, then get registered once when `server` uses both routers. How plugins deduplicate Play `router1``/ip-1` `.use(ip)` Route merged into server `.use(router1)` `router2``/ip-2` `.use(ip)` Route merged into server `.use(router2)` `server`Named hooks `ip` `derive`Global scope `/ip-1`Route available `/ip-2`Route available 4s 1/ 6 `ip` is a named plugin with a global `derive` hook. `router1`, `router2` and `server` are plain instances. **Dependency. **`router1.use(ip)` and `router2.use(ip)` declare the dependency. Each router now has `ip` in its tree. **Deduplication. **`server.use(router1)` brings `ip` along. `ip` isn't registered on server yet, so its hook is registered. **Deduplication. **`server.use(router2)` brings `ip` again. **Deduplication. **server finds `ip` already registered by name, so the hook isn't registered twice. It runs once per request. **Scope. **The hook is `global`, so it reaches every instance that uses `ip`: both routers and server. See [Scope](#scope) below for `local` and `plugin`. For a configurable plugin, include its options as `seed`. The combination of `name` and `seed` identifies a plugin: equal configurations deduplicate, while different seeds stay distinct. ```ts import { Elysia } from 'elysia' const version = (value = 1) => new Elysia({ name: 'version', seed: value }) .get('/version', value) ``` Naming a plugin deduplicates registration; it doesn’t stop your plugin factory from running when you call it. Keep work that should happen only once outside repeated factory calls. ### Global vs explicit dependency Some dependencies make more sense globally. Others should be declared by every instance that uses them. | Use a global dependency for | Use an explicit dependency for | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | Plugins that don’t add types, such as CORS, compression, or security headers | Plugins that add types, such as macros, state, or models | | Lifecycle hooks that should apply throughout the app, such as tracing or logging | Business logic that handlers interact with, such as authentication or a database | Global examples include OpenAPI documentation, OpenTelemetry tracing, and logging. Explicit examples include stores and sessions, ORM or ODM models, authentication and database services, and feature modules such as chat or notifications. If your dependency doesn’t need to be global, prefer an explicit dependency. ## Scope Elysia lifecycle hooks are **encapsulated** within their own instance. A plugin’s hooks don’t automatically apply to the routes of an instance that uses it. In the example below, `current.use(child)` makes `child` a **descendant** of `current`. In the other direction, `parent.use(current)` makes `parent` its immediate **parent**, and `main.use(parent)` adds another parent above it. ### Scope level Choose how far a hook reaches with the first argument of `onBeforeHandle`: `local`, `plugin`, or `global`. Each step uses the same plugin tree. Only the scope changes. The hook logs `hi` without returning a value, so every route still returns its handler’s `hi` response. ### Local scope `local` applies to the current instance and its descendants. - Requests to `/current` and `/child` log `hi`. - Requests to `/parent` and `/main` don’t run the hook. This is the default scope. You can omit `'local'` and write `onBeforeHandle(() => { ... })`. ```typescript import { Elysia } from 'elysia' const child = new Elysia() .get('/child', 'hi') const current = new Elysia() .onBeforeHandle('local', () => { console.log('hi') }) .use(child) .get('/current', 'hi') const parent = new Elysia() .use(current) .get('/parent', 'hi') const main = new Elysia() .use(parent) .get('/main', 'hi') ``` ### Plugin scope `plugin` also reaches the immediate parent. - Requests to `/child`, `/current`, and `/parent` log `hi`. The hook stops at `parent`, so `/main` doesn’t run it. ```typescript import { Elysia } from 'elysia' const child = new Elysia() .get('/child', 'hi') const current = new Elysia() .onBeforeHandle('plugin', () => { console.log('hi') }) .use(child) .get('/current', 'hi') const parent = new Elysia() .use(current) .get('/parent', 'hi') const main = new Elysia() .use(parent) .get('/main', 'hi') ``` ### Global scope `global` continues through every parent that uses the plugin. All four routes log `hi`, including `/main`. ```typescript import { Elysia } from 'elysia' const child = new Elysia() .get('/child', 'hi') const current = new Elysia() .onBeforeHandle('global', () => { console.log('hi') }) .use(child) .get('/current', 'hi') const parent = new Elysia() .use(current) .get('/parent', 'hi') const main = new Elysia() .use(parent) .get('/main', 'hi') ``` The routes that run the hook in each scope are: | Scope | `/child` | `/current` | `/parent` | `/main` | | -------- | -------- | ---------- | --------- | ------- | | `local` | Yes | Yes | No | No | | `plugin` | Yes | Yes | Yes | No | | `global` | Yes | Yes | Yes | Yes | Registration order still matters. A hook applies to routes and plugins registered after it. Sharing a hook through `.use()` doesn’t apply it to routes registered earlier on the receiving instance. Use the diagram to trace the same hook through the plugin tree. How hook scope reaches the tree Play ### Scope level Overview 1. `main``/main`Not shown yet 2. `parent``/parent`Not shown yet 3. `current``/current`Not shown yet 4. `child``/child`Not shown yet 4s 1/ 4 `child` sits inside `current`, which sits inside `parent`, which sits inside `main`. `current` has a `beforeHandle` hook, and its scope decides which routes run it. **Local. **`local` is the default: the current instance and its descendants. `/current` and `/child` run the hook. **Plugin. **`plugin` adds the immediate parent. `/parent` runs the hook too. **Global. **`global` reaches every parent that uses it. `/main` runs the hook as well. To promote hooks already registered on an instance, see [Scope cast](#instance-as) for `.as('plugin')` and `.as('global')`. ## Config To make a plugin reusable, let callers configure the parts that need to change. A function can accept options and return an Elysia instance. ```ts import { Elysia } from 'elysia' const version = (version = 1) => new Elysia() .get('/version', version) const app = new Elysia() .use(version(1)) .listen(3000) ``` Here `/version` returns `1`. Pass another value to create a differently configured plugin. ### Functional callback Prefer a new plugin instance when you can. Functional callbacks receive the main instance directly, which makes encapsulation and scope harder to reason about. To define one, write a function that accepts Elysia and returns the extended instance. ```ts import { Elysia } from 'elysia' const plugin = (app: Elysia) => app .state('counter', 0) .get('/plugin', () => 'Hi') const app = new Elysia() .use(plugin) .get('/counter', ({ store: { counter } }) => counter) .listen(3000) ``` The callback assigns properties directly to the main instance. `/plugin` returns `Hi`, and `/counter` returns `0`. In Elysia 2, the old `app.store` instance getter is gone. Use `store` from the handler’s context to read state. Choose the clearer shape You don’t need to choose a callback for performance. Elysia instances are lightweight, and using a separate instance also gives TypeScript a clearer boundary for inference. ## Guard A guard applies hooks and schemas to multiple routes at once. Put the guarded routes inside its callback. ```ts import { Elysia, t } from 'elysia' import { signUp, signIn, isUserExists } from './auth' new Elysia() .guard( { body: t.Object({ username: t.String(), password: t.String() }) }, (app) => app .post('/sign-up', ({ body }) => signUp(body)) .post('/sign-in', { beforeHandle: isUserExists }, ({ body }) => signIn(body)) ) .get('/', 'hi') .listen(3000) ``` The body schema applies to `/sign-up` and `/sign-in`, but not `/`. The imported functions contain your application’s account logic. | Path | Has body validation | | ---------- | ------------------- | | `/sign-up` | Yes | | `/sign-in` | Yes | | `/` | No | Guard accepts the same schema and hook options as a route. The difference is that it applies them to all routes in its callback. The example is equivalent to repeating those options on each route: ```ts import { Elysia, t } from 'elysia' import { signUp, signIn, isUserExists } from './auth' new Elysia() .post('/sign-up', { body: t.Object({ username: t.String(), password: t.String() }) }, ({ body }) => signUp(body)) .post('/sign-in', { beforeHandle: isUserExists, body: t.Object({ username: t.String(), password: t.String() }) }, ({ body }) => signIn(body)) .get('/', 'hi') .listen(3000) ``` In Elysia 2, a route’s schema replaces the corresponding inherited guard schema by default. Add `schema: 'merge'` to the guard if both schemas must be satisfied. See [validation](/essential/validation/#merge) for more. ### Grouped guard A [group](/essential/route/#group) can take three arguments: a prefix, guard options, and an app callback. Start with a nested group and guard: ```ts import { Elysia, t } from 'elysia' new Elysia() .group('/v1', (app) => app.guard( { body: t.Literal('Rikuhachima Aru') }, (app) => app.post('/student', ({ body }) => body) ) ) .listen(3000) ``` Move the guard options into the group’s second argument to get the same behavior with less nesting: ```ts import { Elysia, t } from 'elysia' new Elysia() .group( '/v1', { body: t.Literal('Rikuhachima Aru') }, (app) => app.post('/student', ({ body }) => body) ) .listen(3000) ``` Both versions register `POST /v1/student`. A JSON string body of `"Rikuhachima Aru"` passes validation and is returned by the handler. ## Scope cast To apply hooks to a parent, choose how much you want to share: 1. ### Inline Define an inline scope for a single lifecycle hook. 2. ### Guard Use guard scope for a set of hooks and schemas. 3. ### Instance Use instance scope to promote its existing hooks and schemas. ### Inline as Pass a scope string before the callback to share one lifecycle hook. ```ts import { Elysia } from 'elysia' const plugin = new Elysia() .derive('plugin', () => ({ hi: 'ok' })) .get('/child', ({ hi }) => hi) const main = new Elysia() .use(plugin) .get('/parent', ({ hi }) => hi) ``` The derived `hi` property is available to both routes. This form changes only this hook’s scope. ### Guard as Pass a scope string before the guard options to share multiple hooks and schemas together. ```ts import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard('plugin', { response: t.String(), beforeHandle() { console.log('ok') } }) .get('/child', 'ok') const main = new Elysia() .use(plugin) .get('/parent', 'hello') ``` The response schema and hook apply to both routes. For a typed derived property, use `.derive('plugin', callback)` instead of putting it in this guard. ### Instance as `.as()` promotes the hooks and schema scopes already registered on the current instance. ```ts import { Elysia } from 'elysia' const plugin = new Elysia() .derive(() => ({ hi: 'ok' })) .get('/child', ({ hi }) => hi) .as('plugin') const main = new Elysia() .use(plugin) .get('/parent', ({ hi }) => hi) ``` The `plugin` scope reaches only one parent. To carry it another level, promote the parent instance as well. ```ts import { Elysia, t } from 'elysia' const plugin = new Elysia() .guard({ response: t.String() }) .onBeforeHandle(() => { console.log('called') }) .get('/child', 'ok') .as('plugin') const instance = new Elysia() .use(plugin) .get('/parent', 'still ok') .as('plugin') const parent = new Elysia() .use(instance) .get('/grandparent', 'also ok') ``` Now the string response schema reaches `/grandparent`. Returning a number from any of these routes violates the schema; TypeScript will point it out, and response validation checks it at runtime. ## Lazy load Modules are eagerly loaded by default, so synchronous plugins are registered before the server starts. A computationally heavy or blocking module can delay startup. An asynchronous plugin registers its routes once its promise settles. `.listen()` and `app.fetch` wait for every pending module before serving requests; only `app.handle()` can run earlier. ### Deferred module A deferred module is an async plugin. This example waits for your file list before registering its routes: plugin.ts ```ts import { Elysia, file } from 'elysia' import { loadAllFiles } from './files' export const loadStatic = async (app: Elysia) => { const files = await loadAllFiles() for (const asset of files) { app.get(asset, file(asset)) } return app } ``` Here, `loadAllFiles` returns an array of local asset paths that are also suitable route paths. In your main file: index.ts ```ts import { Elysia } from 'elysia' import { loadStatic } from './plugin' const app = new Elysia() .use(loadStatic) .listen(3000) ``` ### Lazy load module A dynamic `import()` can load a synchronous or asynchronous plugin lazily. Export the plugin as the module’s default export: plugin.ts ```ts import { Elysia } from 'elysia' export default new Elysia() .get('/plugin', 'Hi') ``` index.ts ```ts import { Elysia } from 'elysia' const app = new Elysia() .use(import('./plugin')) .listen(3000) ``` This is useful when loading the module itself is expensive. `.listen()` waits for `app.modules` on its own, so the server never starts with a route missing. Await `app.modules` yourself only when you call `app.handle()` directly, as in tests. ### Testing In tests, await `app.modules` before requesting a deferred or lazy-loaded route. ```ts import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' describe('Modules', () => { it('inline async', async () => { const app = new Elysia() .use(async (app) => app.get('/async', 'async')) await app.modules const response = await app.handle( new Request('http://localhost/async') ) expect(await response.text()).toBe('async') }) }) ``` --- Source: [essential/route/index.md](essential/route/index.md) # Routing A [REST API](https://en.wikipedia.org/wiki/REST) takes a path and method to decide which handler to run. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', 'Elysia') .get('/hi', 'Hello!') .listen(3000) ``` > Calling `.listen` will start a web server at port 3000. We can access our server by going to . - `/` with the `GET` method will return `Elysia`. - `/hi` with the `GET` method will return `Hello!`. - Any other path will return a `404`. Visiting a URL in your browser sends a `GET` request. ## Path types Paths can be grouped into 3 types: 1. ### Static paths exact match 2. ### Dynamic paths some parts are dynamic, and can be captured as parameters 3. ### Wildcards match the rest of a path ## Static path A static path is an exact match, with no surprises. - `/` matches only `/` - `/hi` matches only `/hi` ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/hi', 'Hi') .listen(3000) ``` ## Dynamic path Add `:` followed by a name to capture its value. `/user/:id` matches `/user/42`, `/user/abc`, and `/user/hello-world`. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', () => 'hello user') .listen(3000) ``` ### Access params Read the matched value from `params` in your handler. When `/user/42/profile` matches `/user/:id/profile`, `params.id` is `42`. The handler returns it as the response. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', () => 'hello user') .get('/user/:id/profile', ({ params: { id } }) => id) .listen(3000) ``` ### Optional params Add `?` after a parameter name to make it optional. `/user/:id?/profile` matches both `/user/42/profile` and `/user/profile`. The value will be `undefined` if the parameter is not present. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', () => 'hello user') .get('/user/:id?/profile', ({ params: { id } }) => id) .listen(3000) ``` ## Wildcard Adding `*` at the end of a path will match any path after it. `/user/*` matches multi-segment paths such as `/user/hello-world/profile`. In this example, `/user/42` still goes to `/user/:id`, because dynamic paths take priority over wildcards (see [path priority](#path-priority)). For `/user/hello-world/profile`, `params['*']` is `hello-world/profile`. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', () => 'hello user') .get('/user/*', ({ params }) => params['*']) .listen(3000) ``` ## Path priority Elysia will resolve paths in the following order: 1. static paths 2. dynamic paths 3. wildcards ```ts import { Elysia } from 'elysia' new Elysia() .get('/user/1', 'Hello Elysia') .get('/user/:id', ({ params: { id } }) => id) .get('/user/*', ({ params }) => params['*']) .listen(3000) ``` *** ## HTTP verbs [HTTP verbs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) are the methods of requests, such as `GET`, `POST`, or `PUT`. ### GET Get data from the server. Browsers use it to visit a page. ### HEAD Similar to **GET**, but only returns the headers, not the body. ### POST Send data to the server, often to create a resource. ### PUT Replace a resource with the data you send. ### PATCH Update part of a resource. ### DELETE Delete a resource. To define a standard method, use the corresponding method on the Elysia instance: ```ts import { Elysia } from 'elysia' new Elysia() .get('/', 'Hi GET!') .post('/', 'Hi POST!') .put('/', 'Hi PUT!') .patch('/', 'Hi PATCH!') .delete('/', 'Hi DELETE!') .listen(3000) ``` ### Custom method Define using `.method` with the method name as the first argument. The rest is the same as other methods. ```ts import { Elysia } from 'elysia' new Elysia() .method('PROPFIND', '/', 'Hi PROPFIND!') .listen(3000) ``` ### All methods Catch all methods using `.all`: ```ts import { Elysia } from 'elysia' new Elysia() .all('/', 'Hi ALL!') .listen(3000) ``` *** ## Group Many paths with the same prefix can be grouped using `.group`: ```ts import { Elysia } from 'elysia' new Elysia() .group('/user', (app) => app .post('/sign-in', 'Sign in') .get('/profile', 'Profile') ) .listen(3000) ``` It’s similar to writing `/user/sign-in` and `/user/profile` separately. ### Prefix If the entire instance has the same prefix, we can define it in the constructor: ```ts import { Elysia } from 'elysia' new Elysia({ prefix: '/user' }) .post('/sign-in', 'Sign in') .get('/profile', 'Profile') ``` This is different from `.group` because the prefix is applied to all paths in the instance while group applies to the paths in the callback. --- Source: [essential/validation/index.md](essential/validation/index.md) # Validation Elysia supports declarative schema validation. Describe what your data should look like and Elysia will handle the rest. ## Start with a parameter Without a schema, `/id/:id` captures a string. - `/id/42` gives the handler `"42"` - `/id/hello` gives it `"hello"` ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', ({ params: { id } }) => id) .listen(3000) ``` ## Add a schema By adding `t.Number()` to the `params` schema, Elysia will convert a numeric path value and validate it. - `/id/42` gives the handler the number `42` - `/id/hello` fails validation with status `422` ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', { params: t.Object({ id: t.Number() }) }, ({ params: { id } }) => id) .listen(3000) ``` ## Validate another field Add `query` to require a `name` query parameter too. - `/id/42?name=Elysia` returns `{ id: 42, name: 'Elysia' }`. - `/id/42` fails validation because `name` is missing. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', { params: t.Object({ id: t.Number() }), query: t.Object({ name: t.String() }) }, ({ params: { id }, query: { name } }) => ({ id, name })) .listen(3000) ``` ## Check the response Add `response` to enforce the shape of the data returned by the handler. Elysia would also clean up any extra fields in the response, if the handler returned them. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', { params: t.Object({ id: t.Number() }), query: t.Object({ name: t.String() }), response: t.Object({ id: t.Number(), name: t.String() }) }, ({ params: { id }, query: { name } }) => ({ id, name })) .listen(3000) ``` ## TypeBox Elysia provides `t`, a named export from `elysia`, as a default schema builder. It’s a wrapper around [TypeBox](https://github.com/sinclairzx81/typebox) with extra features for API validation. These schemas support runtime checks, TypeScript inference, and schema generation for [OpenAPI](/plugins/openapi/). You don’t need to write a separate interface for the same data. ## Standard Schema You can also use a library that implements [Standard Schema](https://github.com/standard-schema/standard-schema), including compatible versions of **Zod**, **Valibot**, ArkType, Effect Schema, Yup, and Joi and use them interchangeably. ```ts import { Elysia } from 'elysia' import { z } from 'zod' import * as v from 'valibot' new Elysia() .get('/id/:id', { params: z.object({ id: z.coerce.number() }), query: v.object({ name: v.literal('Lilith') }) }, ({ params: { id }, query: { name } }) => ({ id, name })) .listen(3000) ``` `/id/42?name=Lilith` passes both schemas. Use your chosen library’s coercion API when you want to turn a URL string into another type. ## Schema type Choose the property that matches the data you want to check: ### [Body schema](#body) [Body checks the parsed HTTP request body, such as JSON or form fields.](#body) ### [Query schema](#query) [Query checks the query string after `?` in the URL.](#query) ### [Params schema](#params) [Params checks values captured from the route’s path.](#params) ### [Headers schema](#headers) [Headers checks incoming request headers.](#headers) ### [Cookie schema](/patterns/cookie/#cookie-schema) [Cookie checks incoming cookie values.](/patterns/cookie/#cookie-schema) ### [Response schema](#response) [Response checks data returned by the handler.](#response) Provide these properties in the route’s second argument, before its handler: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', { query: t.Object({ name: t.String() }), params: t.Object({ id: t.Number() }) }, () => 'Hello World!') .listen(3000) ``` | URL | Query schema | Params schema | | -------------------- | ------------ | ------------- | | `/id/a` | Fails | Fails | | `/id/1?name=Elysia` | Passes | Passes | | `/id/1?alias=Elysia` | Fails | Passes | | `/id/a?name=Elysia` | Passes | Fails | | `/id/a?alias=Elysia` | Fails | Fails | Both schemas must pass for the handler to run. The table describes each check independently; the error response may report the first failing part. ## Body An [HTTP Message](https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages) body. It can be JSON, form fields, or a file. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/body', { body: t.Object({ name: t.String() }) }, ({ body }) => body) .listen(3000) ``` | JSON body | Validation | | ----------------------- | ---------- | | `{ "name": "Elysia" }` | Passes | | `{ "name": 1 }` | Fails | | `{ "alias": "Elysia" }` | Fails | | No body | Fails | ### File Use [t.File()](/patterns/typebox/#file) for a single file and [t.Files()](/patterns/typebox/#files) for multiple files. A `type` check needs a file type detector. Install `file-type` and register it once with `setFileTypeDetector`; without a detector, every upload checked against `type` fails validation. ```ts import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) new Elysia() .post('/body', { body: t.Object({ file: t.File({ type: 'image/*' }), multipleFiles: t.Files() }) }, ({ body }) => ({ file: body.file.name, count: body.multipleFiles.length })) .listen(3000) ``` ### File (Standard Schema) Standard Schema libraries don’t automatically get Elysia’s file checks. Add a refinement using the asynchronous `fileType()` helper and explicitly select multipart parsing: ```ts import { Elysia, fileType, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' import { z } from 'zod' setFileTypeDetector(fileTypeFromBlob) new Elysia() .post('/body', { parse: 'formdata', body: z.object({ file: z.file().refine( (file) => fileType(file, 'image/jpeg'), 'Expected a JPEG file' ) }) }, ({ body }) => ({ name: body.file.name })) ``` The registered detector supplies the byte check here too. A validator that only reads `file.type` doesn’t establish what the uploaded file actually contains. ## Query Query parameters follow `?` in a URL. Describe them with an object schema: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/query', { query: t.Object({ name: t.String() }) }, ({ query }) => query) .listen(3000) ``` | Request | Validation | | ----------------------------------- | -------------------------- | | `/query?name=Elysia` | Passes | | `/query?name=1` | Passes; `name` is a string | | `/query?alias=Elysia` | Fails | | `/query?name=ElysiaJS&alias=Elysia` | Passes | | `/query` | Fails | ### Coercion Elysia converts applicable TypeBox schemas on `query` automatically. For example, `t.Number()` accepts a numeric string and gives the handler a number: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/query', { query: t.Object({ id: t.Number() }) }, ({ query: { id } }) => id) .listen(3000) ``` | Request | Result | | ---------------- | ------------------------------ | | `/query?id=1` | Number `1` | | `/query?id=salt` | Validation error, status `422` | See [Elysia’s type behavior](/patterns/typebox/#elysia-behavior) for other automatic conversions. ### Array Declare an array schema to validate each value and support comma-separated input: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/query', { query: t.Object({ name: t.Array(t.String()), squad: t.String() }) }, ({ query }) => query) .listen(3000) ``` Both requests below produce `{ name: ['rapi', 'anis', 'neon'], squad: 'counter' }` with this schema. #### nuqs format Use commas to separate values, as in the [nuqs](https://nuqs.47ng.com) array format: ```text /query?name=rapi,anis,neon&squad=counter ``` The array schema tells Elysia to split `name`. Without one, a comma-separated value remains a string. #### HTML form format Repeat a key to send several values, as an HTML form does for inputs with the same name: ```text /query?name=rapi&name=anis&name=neon&squad=counter ``` Declare the array schema to retain and validate every repeated value. Without it, an ordinary query property is a single string; a repeated key keeps its last value. ## Params Path parameters capture values from a route such as `/id/:id`. Use an object schema to validate the captured fields: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/id/:id', { params: t.Object({ id: t.Number() }) }, ({ params }) => params) ``` | Request | Result | | ------- | ------------------------------ | | `/id/1` | `{ "id": 1 }` | | `/id/a` | Validation error, status `422` | ## Headers Use `headers` to require particular incoming headers: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/headers', { headers: t.Object({ authorization: t.String() }) }, ({ headers }) => headers.authorization) ``` Header schemas allow additional properties by default. Your client can still send headers such as `accept`; the fields you’ve declared must satisfy their schemas. If you want to reject any extra headers, set `additionalProperties: false`: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/headers', { headers: t.Object( { authorization: t.String() }, { additionalProperties: false } ) }, ({ headers }) => headers.authorization) ``` ## Response Use `response` to check the data returned by your handler: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/response', { response: t.Object({ name: t.String() }) }, () => ({ name: 'Jane Doe (ZZZ)' })) ``` TypeScript checks the handler’s return type, and Elysia validates the response at runtime. ### Response per status Map status codes to schemas when different outcomes have different response shapes: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/response', { response: { 200: t.Object({ name: t.String() }), 400: t.Object({ error: t.String() }) } }, ({ query, status }) => { if (query.fail === 'true') return status(400, { error: 'Something went wrong' }) return { name: 'Jane Doe (ZZZ)' } }) ``` `/response` returns the `200` shape. `/response?fail=true` returns the `400` shape. Use `status()` to preserve the relationship between the status code and its body type. ## Validation errors Customize a schema’s [validation message](/patterns/error-handling/#schema-messages), handle failures with an [error hook](/patterns/error-handling/#error-hook), or inspect the [error list](/patterns/error-handling/#error-list). For named schemas shared across routes and OpenAPI documentation, see [reference models](/patterns/openapi/#reference-model). ## TypeScript Use `UnwrapSchema` to get a schema’s inferred TypeScript type: ```ts import { t, type UnwrapSchema } from 'elysia' const MyType = t.Object({ hello: t.Literal('Elysia') }) type MyType = UnwrapSchema ``` A schema can supply runtime validation, coercion, TypeScript types, and OpenAPI metadata. Keep the definition in one place, and let each use follow it. ## Guard Use `.guard()` to apply a schema to several routes. It affects routes registered after it: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/none', () => 'hi') .guard({ query: t.Object({ name: t.String() }) }) .get('/query', ({ query: { name } }) => name) .listen(3000) ``` | Request | Result | | --------------- | ------------------------------ | | `/none` | `hi` | | `/none?name=a` | `hi` | | `/query` | Validation error, status `422` | | `/query?name=a` | `a` | For the same schema property, a later guard takes precedence over an earlier one. A route’s own schema takes precedence over an inherited guard schema. ### Guard schema type Guards support two ways to handle overlapping schemas. #### Override (default) The nearer schema replaces the inherited schema for that property. For example, a route’s `body` replaces a guard’s `body`; it doesn’t merge their fields. ```ts import { Elysia, t } from 'elysia' new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/age', { body: t.Object({ age: t.Number() }) }, ({ body }) => body) ``` `{ "age": 18 }` satisfies `/age` without a `name` field. #### Merge Set `schema: 'merge'` when both the guard and route schemas must be satisfied: ```ts import { Elysia, t } from 'elysia' new Elysia() .guard({ schema: 'merge', response: t.Object({ title: t.String() }) }) .get('/article', { response: t.Object({ title: t.String(), author: t.String() }) }, () => ({ title: 'Hello Elysia', author: 'Nagisa' })) ``` The response must satisfy the guard and the route schema. Merge accumulates their constraints instead of replacing the inherited schema. --- Source: [integrations/ai-sdk/index.md](integrations/ai-sdk/index.md) # Integration with AI SDK Elysia provides support for response streaming with ease, allowing you to integrate with [Vercel AI SDKs](https://vercel.com/docs/ai) seamlessly. ## Response streaming Elysia supports continuous streaming of a `ReadableStream` and `Response`, allowing you to return streams directly from the AI SDKs. Choose one of the return statements shown in each example. ```ts import { Elysia } from 'elysia' import { streamText } from 'ai' import { openai } from '@ai-sdk/openai' new Elysia().get('/', () => { const stream = streamText({ model: openai('gpt-5'), system: 'You are Yae Miko from Genshin Impact', prompt: 'Hi! How are you doing?' }) // Just return a ReadableStream return stream.textStream // UI Message Stream is also supported return stream.toUIMessageStream() }) ``` Elysia will handle the stream automatically, allowing you to use it in various ways. ## Server-sent events Elysia also supports Server-Sent Events for streaming responses by simply wrapping a `ReadableStream` with the `sse` function. ```ts import { Elysia, sse } from 'elysia' import { streamText } from 'ai' import { openai } from '@ai-sdk/openai' new Elysia().get('/', () => { const stream = streamText({ model: openai('gpt-5'), system: 'You are Yae Miko from Genshin Impact', prompt: 'Hi! How are you doing?' }) // Each chunk will be sent as a Server Sent Event return sse(stream.textStream) // UI Message Stream is also supported return sse(stream.toUIMessageStream()) }) ``` ## As a response If you don’t need type safety from the stream for further usage with [Eden](/eden), you can return the stream directly as a response. ```ts import { Elysia } from 'elysia' import { streamText } from 'ai' import { openai } from '@ai-sdk/openai' new Elysia().get('/', () => { const stream = streamText({ model: openai('gpt-5'), system: 'You are Yae Miko from Genshin Impact', prompt: 'Hi! How are you doing?' }) return stream.toTextStreamResponse() // UI Message Stream Response will use SSE return stream.toUIMessageStreamResponse() }) ``` ## Manual streaming If you want to have more control over the streaming, you can use a generator function to yield the chunks manually. ```ts import { Elysia, sse } from 'elysia' import { streamText } from 'ai' import { openai } from '@ai-sdk/openai' new Elysia().get('/', async function* () { const stream = streamText({ model: openai('gpt-5'), system: 'You are Yae Miko from Genshin Impact', prompt: 'Hi! How are you doing?' }) for await (const data of stream.textStream) yield sse({ data, event: 'message' }) yield sse({ event: 'done' }) }) ``` ## Fetch If the AI SDK doesn’t support the model you’re using, you can still use the `fetch` function to call the provider’s HTTP API and stream the response directly. ```ts import { Elysia } from 'elysia' new Elysia().get('/', () => { return fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify({ model: 'gpt-5', stream: true, messages: [ { role: 'system', content: 'You are Yae Miko from Genshin Impact' }, { role: 'user', content: 'Hi! How are you doing?' } ] }) }) }) ``` Elysia will proxy the response from the global `fetch` with streaming support automatically. *** For additional information, please refer to the [AI SDK documentation](https://ai-sdk.dev/docs/introduction). --- Source: [integrations/astro/index.md](integrations/astro/index.md) # Integration with Astro With [Astro Endpoint](https://docs.astro.build/en/core-concepts/endpoints/), we can run Elysia on Astro directly. Install an [Astro server adapter](https://docs.astro.build/en/guides/on-demand-rendering/) for your deployment target. Then: 1. Set **output** to **server** in **astro.config.mjs** astro.config.mjs ```javascript import { defineConfig } from 'astro/config' // https://astro.build/config export default defineConfig({ output: 'server' }) ``` 2. Create **src/pages/\[…slugs].ts** 3. Create or import an existing Elysia server in **\[…slugs].ts** 4. Export the handler with the name of the method you want to expose src/pages/\[...slugs].ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia() .get('/api', () => 'hi') .post('/api', { body: t.Object({ name: t.String() }) }, ({ body }) => body) const handle = ({ request }: { request: Request }) => app.handle(request) export const GET = handle export const POST = handle ``` Elysia will work normally as expected because of WinterTC compliance. We recommend [Astro on Bun](https://docs.astro.build/en/recipes/bun) when running on Bun. Tip You can run an Elysia server without running Astro on Bun thanks to WinterTC support. With this approach, you can have co-location of both frontend and backend in a single repository and have End-to-end type-safety with Eden. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Prefix If you place an Elysia server not in the root of the pages directory, you need to annotate the prefix on the Elysia server. For example, if you place the Elysia server in **src/pages/api/\[…slugs].ts**, you need to annotate the prefix as **/api** on the Elysia server. src/pages/api/\[...slugs].ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia({ prefix: '/api' }) .get('/', () => 'hi') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) const handle = ({ request }: { request: Request }) => app.handle(request) export const GET = handle export const POST = handle ``` This will ensure that Elysia routing works properly wherever you place it. Please refer to [Astro Endpoints](https://docs.astro.build/en/core-concepts/endpoints/) for more information. --- Source: [integrations/better-auth/index.md](integrations/better-auth/index.md) # Better Auth Better Auth is a framework-agnostic authentication (and authorization) framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities. We recommend going through the [Better Auth basic setup](https://www.better-auth.com/docs/installation) before going through this page. Our basic setup will look like this: auth.ts ```ts import { betterAuth } from 'better-auth' import { Pool } from 'pg' export const auth = betterAuth({ database: new Pool() }) ``` ## Handler After setting up the Better Auth instance, we can mount it to Elysia via [mount](/patterns/mount). We need to mount the handler to an Elysia endpoint. index.ts ```ts import { Elysia } from 'elysia' import { auth } from './auth' const app = new Elysia() .mount(auth.handler) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Then we can access Better Auth at `http://localhost:3000/api/auth`. ### Custom endpoint We recommend setting a prefix path when using [mount](/patterns/mount). index.ts ```ts import { Elysia } from 'elysia' import { auth } from './auth' const app = new Elysia() .mount('/auth', auth.handler) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Then we can access Better Auth at `http://localhost:3000/auth/api/auth`. But the URL looks redundant, so we can customize the `/api/auth` prefix to something else in the Better Auth instance. ```ts import { betterAuth } from 'better-auth' import { Pool } from 'pg' export const auth = betterAuth({ database: new Pool(), basePath: '/api' }) ``` Then we can access Better Auth at `http://localhost:3000/auth/api`. Unfortunately, we can’t set the `basePath` of a Better Auth instance to be empty or `/`. ## OpenAPI Better Auth supports `openapi` with `better-auth/plugins`. However, if we are using [@elysia/openapi](/plugins/openapi), you might want to extract the documentation from the Better Auth instance. First, we need to add the `openAPI` plugin to our Better Auth instance: auth.ts ```ts import { betterAuth } from 'better-auth' import { openAPI } from 'better-auth/plugins' import { Pool } from 'pg' export const auth = betterAuth({ database: new Pool(), plugins: [openAPI()] }) ``` Tip The `openAPI()` plugin is required for `auth.api.generateOpenAPISchema` to be available. Without it, you will get a type error: `Property 'generateOpenAPISchema' does not exist`. Then we may extract the OpenAPI schema with the following code: auth.ts ```ts let _schema: ReturnType const getSchema = async () => (_schema ??= auth.api.generateOpenAPISchema()) export const OpenAPI = { getPaths: (prefix = '/api/auth') => getSchema().then(({ paths }) => { const reference: typeof paths = Object.create(null) for (const path of Object.keys(paths)) { const key = prefix + path reference[key] = paths[path] for (const method of Object.keys(paths[path])) { const operation = (reference[key] as any)[method] operation.tags = ['Better Auth'] } } return reference }) as Promise, components: getSchema().then(({ components }) => components) as Promise } as const ``` Then use it in our Elysia instance that uses `@elysia/openapi`: ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' import { OpenAPI } from './auth' const app = new Elysia().use( openapi({ documentation: { components: await OpenAPI.components, paths: await OpenAPI.getPaths() } }) ) ``` ## CORS To configure CORS, you can use the `cors` plugin from `@elysia/cors`. ```ts import { Elysia } from 'elysia' import { cors } from '@elysia/cors' import { auth } from './auth' const app = new Elysia() .use( cors({ origin: 'http://localhost:3001', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], credentials: true, allowedHeaders: ['Content-Type', 'Authorization'] }) ) .mount(auth.handler) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` ## Macro You can use [macro](/patterns/macro/) with [derive](/patterns/macro#derive) to provide session and user information before passing it to the view. ```ts import { Elysia } from 'elysia' import { auth } from './auth' // user middleware (compute user and session and pass to routes) const betterAuth = new Elysia({ name: 'better-auth' }) .mount(auth.handler) .macro({ auth: { async derive({ status, request: { headers } }) { const session = await auth.api.getSession({ headers }) if (!session) return status(401, 'Unauthorized') return { user: session.user, session: session.session } } } }) const app = new Elysia() .use(betterAuth) .get('/user', { auth: true }, ({ user }) => user) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` This lets you access `user` and `session` in routes that enable `auth: true`. --- Source: [integrations/cheat-sheet/index.md](integrations/cheat-sheet/index.md) # Cheat sheet Here is a quick overview of common Elysia patterns ## Hello world A simple hello world ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'Hello World') .listen(3000) ``` ## Custom HTTP method Define routes using custom HTTP methods/verbs See [Route](/essential/route#custom-method) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/hi', () => 'Hi') .post('/hi', () => 'From Post') .put('/hi', () => 'From Put') .method('M-SEARCH', '/hi', () => 'Custom Method') .listen(3000) ``` ## Path parameter Using dynamic path parameters See [Path](/essential/route/#path-types) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/id/:id', ({ params: { id } }) => id) .get('/rest/*', () => 'Rest') .listen(3000) ``` ## Return JSON Elysia converts responses to JSON automatically See [Handler](/essential/handler) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/json', () => { return { hello: 'Elysia' } }) .listen(3000) ``` ## Return a file Return `file()` for a single file. Use `form()` to return files in a multipart form-data response. The form must be a 1-level deep object See [Handler](/essential/handler/#return-a-file) ```typescript import { Elysia, file, form } from 'elysia' new Elysia() .get('/cat', file('public/cat.jpg')) .get('/form', () => { return form({ hello: 'Elysia', image: file('public/cat.jpg') }) }) .listen(3000) ``` ## Header and status Set a custom header and a status code See [Handler](/essential/handler) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ set, status }) => { set.headers['x-powered-by'] = 'Elysia' return status(418, "I'm a teapot") }) .listen(3000) ``` ## Group Define a prefix once for subroutes See [Group](/essential/route#group) ```typescript import { Elysia } from 'elysia' new Elysia() .get("/", () => "Hi") .group("/auth", app => { return app .get("/", () => "Hi") .post("/sign-in", ({ body }) => body) .put("/sign-up", ({ body }) => body) }) .listen(3000) ``` ## Schema Enforce a data type on a route See [Validation](/essential/validation) ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/mirror', { body: t.Object({ username: t.String(), password: t.String() }) }, ({ body: { username } }) => username) .listen(3000) ``` ## File upload See [Validation#file](/essential/validation/#file). A `type` check needs a file type detector: install `file-type` and register it with `setFileTypeDetector`, otherwise every upload checked against `type` fails validation. ```typescript import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) new Elysia() .post('/body', { body: t.Object({ file: t.File({ type: 'image/*' }), multipleFiles: t.Files() }) }, ({ body }) => body) .listen(3000) ``` ## Lifecycle hook Intercept Elysia events in order See [Lifecycle](/essential/life-cycle) ```typescript import { Elysia, t } from 'elysia' new Elysia() .onRequest(() => { console.log('On request') }) .onBeforeHandle(() => { console.log('Before handle') }) .post('/mirror', { body: t.Object({ username: t.String(), password: t.String() }), afterHandle: () => { console.log("After handle") } }, ({ body }) => body) .listen(3000) ``` ## Guard Enforce a data type on subroutes See [Guard](/essential/plugin/#guard) ```typescript // @errors: 2345 import { Elysia, t } from 'elysia' new Elysia() .guard({ response: t.String() }, (app) => app .get('/', () => 'Hi') // Invalid: will throws error, and TypeScript will report error .get('/invalid', () => 1) ) .listen(3000) ``` ## Custom context Add custom variables to route context See [Context](/essential/handler#context) ```typescript import { Elysia } from 'elysia' new Elysia() .state('version', 1) .decorate('getDate', () => Date.now()) .get('/version', ({ getDate, store: { version } }) => `${version} ${getDate()}`) .listen(3000) ``` ## Redirect Redirect responses See [Handler](/essential/handler#redirect) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'hi') .get('/redirect', ({ redirect }) => { return redirect('/') }) .listen(3000) ``` ## Plugin Create a separate instance See [Plugin](/essential/plugin) ```typescript import { Elysia } from 'elysia' const plugin = new Elysia() .state('plugin-version', 1) .get('/hi', () => 'hi') new Elysia() .use(plugin) .get('/version', ({ store }) => store['plugin-version']) .listen(3000) ``` ## WebSocket Create a realtime connection using WebSocket See [Web Socket](/patterns/websocket) ```typescript import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ping', { message(ws, message) { ws.send('hello ' + message) } }) .listen(3000) ``` ## OpenAPI documentation Create interactive documentation using Scalar (or optionally Swagger) See [openapi](/plugins/openapi) ```typescript import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' const app = new Elysia() .use(openapi()) .listen(3000) console.log(`View documentation at "${app.server!.url}openapi" in your browser`); ``` ## Unit test Write a unit test for your Elysia app See [Unit Test](/patterns/unit-test) test/index.test.ts ```typescript import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' describe('Elysia', () => { it('return a response', async () => { const app = new Elysia().get('/', () => 'hi') const response = await app .handle(new Request('http://localhost/')) .then((res) => res.text()) expect(response).toBe('hi') }) }) ``` ## Custom body parser Create custom logic for parsing bodies See [Parse](/essential/life-cycle#parse) ```typescript import { Elysia } from 'elysia' new Elysia() .onParse(({ request, contentType }) => { if (contentType === 'application/custom-type') return request.text() }) ``` ## GraphQL Create a custom GraphQL server using GraphQL Yoga or Apollo See [GraphQL Yoga](/plugins/graphql-yoga) ```typescript import { Elysia } from 'elysia' import { yoga } from '@elysia/graphql-yoga' const app = new Elysia() .use( yoga({ typeDefs: /* GraphQL */` type Query { hi: String } `, resolvers: { Query: { hi: () => 'Hello from Elysia' } } }) ) .listen(3000) ``` --- Source: [integrations/cloudflare-worker/index.md](integrations/cloudflare-worker/index.md) # Cloudflare Worker Elysia can run on Cloudflare Workers using Web Standard requests and responses. For Elysia v2, there’s no need for Cloudflare adapter. You can either: 1. compile route handlers at start time 2. compile route handlers at build time ### Compile at start time The easiest way to get started is to compile them at start time. 1. add `compile` before exporting the app: src/index.ts ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', () => 'Hello Cloudflare Worker!') .compile() export default { fetch: (request: Request) => app.handle(request) } ``` However, there’s an overhead of compiling the handlers at start time, which can be avoided by compiling them at build time. ### Compile at build time 1. Use [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update) to set up your project: Terminal window ```bash wrangler init elysia-on-cloudflare bun add elysia@rc bun add -d esbuild ``` 2. Define your Elysia app and export Elysia instance and Worker fetch handler: src/index.ts ```ts import { Elysia } from 'elysia' // export here for AOT export const app = new Elysia() .get('/', () => 'Hello Cloudflare Worker!') export default { fetch: (request: Request) => app.handle(request) } ``` 3. Compile Elysia’s handlers for `workerd` during your build: build.ts ```ts import * as esbuild from 'esbuild' import { aot } from 'elysia/plugin/aot/esbuild' await esbuild.build({ entryPoints: ['src/index.ts'], bundle: true, format: 'esm', outfile: 'dist/worker.js', conditions: ['workerd', 'worker', 'browser', 'import'], platform: 'browser', target: 'esnext', external: ['node:*', 'cloudflare:*'], plugins: [aot('src/index.ts', { target: 'workerd' })] }) ``` 4. Point Wrangler at the generated bundle. Choose either JSONC or TOML: wrangler.jsonc ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", "name": "elysia-on-cloudflare", "main": "dist/worker.js", "compatibility_date": "2025-09-06", "compatibility_flags": ["nodejs_compat"], "build": { "command": "bun build.ts" } } ``` wrangler.toml ```toml name = "elysia-on-cloudflare" main = "dist/worker.js" compatibility_date = "2025-09-06" compatibility_flags = ["nodejs_compat"] [build] command = "bun build.ts" ``` The compatibility date and `nodejs_compat` flag match Elysia’s v2 Cloudflare fixture. Keep the flag for this setup; the v1 advice to omit it does not apply here. 5. Start the development server: Terminal window ```bash wrangler dev ``` This starts the local Worker at `http://localhost:8787`. To deploy it, run `wrangler deploy`. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Limitations Here are some known limitations of using Elysia on Cloudflare Workers: 1. `file()` and the [Static plugin](/plugins/static) must not assume access to your development machine’s filesystem. Workers provide a [virtual filesystem](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/), including with the compatibility settings above; use [static assets](#static-files) for files you want to publish. 2. Generate [OpenAPI types](/blog/openapi-type-gen/) during your build. A Worker cannot inspect TypeScript source files that are not included in its bundle. 3. Avoid plugins that construct runtime-only objects during build or module initialization. Workers restrict some operations outside request handling. 4. Use a function handler when returning a value, so response creation happens while handling a request: ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'Hello Elysia') ``` ## Static files Use [Cloudflare’s built-in static file serving](https://developers.cloudflare.com/workers/static-assets/) to publish your `public` directory without relying on a filesystem-based static plugin. Add the following to your wrangler config: wrangler.jsonc ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", "name": "elysia-on-cloudflare", "main": "dist/worker.js", "compatibility_date": "2025-09-06", "compatibility_flags": ["nodejs_compat"], "build": { "command": "bun build.ts" }, "assets": { "directory": "public" } } ``` wrangler.toml ```toml name = "elysia-on-cloudflare" main = "dist/worker.js" compatibility_date = "2025-09-06" compatibility_flags = ["nodejs_compat"] assets = { directory = "public" } [build] command = "bun build.ts" ``` Create a `public` folder and place your static files in it. For example, if you have a folder structure like this: - public - kyuukurarin.mp4 - static - mika.webp - src - index.ts - wrangler.toml Then you should be able to access your static files from the following paths: - **** - **** ## Bindings You can use Cloudflare Workers bindings by importing `env` from `cloudflare:workers`. Import it inside the handler so the build process does not need to resolve a Worker-only module. src/index.ts ```ts import { Elysia } from 'elysia' export const app = new Elysia() .get('/', async () => { const { env } = await import('cloudflare:workers') return `Hello ${await env.KV.get('my-key')}` }) export default { fetch: (request: Request) => app.handle(request) } ``` Configure a `KV` binding in Wrangler and generate its TypeScript types with `wrangler types`. The external `cloudflare:*` imports stay available to the Worker runtime. See [Cloudflare Workers: Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) for more information about bindings. ## AoT compilation In v1, Workers originally required `aot: false`. Cloudflare later added [Function compilation during startup](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-eval-during-startup), and Elysia 1.4.7’s Cloudflare adapter used it with a compatibility date of at least `2025-06-01`. Elysia v2 uses a different compilation pipeline. The `aot` build plugin above moves handler generation into the build, using `target: 'workerd'` to generate code for Workers even when the build runs on Bun. Calling `.compile()` warms up Elysia’s handlers in the current runtime. It does not replace the Worker build step, and v2 has no `aot: false` constructor option. ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', () => 'Hello Elysia') .compile() ``` The deployment and binding examples still need to be tested against your Cloudflare account and chosen dependency versions. --- Source: [integrations/deno/index.md](integrations/deno/index.md) # Integration with Deno Elysia is built on top of Web Standard Request/Response, allowing us to run Elysia with Deno.serve directly. Add Elysia to your Deno project with `deno add npm:elysia@rc`. Then pass `app.fetch` to `Deno.serve`: ```typescript import { Elysia } from 'elysia' const app = new Elysia() .get('/', () => 'Hello Elysia') Deno.serve(app.fetch) ``` Then run the module with network permissions: Terminal window ```bash deno run --allow-net --watch src/index.ts ``` This is all you need to run Elysia on Deno. ### Change the port number You can specify the port number in `Deno.serve`. ```ts Deno.serve({ port: 8787 }, app.fetch) ``` ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` --- Source: [integrations/drizzle/index.md](integrations/drizzle/index.md) # Drizzle Drizzle ORM is a headless TypeScript ORM with a focus on type safety and developer experience. For an Elysia v2 app, [validate the database boundary](#validate-a-v2-database-boundary) with Elysia’s own schema. The legacy v1 recipe converts a Drizzle schema to Elysia validation models using `drizzle-typebox`. TypeBox compatibility Elysia v2 uses the `typebox` 1.x package. The `drizzle-typebox` recipe below targets `@sinclair/typebox` 0.x, including its `Kind` symbol and `BuildSchema` types. Pinning that old package does not make it compatible with v2. Keep the recipe as a reference for those package versions; for a v2 app, use a generator that emits compatible schemas, or define an Elysia validation schema at the database boundary. ### Drizzle TypeBox [Elysia’s `t`](/essential/validation/#typebox) extends TypeBox. Schema compatibility depends on the TypeBox version used by the generator. We can convert a Drizzle schema into a TypeBox schema using [“drizzle-typebox”](https://npmjs.org/package/drizzle-typebox), and use it directly in Elysia v1’s schema validation. ### Here’s how the v1 recipe works: 1. Define your database schema in Drizzle. 2. Convert the Drizzle schema into Elysia validation models using `drizzle-typebox`. 3. Use the converted Elysia validation models to ensure type validation. 4. OpenAPI schema is generated from Elysia validation models. 5. Add [Eden Treaty](/eden) to add type-safety to your frontend. ```text * ——————————————— * | | | -> | Documentation | * ————————— * * ———————— * OpenAPI | | | | | drizzle- | | ——————— | * ——————————————— * | Drizzle | —————————-> | Elysia | | | -typebox | | ——————— | * ——————————————— * * ————————— * * ———————— * Eden | | | | -> | Frontend Code | | | * ——————————————— * ``` ## Installation To install Drizzle, run the following command: Terminal window ```bash bun add drizzle-orm drizzle-typebox ``` For the legacy recipe, pinning `@sinclair/typebox` avoids conflicts between the 0.x schema symbols used by the generator and Elysia v1. Elysia v2 instead declares `typebox >= 1.3.0` as a peer dependency. For an existing v1 project, inspect its TypeBox dependency before choosing a pin: Terminal window ```bash grep "@sinclair/typebox" node_modules/elysia/package.json ``` A legacy `overrides` entry looks like this. The version shown belongs to the original recipe; do not use it to override Elysia v2’s `typebox` dependency: ```json { "overrides": { "@sinclair/typebox": "0.32.4" } } ``` ## Validate a v2 database boundary You can keep using Drizzle for database queries while describing the request with Elysia’s own schema. This avoids mixing TypeBox package generations: ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/sign-up', { body: t.Object({ username: t.String(), password: t.String(), email: t.String({ format: 'email' }) }) }, ({ body }) => { // Hash the password and insert the validated fields with Drizzle. return { username: body.username } }) ``` The remaining sections preserve the v1 schema-generation recipe and its utilities for compatible generator versions. ## Drizzle schema Assuming we have a `user` table in our codebase as follows: src/database/schema.ts ```ts import { pgTable, varchar, timestamp } from 'drizzle-orm/pg-core' import { createId } from '@paralleldrive/cuid2' export const user = pgTable( 'user', { id: varchar('id') .$defaultFn(() => createId()) .primaryKey(), username: varchar('username').notNull().unique(), password: varchar('password').notNull(), email: varchar('email').notNull().unique(), salt: varchar('salt', { length: 64 }).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), } ) export const table = { user } as const export type Table = typeof table ``` ## drizzle-typebox We may convert the `user` table into TypeBox models by using `drizzle-typebox`: src/index.ts ```ts import { Elysia, t } from 'elysia' import { Type } from '@sinclair/typebox' import { createInsertSchema } from 'drizzle-typebox' import { table } from './database/schema' const _createUser = createInsertSchema(table.user, { // Replace email with an email type email: Type.String({ format: 'email' }) }) new Elysia() .post('/sign-up', { body: t.Omit( _createUser, ['id', 'salt', 'createdAt'] ) }, ({ body }) => { // Create a new user }) ``` In an Elysia v1 app, this allows us to reuse the database schema in Elysia validation models ## Type instantiation is possibly infinite If you get an error like **Type instantiation is possibly infinite**, this is because of the circular reference between `drizzle-typebox` and `Elysia`. If we nest a type from drizzle-typebox into an Elysia schema, it will cause an infinite loop of type instantiation. To prevent this, we need to **explicitly define a type between `drizzle-typebox` and `Elysia`** schema: ```ts import { Elysia, t } from 'elysia' import { Type } from '@sinclair/typebox' import { createInsertSchema } from 'drizzle-typebox' import { table } from './database/schema' const _createUser = createInsertSchema(table.user, { email: Type.String({ format: 'email' }) }) // ✅ This works, by referencing the type from `drizzle-typebox` const createUser = t.Omit( _createUser, ['id', 'salt', 'createdAt'] ) // ❌ This will cause an infinite loop of type instantiation const createUserInline = t.Omit( createInsertSchema(table.user, { email: Type.String({ format: 'email' }) }), ['id', 'salt', 'createdAt'] ) ``` Always declare a variable for `drizzle-typebox` and reference it if you want to use Elysia types. ## Utility As we are likely going to use `t.Pick` and `t.Omit` to exclude or include certain fields, it may be cumbersome to repeat the process: These utility functions preserve the `drizzle-typebox` integration for projects still using its `@sinclair/typebox` schemas. Check the compatibility note above before using them with Elysia v2: src/database/utils.ts ```ts /** * @lastModified 2025-02-04 * @see /integrations/drizzle/#utility */ import { Kind, type TObject } from '@sinclair/typebox' import { createInsertSchema, createSelectSchema, BuildSchema, } from 'drizzle-typebox' import { table } from './schema' import type { Table } from 'drizzle-orm' type Spread< T extends TObject | Table, Mode extends 'select' | 'insert' | undefined, > = T extends TObject ? { [K in keyof Fields]: Fields[K] } : T extends Table ? Mode extends 'select' ? BuildSchema< 'select', T['_']['columns'], undefined >['properties'] : Mode extends 'insert' ? BuildSchema< 'insert', T['_']['columns'], undefined >['properties'] : {} : {} /** * Spread a Drizzle schema into a plain object */ export const spread = < T extends TObject | Table, Mode extends 'select' | 'insert' | undefined, >( schema: T, mode?: Mode, ): Spread => { const newSchema: Record = {} let table switch (mode) { case 'insert': case 'select': if (Kind in schema) { table = schema break } table = mode === 'insert' ? createInsertSchema(schema) : createSelectSchema(schema) break default: if (!(Kind in schema)) throw new Error('Expect a schema') table = schema } for (const key of Object.keys(table.properties)) newSchema[key] = table.properties[key] return newSchema as any } /** * Spread a Drizzle Table into a plain object * * If `mode` is 'insert', the schema will be refined for insert * If `mode` is 'select', the schema will be refined for select * If `mode` is undefined, the schema will be spread as is, models will need to be refined manually */ export const spreads = < T extends Record, Mode extends 'select' | 'insert' | undefined, >( models: T, mode?: Mode, ): { [K in keyof T]: Spread } => { const newSchema: Record = {} const keys = Object.keys(models) for (const key of keys) newSchema[key] = spread(models[key], mode) return newSchema as any } ``` This utility function will convert a Drizzle schema into a plain object, from which you can pick fields by property name: ```ts // ✅ Using spread utility function const user = spread(table.user, 'insert') const createUser = t.Object({ id: user.id, // { type: 'string' } username: user.username, // { type: 'string' } password: user.password // { type: 'string' } }) // ⚠️ Using t.Pick const _createUser = createInsertSchema(table.user) const createUserPicked = t.Pick( _createUser, ['id', 'username', 'password'] ) ``` ### Table singleton We recommend using a singleton pattern to store the table schema. This will allow us to access the table schema from anywhere in the codebase: src/database/model.ts ```ts import { table } from './schema' import { spreads } from './utils' export const db = { insert: spreads({ user: table.user, }, 'insert'), select: spreads({ user: table.user, }, 'select') } as const ``` This will allow us to access the table schema from anywhere in the codebase: src/index.ts ```ts import { Elysia, t } from 'elysia' import { db } from './database/model' const { user } = db.insert new Elysia() .post('/sign-up', { body: t.Object({ id: user.id, username: user.username, password: user.password }) }, ({ body }) => { // Create a new user }) ``` ### Refinement If type refinement is needed, you may use `createInsertSchema` and `createSelectSchema` to refine the schema directly. src/database/model.ts ```ts import { Type } from '@sinclair/typebox' import { createInsertSchema, createSelectSchema } from 'drizzle-typebox' import { table } from './schema' import { spreads } from './utils' export const db = { insert: spreads({ user: createInsertSchema(table.user, { email: Type.String({ format: 'email' }) }), }, 'insert'), select: spreads({ user: createSelectSchema(table.user, { email: Type.String({ format: 'email' }) }) }, 'select') } as const ``` In the code above, we refine a `user.email` schema to include a `format` property. The `spread` utility function will skip a refined schema, so you can use it as is. *** For more information, please refer to the [Drizzle ORM](https://orm.drizzle.team) and [Drizzle TypeBox](https://orm.drizzle.team/docs/typebox) documentation. --- Source: [integrations/expo/index.md](integrations/expo/index.md) # Integration with Expo Starting from Expo SDK 50 and App Router v3, Expo allows us to create API routes directly in an Expo app. 1. Create **app/\[…slugs]+api.ts** 2. Define an Elysia server 3. Export **Elysia.fetch** with the name of the HTTP methods you want to use app/\[...slugs]+api.ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', 'hello Expo') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) export const GET = app.fetch export const POST = app.fetch ``` You can treat the Elysia server as if it were a normal Expo API route. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Prefix If you place an Elysia server not in the root directory of the app router, you need to annotate the prefix on the Elysia server. For example, if you place the Elysia server in **app/api/\[…slugs]+api.ts**, you need to annotate the prefix as **/api** on the Elysia server. app/api/\[...slugs]+api.ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia({ prefix: '/api' }) .get('/', 'Hello Expo') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) export const GET = app.fetch export const POST = app.fetch ``` This will ensure that Elysia routing works properly wherever you place it. ## Eden We can add [Eden](/eden) for **end-to-end type safety** similar to tRPC. 1. Export `type` from the Elysia server app/\[...slugs]+api.ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia() .get('/', 'Hello Expo') .post( '/user', { body: t.Object({ name: t.String() }) }, ({ body }) => body ) export type app = typeof app export const GET = app.fetch export const POST = app.fetch ``` 2. Create a Treaty client lib/eden.ts ```typescript import { treaty } from '@elysia/eden' import type { app } from '../app/[...slugs]+api' export const api = treaty('http://localhost:8081') ``` For a physical device, replace `localhost` with an origin the device can reach, such as your development machine’s LAN address. Use your deployed API origin in production. 3. Use the client in your Expo screen app/index.tsx ```tsx import { useEffect, useState } from 'react' import { Text } from 'react-native' import { api } from '../lib/eden' export default function Page() { const [message, setMessage] = useState(null) useEffect(() => { api.get().then(({ data }) => setMessage(data)) }, []) return {message ?? 'Loading…'} } ``` ## Deployment You can either directly use the API route with Elysia and deploy it as a normal Elysia app if needed, or use the [experimental Expo server runtime](https://docs.expo.dev/router/reference/api-routes/#deployment). If you are using the Expo server runtime, you may use the `expo export` command to create an optimized build for your Expo app. This will include an Expo function that uses Elysia under `dist/server/_expo/functions/` Tip An exported API function needs a server adapter to receive requests. Importing the generated function alone does not allocate a port. You may use the Expo function adapter provided by Expo to deploy your Edge Function. The Expo deployment guide covers these adapters: - [Express](https://docs.expo.dev/router/reference/api-routes/#express) - [Netlify](https://docs.expo.dev/router/reference/api-routes/#netlify) - [Vercel](https://docs.expo.dev/router/reference/api-routes/#vercel) --- Source: [integrations/netlify/index.md](integrations/netlify/index.md) # Integration with Netlify Edge Function [Netlify Edge Functions](https://docs.netlify.com/build/edge-functions/overview/) run on [Deno](/integrations/deno), which is one of Elysia’s supported runtimes, as Elysia is built on top of Web Standards. Netlify Edge Functions require a special directory to run a function; the default is **\/netlify/edge-functions**. To create a function at **/hello**, you would need to create a file at `netlify/edge-functions/hello.ts`, then export a function that forwards the request to Elysia. netlify/edge-functions/hello.ts ```typescript import { Elysia } from 'elysia' export const config = { path: '/hello' } const app = new Elysia({ prefix: '/hello' }) .get('/', () => 'Hello Elysia') export default (request: Request) => app.handle(request) ``` ### Running locally To test your Elysia server on Netlify Edge Functions locally, you can install the [Netlify CLI](https://docs.netlify.com/build/edge-functions/get-started/#test-locally) to simulate function invocation. To install the Netlify CLI: Terminal window ```bash bun add -g netlify-cli ``` To run the development environment: Terminal window ```bash netlify dev ``` For additional information, please refer to the [Netlify Edge Functions documentation](https://docs.netlify.com/build/edge-functions). ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` --- Source: [integrations/nextjs/index.md](integrations/nextjs/index.md) # Integration with Next.js With Next.js App Router, we can run Elysia on Next.js routes. 1. Create **app/api/\[\[…slugs]]/route.ts** 2. Define an Elysia server 3. Export **Elysia.fetch** with the name of the HTTP methods you want to use app/api/\[\[...slugs]]/route.ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia({ prefix: '/api' }) .get('/', 'Hello Nextjs') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) export const GET = app.fetch export const POST = app.fetch ``` Elysia will work normally because of WinterTC compliance. You can treat the Elysia server as a normal Next.js API route. With this approach, you can have co-location of both frontend and backend in a single repository and have [End-to-end type safety with Eden](/eden) in both client-side code and server actions. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Prefix Because our Elysia server is not in the root directory of the app router, you need to annotate the prefix on the Elysia server. For example, if you place the Elysia server in **app/user/\[\[…slugs]]/route.ts**, you need to annotate the prefix as **/user** on the Elysia server. app/user/\[\[...slugs]]/route.ts ```typescript import { Elysia, t } from 'elysia' const app = new Elysia({ prefix: '/user' }) .get('/', 'Hello Nextjs') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) export const GET = app.fetch export const POST = app.fetch ``` This will ensure that Elysia routing works properly wherever you place it. ## Eden We can add [Eden](/eden) for **end-to-end type safety** similar to tRPC. In this approach, we will use the isomorphic fetch pattern to allow Elysia to: 1. On Server: directly call Elysia without going through the network layer 2. On Client: call Elysia through the network layer To start, we need to do the following steps: 1. Export the Elysia instance lib/server.ts ```typescript import { Elysia, t } from 'elysia' export const app = new Elysia({ prefix: '/api' }) .get('/', 'Hello Nextjs') .post( '/user', { body: t.Object({ name: t.String() }) }, ({ body }) => body ) ``` Keep the route file limited to Next.js route exports: app/api/\[\[...slugs]]/route.ts ```typescript import { app } from '../../../lib/server' export const GET = app.fetch export const POST = app.fetch ``` 2. Create separate server and browser clients The server client can call the Elysia instance directly. Keep its runtime import out of the browser bundle: lib/eden.server.ts ```typescript import 'server-only' import { treaty } from '@elysia/eden' import { app } from './server' export const api = treaty(app).api ``` The browser client imports only the app’s type and makes an HTTP request: lib/eden.ts ```typescript import { treaty } from '@elysia/eden' import type { app } from './server' export const api = treaty('http://localhost:3000').api ``` Use your deployed origin in production. This makes the server/client boundary explicit without depending on a bundled `process` global. 3. Use the client in both server and client components app/page.tsx ```tsx import { api } from '../lib/eden.server' export default async function Page() { const { data: message } = await api.get() return

Hello, {message}

} ``` This allows you to have type safety from the frontend to the backend with minimal effort and works with both server and client components, as well as with Incremental Static Regeneration (ISR). ## React Query We can also use React Query to interact with the Elysia server on the client. app/client.tsx ```tsx 'use client' import { useQuery } from '@tanstack/react-query' import { api } from '../lib/eden' export default function App() { const { data: response } = useQuery({ queryKey: ['get'], queryFn: () => api.get() }) return response?.data } ``` Place this client component under your `QueryClientProvider`. You can use React Query features like caching, pagination, and infinite queries. *** Please refer to [Next.js Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#static-route-handlers) for more information. --- Source: [integrations/node/index.md](integrations/node/index.md) # Integration with Node.js Elysia provides a runtime adapter to run Elysia on multiple runtimes, including Node.js. To run Elysia v2 on Node.js, install the v2 Node adapter. The v1 adapter is a different release line. bun ```bash bun add elysia@rc @elysia/node@rc ``` pnpm ```bash pnpm add elysia@rc @elysia/node@rc ``` npm ```bash npm install elysia@rc @elysia/node@rc ``` yarn ```bash yarn add elysia@rc @elysia/node@rc ``` Then apply the Node adapter to your main Elysia instance. ```typescript import { Elysia } from 'elysia' import { node } from '@elysia/node' const app = new Elysia({ adapter: node() }) .get('/', () => 'Hello Elysia') .listen(3000) ``` This is all you need to run Elysia on Node.js. ### Additional setup (optional) For the best experience, we recommend installing `tsx`. `tsx` is a CLI that transpiles TypeScript to JavaScript with hot-reload and several more features you would expect from a modern development environment. bun ```bash bun add -d tsx @types/node typescript ``` pnpm ```bash pnpm add -D tsx @types/node typescript ``` npm ```bash npm install --save-dev tsx @types/node typescript ``` yarn ```bash yarn add -D tsx @types/node typescript ``` Then open your `package.json` file, set the package to use ES modules, and add the following scripts: ```json { "type": "module", "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc -p tsconfig.json", "start": "NODE_ENV=production node dist/index.js" } } ``` These scripts refer to the different stages of developing an application: - **dev** - Start Elysia in development mode with auto-reload on code changes. - **build** - Build the application for production use. - **start** - Start a production Elysia server. Make sure to create a `tsconfig.json`: Terminal window ```bash tsc --init ``` Then configure these options in `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "types": ["node"], "rootDir": "src", "outDir": "dist", "strict": true, "skipLibCheck": true }, "include": ["src/**/*.ts"] } ``` The build script reads this configuration, including `strict` and the output directory. Use `.js` extensions when importing your own modules with NodeNext. `tsx watch` reloads on code changes for a similar experience to `bun dev`. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` --- Source: [integrations/nuxt/index.md](integrations/nuxt/index.md) # Integration with Nuxt We can use [nuxt-elysia](https://github.com/tkesgar/nuxt-elysia), a community plugin for Nuxt, to set up Elysia on Nuxt API routes with Eden Treaty. Community integration Use a `nuxt-elysia` release that supports your Elysia version. Its setup and generated Eden client belong to the community package; verify that compatibility before using it with v2. 1. Install the plugin with the following command: Terminal window ```bash bun add elysia@rc @elysia/eden@next bun add -d nuxt-elysia ``` 2. Add `nuxt-elysia` to your Nuxt config: ```ts export default defineNuxtConfig({ modules: [ 'nuxt-elysia' ], nitro: { preset: 'bun' } }) ``` Tip The `nitro.preset: 'bun'` configuration is recommended, not required. It tells Nuxt’s Nitro to use Bun as the server runtime instead of the default Node.js runtime. 3. Create `api.ts` in the project root: api.ts ```typescript import { Elysia } from 'elysia' export default () => new Elysia() .get('/hello', () => ({ message: 'Hello world!' })) ``` 4. Use Eden Treaty in your Nuxt app: ```vue ``` This will automatically set up Elysia to run on Nuxt API routes. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Prefix By default, Elysia will be mounted on **/\_api**, but we can customize it with the `nuxt-elysia` config. ```ts export default defineNuxtConfig({ nuxtElysia: { path: '/api' } }) ``` This will mount Elysia on **/api** instead of **/\_api**. For more configuration options, please refer to [nuxt-elysia](https://github.com/tkesgar/nuxt-elysia). --- Source: [integrations/prisma/index.md](integrations/prisma/index.md) # Prisma [Prisma](https://prisma.io) is an ORM that allows us to interact with databases in a type-safe manner. It provides a way to define your database schema using a Prisma schema file, and then generates TypeScript types based on that schema. Generator compatibility Configure Prismabox to import `t` from `elysia`, as shown below, and check that your generator version supports Elysia v2’s TypeBox 1.x schemas. The Prisma and Prismabox packages are separate integrations; these examples do not establish their runtime compatibility with every version. ### Prismabox [Prismabox](https://github.com/m1212e/prismabox) is a library that generates TypeBox or Elysia validation models from a Prisma schema. We can use Prismabox to convert a Prisma schema into Elysia validation models, which can then be used to ensure type validation in Elysia. ### How it works: 1. Define your database schema in Prisma Schema. 2. Add a `prismabox` generator to generate an Elysia schema. 3. Use the converted Elysia validation models to ensure type validation. 4. OpenAPI schema is generated from Elysia validation models. 5. Add [Eden Treaty](/eden) to add type-safety to your frontend. ```text * ——————————————— * | | | -> | Documentation | * ————————— * * ———————— * OpenAPI | | | | | prismabox | | ——————— | * ——————————————— * | Prisma | —————————-> | Elysia | | | | | ——————— | * ——————————————— * * ————————— * * ———————— * Eden | | | | -> | Frontend Code | | | * ——————————————— * ``` ## Installation To install Prisma, run the following command: Terminal window ```bash bun add @prisma/client prismabox prisma-adapter-bun-sqlite && \ bun add -d prisma ``` ## Prisma schema Assume you already have a `prisma/schema.prisma` file. The datasource has no `url` because Prisma 7 reads the database URL from `prisma.config.ts`. See Prisma’s [config reference](https://www.prisma.io/docs/orm/reference/prisma-config-reference) to set it up. We can add a `prismabox` generator to the Prisma schema file as follows: prisma/schema.prisma ```prisma generator client { provider = "prisma-client" output = "../generated/prisma" } datasource db { provider = "sqlite" // Note: In Prisma 7+, datasource URL is configured in prisma.config.ts (for Prisma CLI), // and when using driver adapters you also pass the runtime URL in adapter setup. } generator prismabox { provider = "prismabox" typeboxImportDependencyName = "elysia" typeboxImportVariableName = "t" inputModel = true output = "../generated/prismabox" } model User { id String @id @default(cuid()) email String @unique name String? posts Post[] } model Post { id String @id @default(cuid()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId String } ``` This will generate Elysia validation models in the `generated/prismabox` directory. Each model will have its own file, and the models will be named based on the Prisma model names. For example: - The `User` model will be generated to `generated/prismabox/User.ts` - The `Post` model will be generated to `generated/prismabox/Post.ts` ## Using generated models Then we can import the generated models in our Elysia application: src/index.ts ```ts import { Elysia, t } from 'elysia' import { PrismaBunSqlite } from 'prisma-adapter-bun-sqlite'; import { PrismaClient } from '../generated/prisma/client' import { UserPlain, UserPlainInputCreate } from '../generated/prismabox/User' const adapter = new PrismaBunSqlite({ url: process.env.DATABASE_URL }); export const prisma = new PrismaClient({ adapter }); const app = new Elysia() .put( '/', { body: UserPlainInputCreate, response: UserPlain }, async ({ body }) => prisma.user.create({ data: body }) ) .get( '/id/:id', { response: { 200: UserPlain, 404: t.String() } }, async ({ params: { id }, status }) => { const user = await prisma.user.findUnique({ where: { id } }) if (!user) return status(404, 'User not found') return user } ) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` This allows us to reuse the database schema in Elysia validation models. *** For more information, please refer to the [Prisma](https://prisma.io) and [Prismabox](https://github.com/m1212e/prismabox) documentation. --- Source: [integrations/react-email/index.md](integrations/react-email/index.md) # React Email React Email is a library that allows you to use React components to create emails. When running on Bun, we can directly write a React Email component and import the JSX directly into our code to send emails. ## Installation To install React Email, run the following command: Terminal window ```bash bun add -d react-email bun add @react-email/components react react-dom ``` Then add this script to `package.json`: ```json { "scripts": { "email": "email dev --dir src/emails" } } ``` We recommend adding email templates into the `src/emails` directory as we can directly import the JSX files. ### TypeScript If you are using TypeScript, you may need to add the following to your `tsconfig.json`: ```json { "compilerOptions": { "jsx": "react" } } ``` ## Your first email Create the file `src/emails/otp.tsx` with the following code: ```tsx import * as React from 'react' import { Tailwind, Section, Text } from '@react-email/components' export default function OTPEmail({ otp }: { otp: number }) { return (
Verify your Email Address Use the following code to verify your email address {otp} This code is valid for 10 minutes Thank you for joining us
) } OTPEmail.PreviewProps = { otp: 123456 } ``` You may notice that we are using `@react-email/components` to create the email template. This library provides a set of components including **styling with Tailwind** that are compatible with email clients like Gmail, Outlook, etc. We also added `PreviewProps` to the `OTPEmail` function. This only applies when previewing the email on our playground. ## Preview your email To preview your email, run the following command: Terminal window ```bash bun email ``` This will open a browser window with the preview of your email. ![React Email playground showing an OTP email we have just written](/recipe/react-email/email-preview.webp) ## Sending email These examples show provider delivery. Before using them for sign-in, store an expiring, single-use code for the requesting account and rate-limit generation and verification. To send an email, we can use `react-dom/server` to render the email, then submit it using a preferred provider: Nodemailer ```tsx import { Elysia, t } from 'elysia' import { randomInt } from 'node:crypto' import * as React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import OTPEmail from './emails/otp' import nodemailer from 'nodemailer' const transporter = nodemailer.createTransport({ host: 'smtp.gehenna.sh', port: 465, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD } }) new Elysia() .post('/otp', { body: t.String({ format: 'email' }) }, async ({ body }) => { // Random between 100,000 and 999,999 const otp = randomInt(100_000, 1_000_000) const html = renderToStaticMarkup() await transporter.sendMail({ from: 'ibuki@gehenna.sh', to: body, subject: 'Verify your email address', html, }) return { success: true } }) .listen(3000) ``` Resend ```tsx import { Elysia, t } from 'elysia' import { randomInt } from 'node:crypto' import * as React from 'react' import OTPEmail from './emails/otp' import { Resend } from 'resend' const resend = new Resend(process.env.RESEND_API_KEY) new Elysia() .post('/otp', { body: t.String({ format: 'email' }) }, async ({ body }) => { // Random between 100,000 and 999,999 const otp = randomInt(100_000, 1_000_000) await resend.emails.send({ from: 'ibuki@gehenna.sh', to: body, subject: 'Verify your email address', react: , }) return { success: true } }) .listen(3000) ``` AWS SES ```tsx import { Elysia, t } from 'elysia' import { randomInt } from 'node:crypto' import * as React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import OTPEmail from './emails/otp' import { type SendEmailCommandInput, SES } from '@aws-sdk/client-ses' import { fromEnv } from '@aws-sdk/credential-providers' const ses = new SES({ credentials: process.env.NODE_ENV === 'production' ? fromEnv() : undefined }) new Elysia() .post('/otp', { body: t.String({ format: 'email' }) }, async ({ body }) => { // Random between 100,000 and 999,999 const otp = randomInt(100_000, 1_000_000) const html = renderToStaticMarkup() await ses.sendEmail({ Source: 'ibuki@gehenna.sh', Destination: { ToAddresses: [body] }, Message: { Body: { Html: { Charset: 'UTF-8', Data: html } }, Subject: { Charset: 'UTF-8', Data: 'Verify your email address' } } } satisfies SendEmailCommandInput) return { success: true } }) .listen(3000) ``` Sendgrid ```tsx import { Elysia, t } from 'elysia' import { randomInt } from 'node:crypto' import * as React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import OTPEmail from './emails/otp' import sendgrid from "@sendgrid/mail" sendgrid.setApiKey(process.env.SENDGRID_API_KEY) new Elysia() .post('/otp', { body: t.String({ format: 'email' }) }, async ({ body }) => { // Random between 100,000 and 999,999 const otp = randomInt(100_000, 1_000_000) const html = renderToStaticMarkup() await sendgrid.send({ from: 'ibuki@gehenna.sh', to: body, subject: 'Verify your email address', html }) return { success: true } }) .listen(3000) ``` Tip When running on Bun, we can directly import the email component out of the box You may see all of the available integrations with React Email in the [React Email Integrations](https://react.email/docs/integrations/overview), and learn more about React Email in the [React Email documentation](https://react.email/docs) --- Source: [integrations/sveltekit/index.md](integrations/sveltekit/index.md) # Integration with SvelteKit With SvelteKit, you can run Elysia on server routes. 1. Create **src/routes/\[…slugs]/+server.ts**. 2. Define an Elysia server. 3. Export a **fallback** function that calls `app.handle`. src/routes/\[...slugs]/+server.ts ```typescript import { Elysia, t } from 'elysia'; const app = new Elysia() .get('/', 'hello SvelteKit') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) interface WithRequest { request: Request } export const fallback = ({ request }: WithRequest) => app.handle(request) ``` You can treat the Elysia server as a normal SvelteKit server route. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Prefix If you place an Elysia server not in the root of the routes directory, you need to annotate the prefix on the Elysia server. For example, if you place an Elysia server in **src/routes/api/\[…slugs]/+server.ts**, you need to annotate the prefix as **/api** on the Elysia server. src/routes/api/\[...slugs]/+server.ts ```typescript import { Elysia, t } from 'elysia'; const app = new Elysia({ prefix: '/api' }) .get('/', () => 'hi') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) type RequestHandler = (v: { request: Request }) => Response | Promise export const fallback: RequestHandler = ({ request }) => app.handle(request) ``` This will ensure that Elysia routing will work properly in any location where you place it. Please refer to [SvelteKit Routing](https://kit.svelte.dev/docs/routing#server) for more information. --- Source: [integrations/tanstack-start/index.md](integrations/tanstack-start/index.md) # Integration with TanStack Start Elysia can run inside TanStack Start server routes. 1. Create **src/routes/api.$.ts** 2. Define an Elysia server 3. Export the Elysia handler in **server.handlers** src/routes/api.$.ts ```typescript import { Elysia } from 'elysia' import { createFileRoute } from '@tanstack/react-router' const app = new Elysia({ prefix: '/api' }).get('/', 'Hello Elysia!') const handle = ({ request }: { request: Request }) => app.fetch(request) export const Route = createFileRoute('/api/$')({ server: { handlers: { GET: handle, POST: handle } } }) ``` Elysia should now be running on **/api**. We may add additional methods to **server.handlers** to support other HTTP methods as needed. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ## Eden We can add [Eden](/eden) for **end-to-end type safety** similar to tRPC. Move the Elysia instance into its own module, so page components never import the route file: src/elysia.ts ```typescript import { Elysia } from 'elysia' export const app = new Elysia({ prefix: '/api' }).get('/', 'Hello Elysia!') ``` Keep the route file limited to the route export: src/routes/api.$.ts ```typescript import { createFileRoute } from '@tanstack/react-router' import { app } from '../elysia' const handle = ({ request }: { request: Request }) => app.fetch(request) export const Route = createFileRoute('/api/$')({ server: { handlers: { GET: handle, POST: handle } } }) ``` Then create the Eden Treaty client in a separate module: src/lib/eden.ts ```typescript import { treaty } from '@elysia/eden' import { createIsomorphicFn } from '@tanstack/react-start' import { app } from '../elysia' export const getTreaty = createIsomorphicFn() .server(() => treaty(app).api) .client(() => treaty('localhost:3000').api) ``` Notice that we use **createIsomorphicFn** to create a separate Eden Treaty instance for both the server and client: 1. On the server, Elysia is called directly without HTTP overhead. 2. On the client, we call the Elysia server through HTTP. TanStack Start removes the `.server()` implementation from the client build, so the browser only uses the app’s type. In a React component, we can use `getTreaty` to call the Elysia server with type safety. ## Loader data TanStack Start supports **Loader** to fetch data before rendering the component. src/routes/index.tsx ```tsx import { createFileRoute } from '@tanstack/react-router' import { getTreaty } from '../lib/eden' export const Route = createFileRoute('/')({ component: App, loader: () => getTreaty().get().then((res) => res.data) }) function App() { const data = Route.useLoaderData() return data } ``` Calling Elysia in a loader executes it on the server during SSR and doesn’t incur HTTP overhead. When navigating from one page to another, the loader will run on the client-side, making an HTTP request to the endpoint. Eden Treaty will ensure type safety on both server and client. ## React Query We can also use React Query to interact with the Elysia server on the client. src/routes/index.tsx ```tsx import { createFileRoute } from '@tanstack/react-router' import { useQuery } from '@tanstack/react-query' import { getTreaty } from '../lib/eden' export const Route = createFileRoute('/')({ component: App }) function App() { const { data: response } = useQuery({ queryKey: ['get'], queryFn: () => getTreaty().get() }) return response?.data } ``` This can work with any React Query features like caching, pagination, infinite queries, etc. *** Please visit [TanStack Start Documentation](https://tanstack.com/start) for more information about TanStack Start. --- Source: [integrations/vercel/index.md](integrations/vercel/index.md) # Deploy Elysia on Vercel Elysia can be deployed on Vercel with zero configuration using either Bun or Node runtime. 1. In **src/index.ts**, create or import an existing Elysia server 2. Export the Elysia server as the default export ```typescript import { Elysia, t } from 'elysia' export default new Elysia() .get('/', () => 'Hello Vercel Function') .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) ``` 3. Develop locally with the Vercel CLI Terminal window ```bash vc dev ``` 4. Deploy to Vercel Terminal window ```bash vc deploy ``` That’s it. Your Elysia app is now running on Vercel. ### pnpm If your pnpm configuration does not install peer dependencies, add the peers required by Elysia v2 explicitly: Terminal window ```bash pnpm add typebox exact-mirror openapi-types ``` ### Using Node.js To deploy with Node.js, make sure to set `type: module` in your `package.json` package.json ```json { "name": "elysia-app", "type": "module" } ``` ### Using Bun To deploy with Bun, make sure to set the runtime to Bun in your `vercel.json` vercel.json ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "bunVersion": "1.x" } ``` ## If this doesn’t work Vercel has zero-configuration support for Elysia. For additional configuration, please refer to the [Vercel documentation](https://vercel.com/docs/frameworks/backend/elysia) --- Source: [internal/jit-compiler/index.md](internal/jit-compiler/index.md) # JIT “compiler” Elysia optimizes both the runtime features it uses and the request-handling work it performs. A route that only reads a path parameter shouldn’t need to parse an unused query or body. Since [Elysia 0.4](/blog/elysia-04/), released on 30 March 2023, the framework has generated JavaScript request handlers using `new Function(...)`. This isn’t a traditional compiler that translates one programming language into another. It generates code tailored to a route’s handler, schemas, and hooks. That’s why we put “compiler” in quotes. In v2, the handler generator lives under `src/compile/handler/`, including `jit.ts`. Older explanations refer to `src/compose.ts`. Sucrose still lives in `src/sucrose.ts`. By default, a route can compile when it is first used. Once generated, the handler is reused. Static responses and precompiled routes can take different paths, so not every request or every route needs the same analysis. ## Static code analysis (Sucrose) *Sucrose* is the nickname for the analysis module that helps the compiler understand which context fields a handler uses. It reads a function’s source with `Function.toString()` and uses specialized analysis to find relevant access patterns. It doesn’t execute the handler to discover what it needs. Consider this route: ```ts import { Elysia } from 'elysia' const app = new Elysia() .patch('/user/:id', ({ params }) => ({ id: params.id })) ``` The handler reads `params`. It doesn’t read `body`, `query`, or `headers`. Sucrose contributes this information to route compilation. The compiler also considers hooks and schemas: a body validation schema still needs its input even if the handler doesn’t read `body` directly. Conceptually, the generated code can do this: Route-specific handler, simplified ```ts function tailoredHandler(request) { const context = { request, params: parseParams(request.url) } return routeHandler(context) } ``` These are explanatory sketches, not Elysia’s generated source or exported helper functions. Compare that with a central handler that eagerly prepares every field: Eager central handler, simplified ```ts async function centralHandler(request) { const context = { request, params: parseParams(request.url), body: await parseBody(request), query: parseQuery(request.url), headers: parseHeaders(request.headers) } return routeHandler(context) } ``` | Input | Parameter-only route | Eager sketch | | --------------- | ---------------------------------------------- | ------------ | | Path parameters | Read | Read | | Body | Skip unless another route requirement needs it | Read | | Query | Skip unless needed | Read | | Headers object | Skip unless needed | Read | This reduces unnecessary work for each request. Other frameworks can also parse lazily; the sketches explain the optimization rather than claiming that every other framework always parses everything. ### Why not acorn, esprima, or other traditional analysis tools? General-purpose parsers understand far more JavaScript than this analysis needs. They also construct an AST, which adds work and allocations. Elysia specializes in the function source patterns relevant to context usage. It treats that subset as its own small input language and uses conservative analysis when access is harder to determine. The tradeoff is narrower scope in exchange for less parsing work. It isn’t a replacement for a general JavaScript parser. ## Compiler optimizations The compiler can shape control flow around a route’s actual requirements, specialize response handling, and use direct property access where possible. Constant values and known schemas also give it information that a single general request handler wouldn’t have. Many small savings contribute to the result; their impact depends on the route. ### Example: `mapResponse`, `mapCompactResponse` Elysia has response mappers for different amounts of response metadata. When a handler doesn’t modify `set` or use a custom status, a compact mapper can return its value without preparing extra status and header state. When that state is needed, the full mapper handles it. Creating and mapping a response with custom headers involves different work from returning a plain value. The optimization selects the path that fits the route instead of always paying for the general case. ## Platform-specific optimization Elysia was originally built for Bun and also supports [Node.js](/integrations/node/), [Deno](/integrations/deno/), and other environments through runtime-specific integrations. Being compatible with a platform and taking advantage of its native APIs are different jobs. For example, Bun’s native routing and static response paths can avoid some JavaScript dispatch work. Other examples include: - Native WebSocket handling on Bun through Elysia’s [WebSocket plugin](/patterns/websocket/). - The `file()` helper using platform-specific file handling when available. - Native header operations where the runtime exposes them. Elysia is listed in [TechEmpower Round 23’s plaintext benchmark](https://www.techempower.com/benchmarks/#section=data-r23\&hw=ph\&test=plaintext). That is a historical result for a specific configuration, not a measurement of the v2 examples on this page. Measure your actual workload and runtime. Database access, serialization, middleware, and network conditions can dominate a small routing benchmark. ## Overhead of the JIT compiler Generating a specialized handler has costs as well as benefits. ### Initial request overhead A route’s first use may need analysis and code generation. Later requests reuse the result. Measure it locally against the version and runtime you’re using: Measure eager route compilation ```ts import { Elysia } from 'elysia' const iterations = 10_000 const start = performance.now() for (let index = 0; index < iterations; index++) { new Elysia() .post('/id/:id', ({ params }) => params.id) .compile() } const elapsed = performance.now() - start console.log(`${elapsed.toFixed(1)} ms total`) console.log(`${(elapsed / iterations).toFixed(4)} ms per instance`) ``` This includes instance creation and route registration as well as compilation. Warmup, garbage collection, route complexity, and hardware affect the result. Use `precompile: true` on the root application to move compilation before listening, or call `.compile()` explicitly: ```ts import { Elysia } from 'elysia' new Elysia({ precompile: true }) .get('/hello/:name', ({ params }) => 'Hello ' + params.name) .listen(3000) ``` You trade some startup time for less first-request work. ### Memory usage Generated code and related metadata occupy memory. More routes can mean more compiled handlers and validators. Measure memory with a representative route set if this matters for your deployment. ### Bigger bundle size The compiler and its analysis code add to Elysia’s package. Ahead-of-time build tooling can change what is needed at runtime, but a smaller bundle and a faster handler aren’t automatically the same tradeoff. ### Maintainability Generated code adds complexity for framework maintainers. Debugging it requires understanding both the route definition and the code generation path. Keeping generation and analysis specialized helps runtime performance, but also means those paths need their own tests. ### Security considerations `new Function(...)` compiles JavaScript. It isn’t a sandbox. Elysia generates code from application routes and compiler-controlled structures; it doesn’t intend to evaluate arbitrary request strings as JavaScript. That boundary still needs care when handling schemas, plugin configuration, or other inputs that influence generated code. Don’t accept untrusted application code or executable schema configuration and assume route compilation makes it safe. A runtime’s restrictions on dynamic code generation also apply independently of application correctness. ## Libraries that use dynamic code generation Elysia isn’t alone in generating specialized functions. [Ajv](https://ajv.js.org/guide/managing-schemas.html) and [TypeBox](https://github.com/sinclairzx81/typebox) use compilation techniques for validation too. The same broad idea is to do preparation once, then reuse a function on the hot path. Elysia applies it to request handling as well as validation. Exact implementation details depend on the validator and version you choose. ## Opting out Elysia v2 The v1 option `new Elysia({ aot: false })` selected the old dynamic handler. The v2 constructor no longer exposes that option. Setting `precompile: true` changes when compilation happens; it doesn’t disable code generation. V2 provides ahead-of-time build plugins under paths such as `elysia/plugin/aot/bun`, `elysia/plugin/aot/esbuild`, and `elysia/plugin/aot/vite`. These prepare handlers during a build rather than using the old dynamic-mode switch. When deploying to a runtime that restricts dynamic code generation, check its [deployment requirements](/patterns/deploy/) and the build plugin’s supported paths. Don’t assume changing the old `aot` flag makes an application compatible. ## Afterword The compiler trades some startup work, memory, and implementation complexity for less repeated request-handling work. That tradeoff is central to Elysia’s design. The project published a [six-page paper in the ACM Digital Library](https://dl.acm.org/doi/10.1145/3605098.3636068) about its earlier JIT compiler and performance optimizations. It gives background on the approach; the current implementation has continued to evolve. Native libraries and bindings, including systems such as uWebSockets, make different tradeoffs and can outperform JavaScript frameworks on some workloads. Use benchmarks to answer a specific workload question rather than to promise a universal winner. --- Source: [key-concept/index.md](key-concept/index.md) # Key concepts ### Important We recommend finishing the [Essential concepts](/essential/route/) chapters before starting your first Elysia project. But if you want to jump straight into writing code, this is a brief overview of Elysia’s key concepts. ## Encapsulation Elysia lifecycle hooks are **encapsulated**. This means a hook added in a plugin will not apply to the instance that uses the plugin unless you explicitly share it. ```ts import { Elysia } from 'elysia' import { throwIfNotSignIn, updateProfile } from './profile' const profile = new Elysia() .onBeforeHandle(({ cookie }) => { throwIfNotSignIn(cookie) }) .get('/profile', () => 'Hi there!') const app = new Elysia() .use(profile) // This route does not inherit the local sign-in check. .patch('/rename', ({ body }) => updateProfile(body)) ``` Here, the `throwIfNotSignIn` is applied to `/profile` but not to `/rename`. *** To make it carry through, you need to tell Elysia to share it. As with the `export` keyword, make the sharing explicit when another instance needs it. To *export* the hook, simply add a [scope](/essential/plugin/#scope-level): ```ts import { Elysia } from 'elysia' import { throwIfNotSignIn, updateProfile } from './profile' const profile = new Elysia() .onBeforeHandle('global', ({ cookie }) => { throwIfNotSignIn(cookie) }) .get('/profile', () => 'Hi there!') const app = new Elysia() .use(profile) // This route inherits the sign-in check. .patch('/rename', ({ body }) => updateProfile(body)) ``` ## Method chaining Use **method chaining** to keep Elysia’s inferred types: ```ts import { Elysia } from 'elysia' new Elysia() .state('build', 1) .get('/', ({ store: { build } }) => build) .listen(3000) ``` `state` returns an instance type with a typed `build` property. Chaining the next call keeps it available. If you don’t use method chaining, it doesn’t keep the new type. This example intentionally produces a TypeScript error: ```ts import { Elysia } from 'elysia' const app = new Elysia() app.state('build', 1) app.get('/', ({ store: { build } }) => build) //! Property 'build' does not exist on type '{}' app.listen(3000) ``` The instance can still be changed at runtime; the problem is that the variable’s static type hasn’t changed. Chain calls, or retain the returned instance in a new variable. ## Dependency Every instance has its own context. When an instance needs another instance’s context, it’s treated as a dependency via `.use()`. ```ts import { Elysia, t } from 'elysia' class Auth { getProfile() { return { name: 'Elysia User' } } } const auth = new Elysia() .decorate({ auth: new Auth() }) const main = new Elysia() // Intentional type error: auth has not been registered here. .get('/', ({ auth }) => auth.getProfile()) //! Property 'auth' does not exist on type '{ body: unknown; ... store: {}; }' .use(auth) .get('/profile', ({ auth }) => auth.getProfile()) ``` This is **Context Propagation**. Each instance has its own context, and `.use()` propagates the context from one instance to another. The `auth` instance is a dependency of `main`, so `main` can use its services and types. It’s intentionally designed to be explicit and have no global type context because: 1. Type soundness: To guarantee types both at runtime and at the type level, Elysia needs to know the dependency explicitly. Declaring a global type context would break this guarantee. 2. Dependency Tracking: When the dependent is changed, Elysia can track the dependency and update the dependent’s type context accordingly. 3. Modularity: Any part should be able to be used independently without any global type context. ### Deduplication A plugin can be registered more than once when multiple instances share it. To prevent duplication, give the plugin a unique `name` and Elysia will deduplicate it for you. ```ts import { Elysia } from 'elysia' const ip = new Elysia({ name: 'ip' }) .derive('global', ({ server, request }) => ({ ip: server?.requestIP(request) })) .get('/ip', ({ ip }) => ip) const router1 = new Elysia() .use(ip) .get('/ip-1', ({ ip }) => ip) const router2 = new Elysia() .use(ip) .get('/ip-2', ({ ip }) => ip) const server = new Elysia() .use(router1) .use(router2) ``` This makes the shared derive hook run once per request. For a configurable plugin, add `seed` to identify its configuration. Matching `name` and `seed` values deduplicate registrations. Different seeds keep configurations distinct. Learn more about [plugin deduplication](/essential/plugin/#deduplication). ## Order of code For routes declared directly on an instance, hooks apply to routes registered **after** them: ```ts import { Elysia } from 'elysia' new Elysia() .onBeforeHandle(() => { console.log('1') }) .get('/', () => 'hi') .onBeforeHandle(() => { console.log('2') }) .listen(3000) ``` A request to `/` logs: ```text 1 ``` It doesn’t log `2` because that hook was registered after the direct route. Mounted routes and early hooks Mounted plugin routes inherit parent hooks registered before `.use(plugin)`, so a parent error handler placed there can handle errors thrown by that plugin’s routes. The `request` stage runs before route matching, so it also differs from route-specific registration order. Keep shared hooks near the start of an instance. See [order of code](/essential/life-cycle/#order-of-code) for the direct-route and mounted-plugin examples. ## Type inference Elysia infers handler context from the route’s schema: ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => body) ``` Use an inline handler so TypeScript can infer context at the route declaration. When you need a separate function, such as a controller method, pass only the values it needs through the inline handler: ```ts import { Elysia, t } from 'elysia' abstract class Controller { static greet({ name }: { name: string }) { return 'hello ' + name } } const app = new Elysia() .post('/', { body: t.Object({ name: t.String() }) }, ({ body }) => Controller.greet(body)) ``` See [best practice: controller](/essential/best-practice/#controller). --- Source: [migrate/from-express/index.md](migrate/from-express/index.md) # From Express to Elysia Elysia v2 The Elysia examples use v2: route options precede the handler, lifecycle methods use their event names, and request-derived values use `derive`. The other framework examples show a typical setup for that framework; they are migration examples rather than a survey of every current capability. Framework integrations have their own release compatibility; check those packages when migrating an application. This guide is for Express users who want to see the differences from Express including syntax, and how to migrate your application from Express to Elysia by example. **Express** is a popular web framework for Node.js, and widely used for building web applications and APIs. It is known for its simplicity and flexibility. **Elysia** is an ergonomic web framework for Bun, Node.js, and runtimes that support Web Standard API. Designed to be ergonomic and developer-friendly with a focus on **sound type safety** and performance. ## Performance Elysia has significant performance improvements over Express thanks to native Bun implementation, and static code analysis. This comparison uses a historical [TechEmpower Round 22 result](https://www.techempower.com/benchmarks/#hw=ph\&test=plaintext\§ion=data-r22) from 2023-10-17 for Plaintext. These are the recorded framework/runtime combinations, not measurements of Elysia v2. | Framework | Requests per second | | --------- | ------------------: | | Elysia | 2,454,631 | | Express | 113,117 | ## Routing Express and Elysia have similar routing syntax, using `app.get()` and `app.post()` methods to define routes and similar path parameter syntax. Express ```ts import express from 'express' const app = express() app.get('/', (req, res) => { res.send('Hello World') }) app.post('/id/:id', (req, res) => { res.status(201).send(req.params.id) }) app.listen(3000) ``` > Express uses `req` and `res` as request and response objects Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', 'Hello World') .post( '/id/:id', ({ status, params: { id } }) => { return status(201, id) } ) .listen(3000) ``` > Elysia uses a single `context` and returns the response directly There is a slight difference in style guide. Elysia recommends usage of method chaining and object destructuring. Elysia also supports an inline value for the response if you don’t need to use the context. ## Handler Both Express and Elysia have a similar property for accessing input parameters like `headers`, `query`, `params`, and `body`. Express ```ts import express from 'express' const app = express() app.use(express.json()) app.post('/user', (req, res) => { const limit = req.query.limit const name = req.body.name const auth = req.headers.authorization res.json({ limit, name, auth }) }) ``` > Express needs `express.json()` middleware to parse a JSON body Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, (ctx) => { const limit = ctx.query.limit const name = ctx.body.name const auth = ctx.headers.authorization return { limit, name, auth } }) ``` > Elysia parses JSON, URL-encoded data, and form data by default ## Subrouter Express uses a dedicated `express.Router()` for declaring a sub-router while Elysia treats every instance as a component that can be plugged and played together. Express ```ts import express from 'express' const subRouter = express.Router() subRouter.get('/user', (req, res) => { res.send('Hello User') }) const app = express() app.use('/api', subRouter) ``` > Express uses `express.Router()` to create a sub-router Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ prefix: '/api' }) .get('/user', 'Hello User') const app = new Elysia() .use(subRouter) ``` > Elysia treats every instance as a component ## Validation Elysia has built-in support for request validation using TypeBox for sound type safety, and support for Standard Schema out of the box allowing you to use your favorite library like Zod, Valibot, ArkType, Effect Schema and so on. Express doesn’t offer built-in validation and requires a manual type declaration based on each validation library. Express ```ts import express from 'express' import { z } from 'zod' const app = express() app.use(express.json()) const paramSchema = z.object({ id: z.coerce.number() }) const bodySchema = z.object({ name: z.string() }) app.patch('/user/:id', (req, res) => { const params = paramSchema.safeParse(req.params) if (!params.success) return res.status(422).json(params.error) const body = bodySchema.safeParse(req.body) if (!body.success) return res.status(422).json(body.error) res.json({ params: params.data.id, body: body.data }) }) ``` > Express requires an external validation library like `zod` or `joi` to validate the request body Elysia TypeBox ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .patch('/user/:id', { params: t.Object({ id: t.Number() }), body: t.Object({ name: t.String() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Zod ```ts import { Elysia } from 'elysia' import { z } from 'zod' const app = new Elysia() .patch('/user/:id', { params: z.object({ id: z.coerce.number() }), body: z.object({ name: z.string() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Valibot ```ts import { Elysia } from 'elysia' import * as v from 'valibot' const app = new Elysia() .patch('/user/:id', { params: v.object({ id: v.pipe(v.string(), v.transform(Number), v.number()) }), body: v.object({ name: v.string() }) }, ({ params, body }) => ({ params, body })) ``` > Elysia uses TypeBox for validation and coerces types automatically, while supporting various validation libraries like Zod and Valibot with the same syntax as well. ## File upload Express relies on the external multer library to handle file uploads, while Elysia provides built-in support for file and form data, including MIME-type validation through its declarative API. Express ```ts import express from 'express' import multer from 'multer' import { fileTypeFromFile } from 'file-type' import path from 'path' const app = express() const upload = multer({ dest: 'uploads/' }) app.post('/upload', upload.single('image'), async (req, res) => { const file = req.file if (!file) return res .status(422) .send('No file uploaded') const type = await fileTypeFromFile(file.path) if (!type || !type.mime.startsWith('image/')) return res .status(422) .send('File is not a valid image') const filePath = path.resolve(file.path) res.sendFile(filePath) }) ``` > Express needs `multer` for file upload and manual mimetype validation Elysia ```ts import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) const app = new Elysia() .post('/upload', { body: t.Object({ file: t.File({ type: 'image/*' }) }) }, ({ body }) => body.file) ``` > Elysia handles file and mimetype validation declaratively File metadata is not proof of the file’s contents. Both examples use **file-type** to inspect its bytes. Elysia registers the detector once with `setFileTypeDetector`, then checks the type declaratively with `t.File({ type })`. The detector is required: without it, every upload checked against `type` fails validation. ## Middleware Express middleware uses a single queue-based order while Elysia gives you more granular control using an **event-based** lifecycle. Elysia exposes stages of the [request lifecycle](/essential/life-cycle): | Stage | Purpose | | ----------------------------- | ------------------------------------------------- | | Request | Inspect the incoming request before parsing | | Parse | Decode the request body | | Transform and validation | Normalize and validate request inputs | | Before handle | Derive request values or return an early response | | Handler | Produce the route response | | After handle and map response | Adjust and serialize the response | | After response | Run work after the response is sent | The error hook handles failures in this pipeline. While Express uses a single linear request pipeline, Elysia can intercept each event in a request pipeline. Express ```ts import express from 'express' const app = express() // Global middleware app.use((req, res, next) => { console.log(`${req.method} ${req.url}`) next() }) app.get( '/protected', // Route-specific middleware (req, res, next) => { const token = req.headers.authorization if (!token) return res.status(401).send('Unauthorized') next() }, (req, res) => { res.send('Protected route') } ) ``` > Express uses a single queue-based order for middleware which executes in order Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() // Global middleware .onRequest(({ request }) => { console.log(request.method, new URL(request.url).pathname) }) // Route-specific middleware .get('/protected', { beforeHandle({ status, headers }) { if (!headers.authorization) return status(401) } }, () => 'protected') ``` > Elysia uses a specific event interceptor for each point in the request pipeline While Express has a `next` function to call the next middleware, Elysia does not have one. ## Sound type safety Elysia is designed to provide sound type safety. For example, [derive](/essential/life-cycle#derive) adds request-specific context with inferred types. In v2 it also takes over the validated-context role previously served by `resolve`. Express ```ts import express from 'express' import type { Request, Response } from 'express' const app = express() const getVersion = (req: Request, res: Response, next: Function) => { // @ts-ignore req.version = 2 next() } app.get('/version', getVersion, (req, res) => { // Error: Property 'version' does not exist on type 'Request' res.send(req.version) }) const authenticate = (req: Request, res: Response, next: Function) => { const token = req.headers.authorization if (!token) return res.status(401).send('Unauthorized') // @ts-ignore req.token = token.split(' ')[1] next() } app.get('/token', getVersion, authenticate, (req, res) => { // Error: Property 'version' does not exist on type 'Request' req.version // Error: Property 'token' does not exist on type 'Request' res.send(req.token) }) ``` > Express attaches properties with `@ts-ignore`, and `Request` doesn’t know about them Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .decorate('version', 2) .get('/version', ({ version }) => version) .derive(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) return { token: authorization.split(' ')[1] } }) // { token: string, version: number } .get('/token', ({ token, version }) => { return token }) ``` > Elysia uses `decorate` to extend the context, and `derive` to add custom properties to the context While Express can use `declare module` to extend the `Request` interface, it is globally available and doesn’t have sound type safety, and doesn’t guarantee that the property is available in all request handlers. ```ts declare module 'express' { interface Request { version: number token: string } } ``` > This is required for the above Express example to work, which doesn’t offer sound type safety ## Middleware parameter Express uses a function that returns middleware to define a reusable route-specific middleware, while Elysia uses [macro](/patterns/macro) to define a custom hook. Express ```ts import { findUser } from './users' import express from 'express' import type { Request, Response } from 'express' const app = express() const role = (role: 'user' | 'admin') => (req: Request, res: Response, next: Function) => { const user = findUser(req.headers.authorization) if (!user || user.role !== role) return res.status(401).send('Unauthorized') req.user = user next() } app.get('/token', role('admin'), (req, res) => { // Error: Property 'user' does not exist on type 'Request' res.send(req.user) }) ``` > Express uses a callback function to accept custom arguments for middleware Elysia ```ts import { Elysia } from 'elysia' import { findUser } from './users' const app = new Elysia() .macro({ role: (role: 'user' | 'admin') => ({ derive({ status, headers: { authorization } }) { const user = findUser(authorization) if (!user || user.role !== role) return status(401) return { user } } }) }) // { user: User } .get('/token', { role: 'admin' }, ({ user }) => user) ``` > Elysia uses macro to pass custom arguments to custom middleware Here, `findUser` is application code that verifies the supplied credential and returns a user with a `role`, or no user when verification fails. A header or Bearer prefix check alone is not authentication. ## Error handling Express uses a single error handler for all routes, while Elysia provides more granular control over error handling. Express ```ts import express from 'express' const app = express() class CustomError extends Error { constructor(message: string) { super(message) this.name = 'CustomError' } } // global error handler app.use((error, req, res, next) => { if(error instanceof CustomError) { res.status(500).json({ message: 'Something went wrong!', error }) } }) // throws CustomError app.get('/error', (req, res) => { throw new CustomError('oh uh') }) ``` > Express uses middleware to handle errors, and a single error handler for all routes Elysia ```ts import { Elysia } from 'elysia' class CustomError extends Error {} const app = new Elysia() .onError(CustomError, ({ error, status }) => status(500, { message: 'Something went wrong!', detail: error.message }) ) .get('/error', () => { throw new CustomError('oh uh') }) .get('/local-error', { error({ error, status }) { if (error instanceof RangeError) return status(500, { message: 'Only for this route!' }) } }, () => { throw new RangeError('outside the allowed range') }) ``` > Elysia provides more granular control over error handling, and scoping mechanisms While Express offers error handling using middleware, Elysia provides: 1. Both global and route-specific error handlers 2. The `status` helper for returning a response with an HTTP status 3. Class-specific error handlers with inferred error types The `/error` route uses the registered `CustomError` handler. The `/local-error` route handles a different error locally. Error hooks run in the order they’re registered, and the first one that returns a value sends the response. Hooks registered on the app before a route run before that route’s own `error` hook, so the route hook can’t override them. Elysia infers the registered error type in its class-specific handler. ## Encapsulation Express middleware is registered globally, while Elysia gives you control over side-effects of a plugin via an explicit scoping mechanism and order-of-code. Express ```ts import express from 'express' const app = express() app.get('/', (req, res) => { res.send('Hello World') }) const subRouter = express.Router() subRouter.use((req, res, next) => { const token = req.headers.authorization if (!token) return res.status(401).send('Unauthorized') next() }) app.use(subRouter) // has side-effect from subRouter app.get('/side-effect', (req, res) => { res.send('hi') }) ``` > Express doesn’t handle side-effects of middleware, and requires a prefix to separate the side-effect Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // doesn't have side-effect from subRouter .get('/side-effect', () => 'hi') ``` > Elysia encapsulates a side-effect into a plugin By default, Elysia will encapsulate lifecycle events and context to the instance that is used, so that the side-effect of a plugin will not affect its parent instance unless explicitly stated. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) // Scoped to parent instance but not beyond .as('plugin') // [!code ++] const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // [!code ++] // now have side-effect from subRouter .get('/side-effect', () => 'hi') ``` Elysia offers 3 types of scoping mechanisms: 1. **local** - Apply to this instance and its descendants (default) 2. **plugin** - Share side-effects with the parent instance but not beyond 3. **global** - Share through all parent instances While Express can scope the middleware side-effect by adding a prefix, it isn’t a true encapsulation. The side-effect is still there but restricted to any routes starting with said prefix, adding mental overhead to the developer to memorize which prefix has side-effects. You can do the following: 1. Change the order of the code, but only if there is a single instance with side-effects. 2. Add a prefix, but the side-effects are still there. If another instance has the same prefix, then it has the side-effects. This can lead to a nightmarish scenario to debug as Express doesn’t offer true encapsulation. ## Cookie Express uses an external library `cookie-parser` to parse cookies, while Elysia has built-in support for cookies and uses a signal-based approach to handle cookies. Express ```ts import express from 'express' import cookieParser from 'cookie-parser' const app = express() app.use(cookieParser('secret')) app.get('/', function (req, res) { req.cookies.name req.signedCookies.name res.cookie('name', 'value', { signed: true, maxAge: 1000 * 60 * 60 * 24 }) }) ``` > Express uses `cookie-parser` to parse cookies Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia({ cookie: { secrets: 'replace-with-a-private-secret', sign: ['name'] } }) .get('/', { cookie: t.Cookie({ name: t.Optional(t.String()) }) }, ({ cookie: { name } }) => { const previous = name.value name.set({ value: 'value', maxAge: 60 * 60 * 24, httpOnly: true, secure: true, sameSite: 'lax' }) return previous ?? 'unset' }) ``` > Elysia verifies and signs the configured cookies automatically. Cookie `maxAge` is in seconds; replace the example secret with a private value and serve secure cookies over HTTPS. ## OpenAPI Express requires a separate configuration for OpenAPI, validation, and type safety while Elysia provides OpenAPI through the `@elysia/openapi` plugin using a schema as a **single source of truth**. Express ```ts import express from 'express' import swaggerUi from 'swagger-ui-express' const app = express() app.use(express.json()) app.post('/users', (req, res) => { // TODO: validate request body res.status(201).json(req.body) }) const swaggerSpec = { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' }, paths: { '/users': { post: { summary: 'Create user', requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { name: { type: 'string', description: 'First name only' }, age: { type: 'integer' } }, required: ['name', 'age'] } } } }, responses: { '201': { description: 'User created' } } } } } } app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)) ``` > Express requires a separate configuration for OpenAPI, validation, and type safety Elysia ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' // [!code ++] const app = new Elysia() .use(openapi()) // [!code ++] .model({ user: t.Object({ name: t.String(), age: t.Integer() }) }) .post('/users', { body: 'user', response: { 201: 'user' }, detail: { summary: 'Create user' } }, ({ body, status }) => status(201, body)) ``` > Elysia uses a schema as a single source of truth Based on the schema you provided, Elysia generates an OpenAPI specification, validates requests and responses, and infers types automatically. Elysia also appends the schema registered in `model` to the OpenAPI spec, allowing you to reference the model in a dedicated section in Swagger or Scalar UI. ## Testing Express uses a single `supertest` library to test the application, while Elysia is built on top of Web Standard API allowing it to be used with any testing library. Express ```ts import express from 'express' import request from 'supertest' import { describe, it, expect } from 'vitest' const app = express() app.get('/', (req, res) => { res.send('Hello World') }) describe('GET /', () => { it('should return Hello World', async () => { const res = await request(app).get('/') expect(res.status).toBe(200) expect(res.text).toBe('Hello World') }) }) ``` > Express uses `supertest` library to test the application Elysia ```ts import { Elysia } from 'elysia' import { describe, it, expect } from 'vitest' const app = new Elysia() .get('/', 'Hello World') describe('GET /', () => { it('should return Hello World', async () => { const res = await app.handle( new Request('http://localhost') ) expect(res.status).toBe(200) expect(await res.text()).toBe('Hello World') }) }) ``` > Elysia uses Web Standard API to handle requests and responses Alternatively, Elysia also offers a helper library called [Eden](/eden) for End-to-end type safety, allowing you to test with auto-completion, and full type safety. Elysia ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' import { describe, expect, it } from 'bun:test' const app = new Elysia().get('/hello', 'Hello World') const api = treaty(app) describe('GET /', () => { it('should return Hello World', async () => { const { data, error, status } = await api.hello.get() expect(status).toBe(200) expect(data).toBe('Hello World') }) }) ``` ## End-to-end type safety Elysia offers built-in support for **end-to-end type safety** without code generation using [Eden](/eden), while Express does not. Elysia ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/mirror', { body: t.Object({ message: t.String() }) }, ({ body }) => body) const api = treaty(app) const { data, error } = await api.mirror.post({ message: 'Hello World' }) if (error) // error.status: 422 // error.value: { type: 'validation', title: 'Validation Error', status: 422, on: 'body', detail, errors: [...] } throw error // { message: 'Hello World' } console.log(data) ``` If end-to-end type safety is important for you, then Elysia is the right choice. *** Elysia offers a more ergonomic and developer-friendly experience with a focus on performance, type safety, and simplicity while Express is a popular web framework for Node.js, but it has some limitations when it comes to performance and simplicity. If you are looking for a framework that is easy to use, has a great developer experience, and is built on top of Web Standard API, Elysia is the right choice for you. Alternatively, if you are coming from a different framework, you can check out: ### [From Fastify](/migrate/from-fastify) [Comparison between Fastify and Elysia](/migrate/from-fastify) ### [From Hono](/migrate/from-hono) [Comparison between Hono and Elysia](/migrate/from-hono) ### [From tRPC](/migrate/from-trpc) [Comparison between tRPC and Elysia](/migrate/from-trpc) --- Source: [migrate/from-fastify/index.md](migrate/from-fastify/index.md) # From Fastify to Elysia Elysia v2 The Elysia examples use v2: route options precede the handler, lifecycle methods use their event names, and request-derived values use `derive`. The other framework examples show a typical setup for that framework; they are migration examples rather than a survey of every current capability. Framework integrations have their own release compatibility; check those packages when migrating an application. This guide is for Fastify users who want to see the differences from Fastify including syntax, and how to migrate your application from Fastify to Elysia by example. **Fastify** is a fast and low overhead web framework for Node.js, designed to be simple and easy to use. It is built on top of the HTTP module and provides a set of features that make it easy to build web applications. **Elysia** is an ergonomic web framework for Bun, Node.js, and runtimes that support Web Standard API. Designed to be ergonomic and developer-friendly with a focus on **sound type safety** and performance. ## Performance Elysia has significant performance improvements over Fastify thanks to native Bun implementation, and static code analysis. This comparison uses a historical [TechEmpower Round 22 result](https://www.techempower.com/benchmarks/#hw=ph\&test=plaintext\§ion=data-r22) from 2023-10-17 for Plaintext. These are the recorded framework/runtime combinations, not measurements of Elysia v2. | Framework | Requests per second | | --------- | ------------------: | | Elysia | 2,454,631 | | Fastify | 415,600 | ## Routing Fastify and Elysia have similar routing syntax, using `app.get()` and `app.post()` methods to define routes and similar path parameter syntax. Fastify ```ts import fastify from 'fastify' const app = fastify() app.get('/', (request, reply) => { reply.send('Hello World') }) app.post('/id/:id', (request, reply) => { reply.status(201).send(request.params.id) }) app.listen({ port: 3000 }) ``` > Fastify uses `request` and `reply` as request and response objects Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', 'Hello World') .post( '/id/:id', ({ status, params: { id } }) => { return status(201, id) } ) .listen(3000) ``` > Elysia uses a single `context` and returns the response directly There is a slight difference in style guide. Elysia recommends usage of method chaining and object destructuring. Elysia also supports an inline value for the response if you don’t need to use the context. ## Handler Both have similar properties for accessing input parameters like `headers`, `query`, `params`, and `body`. Elysia automatically parses the request body as JSON, URL-encoded data, and formdata, while Fastify parses JSON and needs `@fastify/formbody` and `@fastify/multipart` for the other two. Fastify ```ts import fastify from 'fastify' const app = fastify() app.post('/user', (request, reply) => { const limit = request.query.limit const name = request.body.name const auth = request.headers.authorization reply.send({ limit, name, auth }) }) ``` > Fastify parses data and puts it into `request` object Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, (ctx) => { const limit = ctx.query.limit const name = ctx.body.name const auth = ctx.headers.authorization return { limit, name, auth } }) ``` > Elysia parses data and puts it into `context` object ## Subrouter Fastify uses a function callback to define a subrouter while Elysia treats every instance as a component that can be plugged and played together. Fastify ```ts import fastify, { FastifyPluginCallback } from 'fastify' const subRouter: FastifyPluginCallback = (app, opts, done) => { app.get('/user', (request, reply) => { reply.send('Hello User') }) done() } const app = fastify() app.register(subRouter, { prefix: '/api' }) ``` > Fastify uses a function callback to declare a sub router Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ prefix: '/api' }) .get('/user', 'Hello User') const app = new Elysia() .use(subRouter) ``` > Elysia treats every instance as a component While Elysia sets the prefix in the constructor, Fastify requires you to set the prefix in the options. ## Validation Elysia has built-in support for request validation with sound type safety out of the box using **TypeBox** while Fastify uses JSON Schema for declaring schemas, and **ajv** for validation. However, it doesn’t infer types automatically, and you need to use a type provider like `@fastify/type-provider-json-schema-to-ts` to infer types. Fastify ```ts import fastify from 'fastify' import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' const app = fastify().withTypeProvider() app.patch( '/user/:id', { schema: { params: { type: 'object', properties: { id: { type: 'string', pattern: '^[0-9]+$' } }, required: ['id'] }, body: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, } }, (request, reply) => { // map string to number request.params.id = +request.params.id reply.send({ params: request.params, body: request.body }) } ) ``` > Fastify uses JSON Schema for validation Elysia TypeBox ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .patch('/user/:id', { params: t.Object({ id: t.Number() }), body: t.Object({ name: t.String() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Zod ```ts import { Elysia } from 'elysia' import { z } from 'zod' const app = new Elysia() .patch('/user/:id', { params: z.object({ id: z.coerce.number() }), body: z.object({ name: z.string() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Valibot ```ts import { Elysia } from 'elysia' import * as v from 'valibot' const app = new Elysia() .patch('/user/:id', { params: v.object({ id: v.pipe(v.string(), v.transform(Number), v.number()) }), body: v.object({ name: v.string() }) }, ({ params, body }) => ({ params, body })) ``` > Elysia uses TypeBox for validation, and coerces types automatically, while supporting various validation libraries like Zod and Valibot with the same syntax as well. Alternatively, Fastify can also use **TypeBox** or **Zod** for validation using `@fastify/type-provider-typebox` to infer types automatically. While Elysia **prefers TypeBox** for validation, Elysia also supports Standard Schema allowing you to use libraries like Zod, Valibot, ArkType, Effect Schema and so on out of the box. ## File upload Fastify uses `fastify-multipart` to handle file upload which uses `Busboy` under the hood while Elysia uses Web Standard API for handling formdata, mimetype validation using declarative API. However, Fastify doesn’t offer a straightforward way for file validation, eg. file size and mimetype, and requires some workarounds to validate the file. Fastify ```ts import fastify from 'fastify' import multipart from '@fastify/multipart' import { fileTypeFromBuffer } from 'file-type' const app = fastify() app.register(multipart, { attachFieldsToBody: 'keyValues' }) app.post( '/upload', { schema: { body: { type: 'object', properties: { file: { type: 'object' } }, required: ['file'] } } }, async (req, res) => { const file = req.body.file if (!file) return res.status(422).send('No file uploaded') const type = await fileTypeFromBuffer(file) if (!type || !type.mime.startsWith('image/')) return res.status(422).send('File is not a valid image') res.header('Content-Type', type.mime) res.send(file) } ) ``` > Fastify uses `fastify-multipart` to handle file uploads and uses a fake `type: object` to allow a Buffer Elysia ```ts import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) const app = new Elysia() .post('/upload', { body: t.Object({ file: t.File({ type: 'image/*' }) }) }, ({ body }) => body.file) ``` > Elysia handles files and mimetype validation using `t.File` File metadata is not proof of the file’s contents. The example uses **file-type** to inspect its bytes. Elysia validates the file schema. Register the `file-type` detector as shown to validate the file bytes. ## Lifecycle Event Both Fastify and Elysia have somewhat similar lifecycle events using an event-based approach. ### Elysia Lifecycle Elysia exposes stages of the [request lifecycle](/essential/life-cycle): | Stage | Purpose | | ----------------------------- | ------------------------------------------------- | | Request | Inspect the incoming request before parsing | | Parse | Decode the request body | | Transform and validation | Normalize and validate request inputs | | Before handle | Derive request values or return an early response | | Handler | Produce the route response | | After handle and map response | Adjust and serialize the response | | After response | Run work after the response is sent | The error hook handles failures in this pipeline. ### Fastify Lifecycle Fastify lifecycle events also use an event-based approach similar to Elysia. Both also have somewhat similar syntax for intercepting the request and response lifecycle events. However, Elysia doesn’t require you to call `done` to continue the lifecycle event. Fastify ```ts import fastify from 'fastify' const app = fastify() // Global middleware app.addHook('onRequest', (request, reply, done) => { console.log(`${request.method} ${request.url}`) done() }) app.get( '/protected', { // Route-specific middleware preHandler(request, reply, done) { const token = request.headers.authorization if (!token) return reply.status(401).send('Unauthorized') done() } }, (request, reply) => { reply.send('Protected route') } ) ``` > Fastify uses `addHook` to register middleware, and requires you to call `done` to continue the lifecycle event Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() // Global middleware .onRequest(({ request }) => { console.log(request.method, new URL(request.url).pathname) }) // Route-specific middleware .get('/protected', { beforeHandle({ status, headers }) { if (!headers.authorization) return status(401) } }, () => 'protected') ``` > Elysia detects the lifecycle event automatically, and doesn’t require you to call `done` to continue the lifecycle event ## Sound type safety Elysia is designed to provide sound type safety. For example, [derive](/essential/life-cycle#derive) adds request-specific context with inferred types. In v2 it also takes over the validated-context role previously served by `resolve`. Fastify ```ts import fastify from 'fastify' const app = fastify() app.decorateRequest('version', 2) app.get('/version', (req, res) => { // Error: Property 'version' does not exist on type 'FastifyRequest' res.send(req.version) }) app.get( '/token', { preHandler(req, res, done) { const token = req.headers.authorization if (!token) return res.status(401).send('Unauthorized') // @ts-ignore req.token = token.split(' ')[1] done() } }, (req, res) => { // Error: Property 'version' does not exist on type 'FastifyRequest' req.version // Error: Property 'token' does not exist on type 'FastifyRequest' res.send(req.token) } ) app.listen({ port: 3000 }) ``` > Fastify uses `decorateRequest` but doesn’t offer sound type safety Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .decorate('version', 2) .get('/version', ({ version }) => version) .derive(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) return { token: authorization.split(' ')[1] } }) // { token: string, version: number } .get('/token', ({ token, version }) => { return token }) ``` > Elysia uses `decorate` to extend the context, and `derive` to add custom properties to the context While Fastify can use `declare module` to extend the `FastifyRequest` interface, it is globally available and doesn’t have sound type safety, and doesn’t guarantee that the property is available in all request handlers. ```ts declare module 'fastify' { interface FastifyRequest { version: number token: string } } ``` > This is required for the above Fastify example to work, which doesn’t offer sound type safety ## Middleware parameter Fastify uses a function to return a Fastify plugin to define named middleware, while Elysia uses [macro](/patterns/macro) to define a custom hook. Fastify ```ts import { findUser } from './users' import fastify from 'fastify' import type { FastifyRequest, FastifyReply } from 'fastify' const app = fastify() const role = (role: 'user' | 'admin') => (request: FastifyRequest, reply: FastifyReply, next: Function) => { const user = findUser(request.headers.authorization) if (!user || user.role !== role) return reply.status(401).send('Unauthorized') // Error: Property 'user' does not exist on type 'FastifyRequest' request.user = user next() } app.get( '/token', { preHandler: role('admin') }, (request, reply) => { // Error: Property 'user' does not exist on type 'FastifyRequest' reply.send(request.user) } ) ``` > Fastify uses a function callback to accept custom arguments for middleware Elysia ```ts import { Elysia } from 'elysia' import { findUser } from './users' const app = new Elysia() .macro({ role: (role: 'user' | 'admin') => ({ derive({ status, headers: { authorization } }) { const user = findUser(authorization) if (!user || user.role !== role) return status(401) return { user } } }) }) // { user: User } .get('/token', { role: 'admin' }, ({ user }) => user) ``` > Elysia uses macro to pass custom arguments to custom middleware Here, `findUser` is application code that verifies the supplied credential and returns a user with a `role`, or no user when verification fails. A header or Bearer prefix check alone is not authentication. While Fastify uses a function callback, it needs to return a function to be placed in an event handler or an object represented as a hook which can be hard to handle when there is a need for multiple custom functions as you need to reconcile them into a single object. ## Error handling Both Fastify and Elysia offer a lifecycle event to handle errors. Fastify ```ts import fastify from 'fastify' const app = fastify() class CustomError extends Error { constructor(message: string) { super(message) this.name = 'CustomError' } } // global error handler app.setErrorHandler((error, request, reply) => { if (error instanceof CustomError) reply.status(500).send({ message: 'Something went wrong!', error }) }) app.get( '/error', { // route-specific error handler errorHandler(error, request, reply) { reply.send({ message: 'Only for this route!', error }) } }, (request, reply) => { throw new CustomError('oh uh') } ) ``` > Fastify uses `setErrorHandler` for a global error handler, and `errorHandler` for a route-specific error handler Elysia ```ts import { Elysia } from 'elysia' class CustomError extends Error { status = 500 } const app = new Elysia() .onError(CustomError, ({ error, status }) => status(500, { message: 'Something went wrong!', detail: error.message }) ) .get('/error', () => { throw new CustomError('oh uh') }) .get('/local-error', { error({ error, status }) { if (error instanceof RangeError) return status(500, { message: 'Only for this route!' }) } }, () => { throw new RangeError('outside the allowed range') }) ``` > Elysia offers class-specific error handlers and a helper for HTTP status responses. While both offer error handling using lifecycle events, Elysia also provides: 1. Class-specific error handlers with inferred error types 2. The `status` helper for returning a response with an HTTP status The `/error` route uses the registered `CustomError` handler. The `/local-error` route handles a different error locally. Error hooks run in the order they’re registered, and the first one that returns a value sends the response. Hooks registered on the app before a route run before that route’s own `error` hook, so the route hook can’t override them. Elysia infers the registered error type in its class-specific handler. ## Encapsulation Fastify encapsulates plugin side-effects, while Elysia gives you control over side-effects of a plugin via an explicit scoping mechanism and order-of-code. Fastify ```ts import fastify from 'fastify' import type { FastifyPluginCallback } from 'fastify' const subRouter: FastifyPluginCallback = (app, opts, done) => { app.addHook('preHandler', async (request, reply) => { if (!request.headers.authorization?.startsWith('Bearer ')) return reply.code(401).send({ error: 'Unauthorized' }) }) done() } const app = fastify() .get('/', (request, reply) => { reply.send('Hello World') }) .register(subRouter) // doesn't have side-effect from subRouter .get('/side-effect', () => 'hi') ``` > Fastify encapsulates side-effects of a plugin Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // doesn't have side-effect from subRouter .get('/side-effect', () => 'hi') ``` > Elysia encapsulates side-effects of a plugin unless explicitly stated Both have an encapsulation mechanism of a plugin to prevent side-effects. However, Elysia can explicitly state which plugin should have side-effects by declaring plugin scope, while Fastify encapsulates by default and needs `fastify-plugin` to share side-effects. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) // Scoped to parent instance but not beyond .as('plugin') // [!code ++] const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // [!code ++] // now have side-effect from subRouter .get('/side-effect', () => 'hi') ``` Elysia offers 3 types of scoping mechanisms: 1. **local** - Apply to this instance and its descendants (default) 2. **plugin** - Share side-effects with the parent instance but not beyond 3. **global** - Share through all parent instances *** To share hooks beyond Fastify’s default encapsulation boundary, the example below creates a function for each hook and appends it manually. However, this can cause duplicated side-effects if not handled carefully. Fastify ```ts import fastify from 'fastify' import type { FastifyRequest, FastifyReply, FastifyPluginCallback } from 'fastify' const log = (request: FastifyRequest, reply: FastifyReply, done: Function) => { console.log('Middleware executed') done() } const app = fastify() app.addHook('onRequest', log) app.get('/main', (request, reply) => { reply.send('Hello from main!') }) const subRouter: FastifyPluginCallback = (app, opts, done) => { app.addHook('onRequest', log) // This would log twice app.get('/sub', (request, reply) => { return reply.send('Hello from sub router!') }) done() } app.register(subRouter, { prefix: '/sub' }) app.listen({ port: 3000 }) ``` In this scenario, Elysia offers a plugin deduplication mechanism to prevent duplicated side-effects. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ name: 'subRouter' }) // [!code ++] .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) .as('plugin') const app = new Elysia() .get('/', 'Hello World') .use(subRouter) .use(subRouter) // [!code ++] .use(subRouter) // [!code ++] .use(subRouter) // [!code ++] // side-effect only called once .get('/side-effect', () => 'hi') ``` By using a unique `name`, Elysia will apply the plugin only once, and will not cause duplicated side-effects. ## Cookie Fastify uses `@fastify/cookie` to parse cookies, while Elysia has built-in support for cookies and uses a signal-based approach to handle cookies. Fastify ```ts import fastify from 'fastify' import cookie from '@fastify/cookie' const app = fastify() app.register(cookie, { secret: 'secret', hook: 'onRequest' }) app.get('/', function (request, reply) { request.unsignCookie(request.cookies.name) reply.setCookie('name', 'value', { path: '/', signed: true }) }) ``` > Fastify uses `unsignCookie` to verify the cookie signature, and `setCookie` to set the cookie Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia({ cookie: { secrets: 'replace-with-a-private-secret', sign: ['name'] } }) .get('/', { cookie: t.Cookie({ name: t.Optional(t.String()) }) }, ({ cookie: { name } }) => { const previous = name.value name.set({ value: 'value', maxAge: 60 * 60 * 24, httpOnly: true, secure: true, sameSite: 'lax' }) return previous ?? 'unset' }) ``` > Elysia verifies and signs the configured cookies automatically. Cookie `maxAge` is in seconds; replace the example secret with a private value and serve secure cookies over HTTPS. ## OpenAPI Both offer OpenAPI documentation using Swagger. However, Elysia defaults to Scalar UI which is a more modern and user-friendly interface for OpenAPI documentation. Fastify ```ts import fastify from 'fastify' import swagger from '@fastify/swagger' const app = fastify() app.register(swagger, { openapi: { info: { title: 'My API', version: '1.0.0' } } }) app.addSchema({ $id: 'user', type: 'object', properties: { name: { type: 'string', description: 'First name only' }, age: { type: 'integer' } }, required: ['name', 'age'] }) app.post( '/users', { schema: { summary: 'Create user', body: { $ref: 'user#' }, response: { '201': { $ref: 'user#' } } } }, (req, res) => { res.status(201).send(req.body) } ) await app.ready() app.swagger() ``` > Fastify uses `@fastify/swagger` for OpenAPI documentation using Swagger Elysia ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' // [!code ++] const app = new Elysia() .use(openapi()) // [!code ++] .model({ user: t.Object({ name: t.String(), age: t.Integer() }) }) .post('/users', { body: 'user', response: { 201: 'user' }, detail: { summary: 'Create user' } }, ({ body, status }) => status(201, body)) ``` > Elysia uses `@elysia/openapi` for OpenAPI documentation with Scalar by default. Both offer model reference using `$ref` for OpenAPI documentation. However, Fastify doesn’t offer type-safety, and auto-completion for specifying model names while Elysia does. ## Testing Fastify tests handlers in process with `fastify.inject()`. Elysia accepts a Web Standard `Request` through `app.handle()`. Neither approach needs a listening network server. Fastify ```ts import fastify from 'fastify' import { describe, it, expect } from 'vitest' function build(opts = {}) { const app = fastify(opts) app.get('/', async function (request, reply) { reply.send({ hello: 'world' }) }) return app } describe('GET /', () => { it('should return the greeting', async () => { const app = build() const response = await app.inject({ url: '/', method: 'GET', }) expect(response.statusCode).toBe(200) expect(response.json()).toEqual({ hello: 'world' }) }) }) ``` > Fastify uses `fastify.inject()` to simulate a network request Elysia ```ts import { Elysia } from 'elysia' import { describe, it, expect } from 'vitest' const app = new Elysia() .get('/', () => ({ hello: 'world' })) describe('GET /', () => { it('should return the greeting', async () => { const res = await app.handle( new Request('http://localhost') ) expect(res.status).toBe(200) expect(await res.json()).toEqual({ hello: 'world' }) }) }) ``` > Elysia uses Web Standard API to handle requests Alternatively, Elysia also offers a helper library called [Eden](/eden) for End-to-end type safety, allowing us to test with auto-completion, and full type safety. Elysia ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' import { describe, expect, it } from 'bun:test' const app = new Elysia().get('/hello', 'Hello World') const api = treaty(app) describe('GET /', () => { it('should return Hello World', async () => { const { data, error, status } = await api.hello.get() expect(status).toBe(200) expect(data).toBe('Hello World') }) }) ``` ## End-to-end type safety Elysia offers built-in support for **end-to-end type safety** without code generation using [Eden](/eden), while Fastify doesn’t offer one. Elysia ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/mirror', { body: t.Object({ message: t.String() }) }, ({ body }) => body) const api = treaty(app) const { data, error } = await api.mirror.post({ message: 'Hello World' }) if (error) // error.status: 422 // error.value: { type: 'validation', title: 'Validation Error', status: 422, on: 'body', detail, errors: [...] } throw error // { message: 'Hello World' } console.log(data) ``` If end-to-end type safety is important for you, then Elysia is the right choice. *** Elysia offers a more ergonomic and developer-friendly experience with a focus on performance, type safety, and simplicity while Fastify is one of the established frameworks for Node.js, but doesn’t have **sound type safety** and **end-to-end type safety** offered by next generation frameworks. If you are looking for a framework that is easy to use, has a great developer experience, and is built on top of Web Standard API, Elysia is the right choice for you. Alternatively, if you are coming from a different framework, you can check out: ### [From Express](/migrate/from-express) [Comparison between Express and Elysia](/migrate/from-express) ### [From Hono](/migrate/from-hono) [Comparison between Hono and Elysia](/migrate/from-hono) ### [From tRPC](/migrate/from-trpc) [Comparison between tRPC and Elysia](/migrate/from-trpc) --- Source: [migrate/from-hono/index.md](migrate/from-hono/index.md) # From Hono to Elysia Elysia v2 The Elysia examples use v2: route options precede the handler, lifecycle methods use their event names, and request-derived values use `derive`. The other framework examples show a typical setup for that framework; they are migration examples rather than a survey of every current capability. Framework integrations have their own release compatibility; check those packages when migrating an application. This guide is for Hono users who want to see the differences from Elysia including syntax, and how to migrate your application from Hono to Elysia by example. **Hono** is a fast and lightweight web framework built on Web Standard. It has broad compatibility with multiple runtimes like Deno, Bun, Cloudflare Workers, and Node.js. **Elysia** is an ergonomic web framework. Designed for developer experience with a focus on **sound type safety** and performance. Not limited to Bun, Elysia also supports multiple runtimes like Node.js, and Cloudflare Workers. ## When to use Here’s a TLDR comparison between Hono and Elysia to help you decide: **Hono** - **Originally built for Cloudflare Workers**, more integrated with the Cloudflare ecosystem - Supports multiple runtimes with Web Standard, including **Node.js** and **Bun** - Lightweight and minimalistic, suitable for edge environments - Supports OpenAPI but requires additional effort to describe the specification - Prefers a simple, linear middleware-based approach - Type safety is better than most frameworks, but not sound in some areas - Somewhat similar to Express, Koa in terms of middleware, plugin style **Elysia** - **Originally built for native Bun**, uses most of Bun’s features to the fullest extent - Supports multiple runtimes with Web Standard, including **Node.js** and **Cloudflare Workers** - **Better performance**. Geared toward long-running servers via JIT. - **Better OpenAPI support** with a seamless experience, especially with [OpenAPI Type Gen](/patterns/openapi#openapi-from-types) - Prefers an event-based lifecycle approach for better control over the request pipeline - Sound type safety across the board, including middleware, and error handling - Somewhat similar to Fastify in terms of middleware, encapsulation, and plugin style There is a huge **difference between being compatible and specifically built for** something. If you decide to use Elysia on Cloudflare Workers, you might miss some of the Cloudflare-specific features that Hono provides out of the box. Similarly, if you use Hono on Bun, you might not get the best performance possible compared to using Elysia. ## Performance Elysia has historically benchmarked faster than Hono thanks to static code analysis. This comparison uses a historical [TechEmpower Round 23 result](https://www.techempower.com/benchmarks/#section=data-r23\&test=json) from 2025-02-24 for JSON serialization. These are the recorded framework/runtime combinations, not measurements of Elysia v2. | Framework | Requests per second | | --------- | ------------------: | | Elysia | 1,837,294 | | Hono | 740,451 | ## Routing Hono and Elysia have similar routing syntax, using `app.get()` and `app.post()` methods to define routes and similar path parameter syntax. Both use a single `Context` parameter to handle requests and responses, and return a response directly. Hono ```ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => { return c.text('Hello World') }) app.post('/id/:id', (c) => { c.status(201) return c.text(c.req.param('id')) }) export default app ``` > Hono uses helpers `c.text` and `c.json` to return a response Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', 'Hello World') .post( '/id/:id', ({ status, params: { id } }) => { return status(201, id) } ) .listen(3000) ``` > Elysia uses a single `context` and returns the response directly While Hono uses `c.text` and `c.json` to wrap a response, Elysia maps a value to a response automatically. There is a slight difference in style guide. Elysia recommends usage of method chaining and object destructuring. Hono port allocation depends on the runtime and adapter while Elysia uses a single `listen` method to start the server. ## Handler Hono uses a function to parse query, header, and body manually while Elysia automatically parses properties. Hono ```ts import { Hono } from 'hono' const app = new Hono() app.post('/user', async (c) => { const limit = c.req.query('limit') const { name } = await c.req.json() const auth = c.req.header('authorization') return c.json({ limit, name, auth }) }) ``` > Hono reads JSON with `c.req.json()` and exposes query and header accessors on `c.req`. Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, (ctx) => { const limit = ctx.query.limit const name = ctx.body.name const auth = ctx.headers.authorization return { limit, name, auth } }) ``` > Elysia uses static code analysis to analyze what to parse Elysia uses **static code analysis** to determine what to parse, and only parses the required properties. This is useful for performance and type safety. ## Subrouter Both can inherit another instance as a router, but Elysia treats every instance as a component which can be used as a subrouter. Hono ```ts import { Hono } from 'hono' const subRouter = new Hono() subRouter.get('/user', (c) => { return c.text('Hello User') }) const app = new Hono() app.route('/api', subRouter) ``` > Hono **requires** a prefix to separate the subrouter Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ prefix: '/api' }) .get('/user', 'Hello User') const app = new Elysia() .use(subRouter) ``` > Elysia uses an optional constructor prefix to define one While Hono requires a prefix to separate the subrouter, Elysia doesn’t require a prefix to separate the subrouter. ## Validation While Hono supports various validators via external packages, Elysia has built-in validation using **TypeBox**, and supports Standard Schema out of the box allowing you to use your favorite library like Zod, Valibot, ArkType, Effect Schema and so on without additional libraries. Elysia also offers seamless integration with OpenAPI, and type inference behind the scenes. Hono ```ts import { Hono } from 'hono' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const app = new Hono() app.patch( '/user/:id', zValidator( 'param', z.object({ id: z.coerce.number() }) ), zValidator( 'json', z.object({ name: z.string() }) ), (c) => { return c.json({ params: c.req.valid('param'), body: c.req.valid('json') }) } ) ``` > Hono uses a pipe-based approach Elysia TypeBox ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .patch('/user/:id', { params: t.Object({ id: t.Number() }), body: t.Object({ name: t.String() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Zod ```ts import { Elysia } from 'elysia' import { z } from 'zod' const app = new Elysia() .patch('/user/:id', { params: z.object({ id: z.coerce.number() }), body: z.object({ name: z.string() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Valibot ```ts import { Elysia } from 'elysia' import * as v from 'valibot' const app = new Elysia() .patch('/user/:id', { params: v.object({ id: v.pipe(v.string(), v.transform(Number), v.number()) }), body: v.object({ name: v.string() }) }, ({ params, body }) => ({ params, body })) ``` > Elysia uses TypeBox for validation, and coerces types automatically, while supporting various validation libraries like Zod and Valibot with the same syntax as well. Both offer type inference from schema to context automatically. ## File upload Both Hono and Elysia use Web Standard API to handle file upload, and both use **file-type** to validate mimetype. Elysia registers it once and validates declaratively. Hono ```ts import { Hono } from 'hono' import { z } from 'zod' import { zValidator } from '@hono/zod-validator' import { fileTypeFromBlob } from 'file-type' const app = new Hono() app.post( '/upload', zValidator( 'form', z.object({ file: z.instanceof(File) }) ), async (c) => { const body = c.req.valid('form') const type = await fileTypeFromBlob(body.file) if (!type || !type.mime.startsWith('image/')) { c.status(422) return c.text('File is not a valid image') } return new Response(body.file) } ) ``` > Hono calls `file-type` in the handler to validate mimetype Elysia ```ts import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) const app = new Elysia() .post('/upload', { body: t.Object({ file: t.File({ type: 'image/*' }) }) }, ({ body }) => body.file) ``` > Elysia registers `file-type` once and validates mimetype declaratively As Web Standard API doesn’t validate mimetype, it is a security risk to trust `content-type` provided by the client so both use `file-type` to inspect the file’s bytes. Elysia registers the detector once with `setFileTypeDetector`, then checks the type declaratively with `t.File({ type })`. The detector is required: without it, every upload checked against `type` fails validation. ## Middleware Hono middleware uses a single queue-based order similar to Express while Elysia gives you more granular control using an **event-based** lifecycle. Elysia exposes stages of the [request lifecycle](/essential/life-cycle): | Stage | Purpose | | ----------------------------- | ------------------------------------------------- | | Request | Inspect the incoming request before parsing | | Parse | Decode the request body | | Transform and validation | Normalize and validate request inputs | | Before handle | Derive request values or return an early response | | Handler | Produce the route response | | After handle and map response | Adjust and serialize the response | | After response | Run work after the response is sent | The error hook handles failures in this pipeline. While Hono has a single flow for the request pipeline in order, Elysia can intercept each event in a request pipeline. Hono ```ts import { Hono } from 'hono' const app = new Hono() // Global middleware app.use(async (c, next) => { console.log(`${c.req.method} ${c.req.url}`) await next() }) app.get( '/protected', // Route-specific middleware async (c, next) => { const token = c.req.header('authorization') if (!token) { c.status(401) return c.text('Unauthorized') } await next() }, (c) => c.text('Protected route') ) ``` > Hono uses a single queue-based order for middleware which executes in order Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() // Global middleware .onRequest(({ request }) => { console.log(request.method, new URL(request.url).pathname) }) // Route-specific middleware .get('/protected', { beforeHandle({ status, headers }) { if (!headers.authorization) return status(401) } }, () => 'protected') ``` > Elysia uses a specific event interceptor for each point in the request pipeline While Hono has a `next` function to call the next middleware, Elysia does not have one. ## Sound type safety Elysia is designed to provide sound type safety. For example, [derive](/essential/life-cycle#derive) adds request-specific context with inferred types. In v2 it also takes over the validated-context role previously served by `resolve`. Hono ```ts import { Hono } from 'hono' import { createMiddleware } from 'hono/factory' const app = new Hono() const getVersion = createMiddleware(async (c, next) => { c.set('version', 2) await next() }) app.use(getVersion) app.get('/version', getVersion, (c) => { return c.text(c.get('version') + '') }) const authenticate = createMiddleware(async (c, next) => { const token = c.req.header('authorization') if (!token) { c.status(401) return c.text('Unauthorized') } c.set('token', token.split(' ')[1]) await next() }) app.post('/user', authenticate, async (c) => { c.get('version') return c.text(c.get('token')) }) ``` > Hono uses middleware to extend the context, but is not type safe Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .decorate('version', 2) .get('/version', ({ version }) => version) .derive(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) return { token: authorization.split(' ')[1] } }) // { token: string, version: number } .get('/token', ({ token, version }) => { return token }) ``` > Elysia uses `decorate` to extend the context, and `derive` to add custom properties to the context The Bearer prefix check stands in for real credential verification. A header or Bearer prefix check alone is not authentication. While Hono can use `declare module` to extend the `ContextVariableMap` interface, it is globally available and doesn’t have sound type safety, and doesn’t guarantee that the property is available in all request handlers. ```ts declare module 'hono' { interface ContextVariableMap { version: number token: string } } ``` > This is required for the above Hono example to work, which doesn’t offer sound type safety ## Middleware parameter Hono uses a callback function to define reusable route-specific middleware, while Elysia uses [macro](/patterns/macro) to define a custom hook. Hono ```ts import { findUser } from './users' import { Hono } from 'hono' import { createMiddleware } from 'hono/factory' const app = new Hono() const role = (role: 'user' | 'admin') => createMiddleware(async (c, next) => { const user = findUser(c.req.header('Authorization')) if(!user || user.role !== role) { c.status(401) return c.text('Unauthorized') } c.set('user', user) await next() }) app.get('/user/:id', role('admin'), (c) => { // Error: No overload matches this call. return c.json(c.get('user')) }) ``` > Hono uses a callback to return `createMiddleware` to create reusable middleware, but is not type safe Elysia ```ts import { Elysia } from 'elysia' import { findUser } from './users' const app = new Elysia() .macro({ role: (role: 'user' | 'admin') => ({ derive({ status, headers: { authorization } }) { const user = findUser(authorization) if (!user || user.role !== role) return status(401) return { user } } }) }) // { user: User } .get('/token', { role: 'admin' }, ({ user }) => user) ``` > Elysia uses macro to pass custom arguments to custom middleware Here, `findUser` is application code that verifies the supplied credential and returns a user with a `role`, or no user when verification fails. A header or Bearer prefix check alone is not authentication. ## Error handling Hono provides an `onError` function which applies to all routes while Elysia provides more granular control over error handling. Hono ```ts import { Hono } from 'hono' const app = new Hono() class CustomError extends Error { constructor(message: string) { super(message) this.name = 'CustomError' } } // global error handler app.onError((error, c) => { if(error instanceof CustomError) { c.status(500) return c.json({ message: 'Something went wrong!', error }) } return c.text('Internal Server Error', 500) }) // This route throws an error handled by onError. app.get('/error', () => { throw new CustomError('oh uh') }) ``` > Hono uses the `onError` function to handle errors, a single error handler for all routes Elysia ```ts import { Elysia } from 'elysia' class CustomError extends Error { status = 500 } const app = new Elysia() .onError(CustomError, ({ error, status }) => status(500, { message: 'Something went wrong!', detail: error.message }) ) .get('/error', () => { throw new CustomError('oh uh') }) .get('/local-error', { error({ error, status }) { if (error instanceof RangeError) return status(500, { message: 'Only for this route!' }) } }, () => { throw new RangeError('outside the allowed range') }) ``` > Elysia provides more granular control over error handling, and scoping mechanisms While Hono offers `onError` per app, Elysia provides: 1. Both global and route-specific error handlers 2. The `status` helper for returning a response with an HTTP status 3. Errors with a `status` (or `HTTPError`) map to their HTTP status The `/error` route uses the registered `CustomError` handler. The `/local-error` route handles a different error locally. Error hooks run in the order they’re registered, and the first one that returns a value sends the response. Hooks registered on the app before a route run before that route’s own `error` hook, so the route hook can’t override them. Elysia infers the registered error type in its class-specific handler. ## Encapsulation Hono encapsulates plugin side-effects, while Elysia gives you control over side-effects of a plugin via an explicit scoping mechanism and order-of-code. Hono ```ts import { Hono } from 'hono' const subRouter = new Hono() subRouter.use(async (c, next) => { if (!c.req.header('authorization')?.startsWith('Bearer ')) return c.text('Unauthorized', 401) await next() }) subRouter.get('/user', (c) => { return c.text('Hello User') }) const app = new Hono() app.route('/api', subRouter) // doesn't have side-effect from subRouter app.get('/side-effect', (c) => c.text('hi')) ``` > Hono encapsulates side-effects of a plugin Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // doesn't have side-effect from subRouter .get('/side-effect', () => 'hi') ``` > Elysia encapsulates side-effects of a plugin unless explicitly stated Both have an encapsulation mechanism for plugins to prevent side-effects. However, Elysia can explicitly state which plugin should have side-effects by declaring plugin scope, while Hono keeps middleware on the router it’s registered on. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) // Scoped to parent instance but not beyond .as('plugin') // [!code ++] const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // [!code ++] // now has side-effects from subRouter .get('/side-effect', () => 'hi') ``` Elysia offers 3 types of scoping mechanisms: 1. **local** - Apply to the current instance and its descendants (default) 2. **plugin** - Share side-effects with the parent instance but not beyond 3. **global** - Share through all parent instances *** As Hono can’t share a sub-router’s middleware with its parent, the example below creates a function for each hook and appends it manually. However, this can cause duplicated side-effects if not handled carefully. Hono ```ts import { Hono } from 'hono' import { createMiddleware } from 'hono/factory' const middleware = createMiddleware(async (c, next) => { console.log('called') await next() }) const app = new Hono() const subRouter = new Hono() app.use(middleware) app.get('/main', (c) => c.text('Hello from main!')) subRouter.use(middleware) // This would log twice subRouter.get('/sub', (c) => c.text('Hello from sub router!')) app.route('/sub', subRouter) export default app ``` In this scenario, Elysia offers a plugin deduplication mechanism to prevent duplicated side-effects. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ name: 'subRouter' }) // [!code ++] .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) .as('plugin') const app = new Elysia() .get('/', 'Hello World') .use(subRouter) .use(subRouter) // [!code ++] .use(subRouter) // [!code ++] .use(subRouter) // [!code ++] // side-effect only called once .get('/side-effect', () => 'hi') ``` By using a unique `name`, Elysia will apply the plugin only once, and will not cause duplicated side-effects. ## Cookie Hono has built-in cookie utility functions under `hono/cookie`, while Elysia uses a signal-based approach to handle cookies. Hono ```ts import { Hono } from 'hono' import { getSignedCookie, setSignedCookie } from 'hono/cookie' const app = new Hono() app.get('/', async (c) => { const name = await getSignedCookie(c, 'secret', 'name') await setSignedCookie( c, 'name', 'value', 'secret', { maxAge: 1000, } ) }) ``` > Hono uses utility functions to handle cookies Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia({ cookie: { secrets: 'replace-with-a-private-secret', sign: ['name'] } }) .get('/', { cookie: t.Cookie({ name: t.Optional(t.String()) }) }, ({ cookie: { name } }) => { const previous = name.value name.set({ value: 'value', maxAge: 60 * 60 * 24, httpOnly: true, secure: true, sameSite: 'lax' }) return previous ?? 'unset' }) ``` > Elysia verifies and signs the configured cookies automatically. Cookie `maxAge` is in seconds; replace the example secret with a private value and serve secure cookies over HTTPS. ## OpenAPI Hono requires additional effort to describe the specification, while Elysia seamlessly integrates the specification into the schema. Hono ```ts import { Hono } from 'hono' import { describeRoute, openAPISpecs } from 'hono-openapi' import { resolver, validator as zodValidator } from 'hono-openapi/zod' import { swaggerUI } from '@hono/swagger-ui' import { z } from '@hono/zod-openapi' const app = new Hono() const model = z.array( z.object({ name: z.string().openapi({ description: 'first name only' }), age: z.number() }) ) const detail = await resolver(model).builder() console.log(detail) app.post( '/', zodValidator('json', model), describeRoute({ validateResponse: true, summary: 'Create user', requestBody: { content: { 'application/json': { schema: detail.schema } } }, responses: { 201: { description: 'User created', content: { 'application/json': { schema: resolver(model) } } } } }), (c) => { c.status(201) return c.json(c.req.valid('json')) } ) app.get('/ui', swaggerUI({ url: '/doc' })) app.get( '/doc', openAPISpecs(app, { documentation: { info: { title: 'Hono API', version: '1.0.0', description: 'Greeting API' }, components: { ...detail.components } } }) ) export default app ``` > Hono requires additional effort to describe the specification Elysia ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' // [!code ++] const app = new Elysia() .use(openapi()) // [!code ++] .model({ user: t.Array( t.Object({ name: t.String(), age: t.Number() }) ) }) .post('/users', { body: 'user', response: { 201: 'user' }, detail: { summary: 'Create user' } }, ({ body, status }) => status(201, body)) ``` > Elysia seamlessly integrates the specification into the schema Hono has a separate function to describe route specification, validation, and requires some effort to set up properly. Elysia uses the schema you provide to generate the OpenAPI specification, and validates the request/response, and infers types automatically all from a **single source of truth**. Elysia also appends the schema registered in `model` to the OpenAPI spec, allowing you to reference the model in a dedicated section in Swagger or Scalar UI while Hono inlines the schema to the route. ## Testing Both are built on top of Web Standard API allowing them to be used with any testing library. Hono ```ts import { Hono } from 'hono' import { describe, it, expect } from 'vitest' const app = new Hono() .get('/', (c) => c.text('Hello World')) describe('GET /', () => { it('should return Hello World', async () => { const res = await app.request('/') expect(res.status).toBe(200) expect(await res.text()).toBe('Hello World') }) }) ``` > Hono has a built-in `request` method to run the request Elysia ```ts import { Elysia } from 'elysia' import { describe, it, expect } from 'vitest' const app = new Elysia() .get('/', 'Hello World') describe('GET /', () => { it('should return Hello World', async () => { const res = await app.handle( new Request('http://localhost') ) expect(res.status).toBe(200) expect(await res.text()).toBe('Hello World') }) }) ``` > Elysia uses Web Standard API to handle requests and responses Alternatively, Elysia also offers a helper library called [Eden](/eden) for End-to-end type safety, allowing us to test with auto-completion, and full type safety. Elysia ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' import { describe, expect, it } from 'bun:test' const app = new Elysia().get('/hello', 'Hello World') const api = treaty(app) describe('GET /', () => { it('should return Hello World', async () => { const { data, error, status } = await api.hello.get() expect(status).toBe(200) expect(data).toBe('Hello World') }) }) ``` ## End-to-end type safety Both offer end-to-end type safety. However, Hono doesn’t seem to offer type-safe error handling based on status code. Hono ```ts import { Hono } from 'hono' import { hc } from 'hono/client' import { z } from 'zod' import { zValidator } from '@hono/zod-validator' const app = new Hono() .post( '/mirror', zValidator( 'json', z.object({ message: z.string() }) ), (c) => c.json(c.req.valid('json')) ) const client = hc('/') const response = await client.mirror.$post({ json: { message: 'Hello, world!' } }) // { message: string } const data = await response.json() ``` > Hono uses `hc` to run the request, and offers end-to-end type safety Elysia ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/mirror', { body: t.Object({ message: t.String() }) }, ({ body }) => body) const api = treaty(app) const { data, error } = await api.mirror.post({ message: 'Hello World' }) if (error) // error.status: 422 // error.value: { type: 'validation', title: 'Validation Error', status: 422, on: 'body', detail, errors: [...] } throw error // { message: 'Hello World' } console.log(data) ``` > Elysia uses `treaty` to run the request, and offers end-to-end type safety While both offer end-to-end type safety, Elysia offers more type-safe error handling based on status code, while Hono doesn’t. The following type-inference timings were recorded for the versions and schema in these screenshots. They are historical observations, not a benchmark of current releases. ![Elysia eden type inference performance](/migrate/elysia-type-infer.webp) > Elysia takes 536ms to infer both Elysia, and Eden ![Hono HC type inference performance](/migrate/hono-type-infer.webp) > Hono takes 1.27s to infer both Hono, and HC with error (aborted) The 1.27 seconds doesn’t reflect the entire duration of the inference, but a duration from the start until it was aborted by the error **“Type instantiation is excessively deep and possibly infinite.”** which happens when the schemas are too large. ![Hono HC code showing excessively deep error](/migrate/hono-hc-infer.webp) > Hono HC showing excessively deep error In that test, Hono HC reached the TypeScript instantiation limit with over 100 routes and complex body and response schemas. This is not a general route limit; compiler versions, schema structure, and client types affect the result. ![Elysia Eden code showing type inference without error](/migrate/elysia-eden-infer.webp) > Elysia Eden code showing type inference without error The author reported successful inference for the Elysia and Eden configuration with up to 2,000 routes. Because the Hono run aborted, its elapsed time cannot establish a completed-check speed ratio. Test both clients against your own route and schema structure when evaluating end-to-end type safety. *** Both are next generation web frameworks built on top of Web Standard API with slight differences. Elysia is designed to be ergonomic and developer-friendly with a focus on **sound type safety**, and has better performance than Hono. Hono offers broad compatibility with multiple runtimes, especially with Cloudflare Workers, and a larger user base. Alternatively, if you are coming from a different framework, you can check out: ### [From Express](/migrate/from-express) [Comparison between Express and Elysia](/migrate/from-express) ### [From Fastify](/migrate/from-fastify) [Comparison between Fastify and Elysia](/migrate/from-fastify) ### [From tRPC](/migrate/from-trpc) [Comparison between tRPC and Elysia](/migrate/from-trpc) --- Source: [migrate/from-trpc/index.md](migrate/from-trpc/index.md) # From tRPC to Elysia Elysia v2 The Elysia examples use v2: route options precede the handler, lifecycle methods use their event names, and request-derived values use `derive`. The other framework examples show a typical setup for that framework; they are migration examples rather than a survey of every current capability. Framework integrations have their own release compatibility; check those packages when migrating an application. This guide is for tRPC users who want to see the differences from Elysia including syntax, and how to migrate your application from tRPC to Elysia by example. **tRPC** is a typesafe RPC framework for building APIs using TypeScript. It provides a way to create end-to-end type-safe APIs with a type-safe contract between frontend and backend. **Elysia** is an ergonomic web framework. Designed to be ergonomic and developer-friendly with a focus on **sound type safety** and performance. ## Overview tRPC is primarily designed for RPC communication with a proprietary abstraction over RESTful APIs, while Elysia is focused on RESTful APIs. The main feature of tRPC is an end-to-end type safety contract between frontend and backend which Elysia also offers via [Eden](/eden). This makes Elysia a better fit for building a universal API with a RESTful standard that developers already know instead of learning a new proprietary abstraction while having the end-to-end type safety that tRPC offers. ## Routing Elysia uses a syntax similar to Express, and Hono like `app.get()` and `app.post()` methods to define routes and similar path parameter syntax. tRPC uses a nested router approach to define routes. tRPC ```ts import { initTRPC } from '@trpc/server' import { createHTTPServer } from '@trpc/server/adapters/standalone' const t = initTRPC.create() const appRouter = t.router({ hello: t.procedure.query(() => 'Hello World'), user: t.router({ getById: t.procedure .input((id: string) => id) .query(({ input }) => { return { id: input } }) }) }) const server = createHTTPServer({ router: appRouter }) server.listen(3000) ``` > tRPC uses nested routers and procedures to define routes Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', 'Hello World') .get('/user/:id', ({ params: { id } }) => ({ id })) .listen(3000) ``` > Elysia uses HTTP methods and path parameters to define routes While tRPC uses a proprietary abstraction over RESTful APIs with procedures and routers, Elysia uses a syntax similar to Express, and Hono like `app.get()` and `app.post()` methods to define routes and similar path parameter syntax. ## Handler A tRPC handler is called `procedure` which can be either `query` or `mutation`, while Elysia uses HTTP methods like `get`, `post`, `put`, `delete` and so on. tRPC doesn’t have a concept of HTTP properties like query, headers, status code, and so on, only `input` and `output`. tRPC ```ts import { initTRPC } from '@trpc/server' const t = initTRPC.create() const appRouter = t.router({ user: t.procedure .input((val: { limit?: number; name: string; authorization?: string }) => val) .mutation(({ input }) => { const limit = input.limit const name = input.name const auth = input.authorization return { limit, name, auth } }) }) ``` > tRPC uses a single `input` for all properties Elysia ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .post('/user', { body: t.Object({ name: t.String() }) }, (ctx) => { const limit = ctx.query.limit const name = ctx.body.name const auth = ctx.headers.authorization return { limit, name, auth } }) ``` > Elysia uses a specific property for each HTTP property Elysia uses **static code analysis** to determine what to parse, and only parses the required properties. This is useful for performance and type safety. ## Subrouter tRPC uses nested routers to define subrouters, while Elysia uses the `.use()` method to define a subrouter. tRPC ```ts import { initTRPC } from '@trpc/server' const t = initTRPC.create() const subRouter = t.router({ user: t.procedure.query(() => 'Hello User') }) const appRouter = t.router({ api: subRouter }) ``` > tRPC uses nested routers to define subrouters Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia({ prefix: '/api' }) .get('/user', 'Hello User') const app = new Elysia() .use(subRouter) ``` > Elysia uses a `.use()` method to define a subrouter While you can inline the subrouter in tRPC, Elysia uses the `.use()` method to define a subrouter. ## Validation Both support Standard Schema for validation, allowing you to use various validation libraries like Zod, Yup, Valibot, and so on. tRPC ```ts import { initTRPC } from '@trpc/server' import { z } from 'zod' const t = initTRPC.create() const appRouter = t.router({ user: t.procedure .input( z.object({ id: z.number(), name: z.string() }) ) .mutation(({ input }) => input) // ^? }) ``` > tRPC uses `input` to define a validation schema Elysia TypeBox ```ts import { Elysia, t } from 'elysia' const app = new Elysia() .patch('/user/:id', { params: t.Object({ id: t.Number() }), body: t.Object({ name: t.String() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Zod ```ts import { Elysia } from 'elysia' import { z } from 'zod' const app = new Elysia() .patch('/user/:id', { params: z.object({ id: z.coerce.number() }), body: z.object({ name: z.string() }) }, ({ params, body }) => ({ params, body })) ``` Elysia Valibot ```ts import { Elysia } from 'elysia' import * as v from 'valibot' const app = new Elysia() .patch('/user/:id', { params: v.object({ id: v.pipe(v.string(), v.transform(Number), v.number()) }), body: v.object({ name: v.string() }) }, ({ params, body }) => ({ params, body })) ``` > Elysia uses a specific property to define a validation schema Both offer type inference from schema to context automatically. ## File upload This tRPC example transports a file as a base64 string and checks its bytes explicitly. This adds encoding overhead; the available upload options depend on your tRPC version and transport. Elysia handles file upload with Web Standard API, and checks the bytes with the same `file-type` library, registered once. tRPC ```ts import { initTRPC, TRPCError } from '@trpc/server' import { z } from 'zod' import { fileTypeFromBuffer } from 'file-type' import { Buffer } from 'node:buffer' const t = initTRPC.create() export const uploadRouter = t.router({ uploadImage: t.procedure .input(z.base64()) .mutation(async ({ input }) => { const buffer = Buffer.from(input, 'base64') const type = await fileTypeFromBuffer(buffer) if (!type || !type.mime.startsWith('image/')) throw new TRPCError({ code: 'UNPROCESSABLE_CONTENT', message: 'Invalid file type', }) return input }) }) ``` > tRPC sends the file as base64 and calls `file-type` in the procedure Elysia ```ts import { Elysia, t, setFileTypeDetector } from 'elysia' import { fileTypeFromBlob } from 'file-type' setFileTypeDetector(fileTypeFromBlob) const app = new Elysia() .post('/upload', { body: t.Object({ file: t.File({ type: 'image/*' }) }) }, ({ body }) => body.file) ``` > Elysia registers `file-type` once and validates mimetype declaratively Neither checks the file bytes on its own, so both use `file-type`. Elysia registers the detector once with `setFileTypeDetector`, then checks the type declaratively with `t.File({ type })`. The detector is required: without it, every upload checked against `type` fails validation. ## Middleware tRPC middleware uses a single queue-based order with `next` similar to Express, while Elysia gives you more granular control using an **event-based** lifecycle. Elysia exposes stages of the [request lifecycle](/essential/life-cycle): | Stage | Purpose | | ----------------------------- | ------------------------------------------------- | | Request | Inspect the incoming request before parsing | | Parse | Decode the request body | | Transform and validation | Normalize and validate request inputs | | Before handle | Derive request values or return an early response | | Handler | Produce the route response | | After handle and map response | Adjust and serialize the response | | After response | Run work after the response is sent | The error hook handles failures in this pipeline. While tRPC has a single flow for the request pipeline in order, Elysia can intercept each event in a request pipeline. tRPC ```ts import { initTRPC } from '@trpc/server' const t = initTRPC.create() const log = t.middleware(async ({ ctx, next }) => { console.log('Request started') const result = await next() console.log('Request ended') return result }) const appRouter = t.router({ hello: t.procedure.use(log).query(() => 'Hello World') }) ``` > tRPC uses a single middleware queue defined as a procedure Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() // Global middleware .onRequest(({ request }) => { console.log(request.method, new URL(request.url).pathname) }) // Route-specific middleware .get('/protected', { beforeHandle({ status, headers }) { if (!headers.authorization) return status(401) } }, () => 'protected') ``` > Elysia uses a specific event interceptor for each point in the request pipeline While tRPC has a `next` function to call the next middleware in the queue, Elysia uses a specific event interceptor for each point in the request pipeline. ## Sound type safety Elysia is designed to provide sound type safety. For example, [derive](/essential/life-cycle#derive) adds request-specific context with inferred types. In v2 it also takes over the validated-context role previously served by `resolve`. tRPC ```ts import { initTRPC } from '@trpc/server' const t = initTRPC.context<{ version: number token: string }>().create() const appRouter = t.router({ version: t.procedure.query(({ ctx: { version } }) => version), token: t.procedure.query(({ ctx: { token, version } }) => { return token }) }) ``` > tRPC uses `context` to extend context but doesn’t have sound type safety Elysia ```ts import { Elysia } from 'elysia' const app = new Elysia() .decorate('version', 2) .get('/version', ({ version }) => version) .derive(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) return { token: authorization.split(' ')[1] } }) // { token: string, version: number } .get('/token', ({ token, version }) => { return token }) ``` > Elysia uses `decorate` to extend the context, and `derive` to add custom properties to the context ## Middleware parameter Both support custom middleware, but Elysia uses macro to pass custom arguments to custom middleware while tRPC uses a higher-order function. tRPC ```ts import { initTRPC, TRPCError } from '@trpc/server' import { findUser } from './users' const t = initTRPC.context<{ authorization?: string }>().create() const role = (role: 'user' | 'admin') => t.middleware(({ ctx, next }) => { const user = findUser(ctx.authorization) if (!user || user.role !== role) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized', }) return next({ ctx: { user } }) }) const appRouter = t.router({ token: t.procedure .use(role('admin')) .query(({ ctx: { user } }) => user) }) ``` > tRPC uses a higher-order function to pass custom arguments to custom middleware Elysia ```ts import { Elysia } from 'elysia' import { findUser } from './users' const app = new Elysia() .macro({ role: (role: 'user' | 'admin') => ({ derive({ status, headers: { authorization } }) { const user = findUser(authorization) if (!user || user.role !== role) return status(401) return { user } } }) }) // The handler receives the user returned by the macro. .get('/token', { role: 'admin' }, ({ user }) => user) ``` > Elysia uses macro to pass custom arguments to custom middleware Here, `findUser` is application code that verifies the supplied credential and returns a user with a `role`, or no user when verification fails. A header or Bearer prefix check alone is not authentication. ## Error handling tRPC uses a middleware-like approach to handle errors, while Elysia provides custom errors with type safety and error interceptors for both global and route-specific error handlers. tRPC ```ts import { initTRPC, TRPCError } from '@trpc/server' const t = initTRPC.create() class CustomError extends Error { constructor(message: string) { super(message) this.name = 'CustomError' } } const procedure = t.procedure.use(async ({ next }) => { const result = await next() if (!result.ok) { console.log(result.error) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: result.error.message }) } return result }) const appRouter = t.router({ error: procedure.query(() => { throw new CustomError('oh uh') }) }) ``` > tRPC uses a middleware-like approach to handle errors Elysia ```ts import { Elysia } from 'elysia' class CustomError extends Error { status = 500 } const app = new Elysia() .onError(CustomError, ({ error, status }) => status(500, { message: 'Something went wrong!', detail: error.message }) ) .get('/error', () => { throw new CustomError('oh uh') }) .get('/local-error', { error({ error, status }) { if (error instanceof RangeError) return status(500, { message: 'Only for this route!' }) } }, () => { throw new RangeError('outside the allowed range') }) ``` > Elysia provides more granular control over error handling, and scoping mechanisms While tRPC offers error handling using a middleware-like approach, Elysia provides: 1. Both global and route-specific error handlers 2. The `status` helper for returning a response with an HTTP status 3. Errors with a `status` (or `HTTPError`) map to their HTTP status The `/error` route uses the registered `CustomError` handler. The `/local-error` route handles a different error locally. Error hooks run in the order they’re registered, and the first one that returns a value sends the response. Hooks registered on the app before a route run before that route’s own `error` hook, so the route hook can’t override them. Elysia infers the registered error type in its class-specific handler. ## Encapsulation tRPC encapsulates side-effects of a procedure or router making it always isolated, while Elysia gives you control over side-effects of a plugin via an explicit scoping mechanism and order-of-code. tRPC ```ts import { initTRPC, TRPCError } from '@trpc/server' // Supply these headers from your adapter's createContext callback. type Context = { headers: { authorization?: string } } const t = initTRPC.context().create() const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.headers.authorization?.startsWith('Bearer ')) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' }) return next() }) const subRouter = t.router({ sideEffect: protectedProcedure.mutation(() => 'hi') }) const appRouter = t.router({ // This procedure does not use the protected middleware. hello: t.procedure.query(() => 'Hello World'), api: subRouter }) ``` > tRPC encapsulates side-effects of a plugin into the procedure or router Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // doesn't have side-effect from subRouter .get('/side-effect', () => 'hi') ``` > Elysia encapsulates side-effects of a plugin unless explicitly stated Both have an encapsulation mechanism of a plugin to prevent side-effects. However, Elysia can explicitly state which plugin should have side-effects by declaring plugin scope while tRPC always encapsulates it. Elysia ```ts import { Elysia } from 'elysia' const subRouter = new Elysia() .onBeforeHandle(({ status, headers: { authorization } }) => { if(!authorization?.startsWith('Bearer ')) return status(401) }) // Scoped to parent instance but not beyond .as('plugin') // [!code ++] const app = new Elysia() .get('/', 'Hello World') .use(subRouter) // [!code ++] // now have side-effect from subRouter .get('/side-effect', () => 'hi') ``` Elysia offers 3 types of scoping mechanisms: 1. **local** - Apply to this instance and its descendants (default) 2. **plugin** - Share side-effects with the parent instance but not beyond 3. **global** - Share through all parent instances ## OpenAPI tRPC doesn’t offer OpenAPI first-party, and relies on a third-party library like `trpc-to-openapi` which is not a streamlined solution. Elysia provides OpenAPI through the [@elysia/openapi](/plugins/openapi) plugin in a single line of code. tRPC ```ts import { initTRPC } from '@trpc/server' import { z } from 'zod' import { generateOpenApiDocument, type OpenApiMeta } from 'trpc-to-openapi' const t = initTRPC.meta().create() const appRouter = t.router({ user: t.procedure .meta({ openapi: { method: 'POST', path: '/users', tags: ['User'], summary: 'Create user', } }) .input(z.object({ users: z.array(z.object({ name: z.string(), age: z.number() })) })) .output( z.array( z.object({ name: z.string(), age: z.number() }) ) ) .mutation(({ input }) => input.users) }) export const openApiDocument = generateOpenApiDocument(appRouter, { title: 'tRPC OpenAPI', version: '1.0.0', baseUrl: 'http://localhost:3000' }) ``` > tRPC relies on a third-party library to generate the OpenAPI specification. This plugin requires an object input for mutations, so the example wraps the user array in `users`. Elysia ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' // [!code ++] const app = new Elysia() .use(openapi()) // [!code ++] .model({ user: t.Array( t.Object({ name: t.String(), age: t.Number() }) ) }) .post('/users', { body: 'user', response: { 201: 'user' }, detail: { summary: 'Create user' } }, ({ body, status }) => status(201, body)) ``` > Elysia seamlessly integrates the specification into the schema tRPC relies on a third-party library to generate OpenAPI spec, and **MUST** require that you define a correct path name and HTTP method in the metadata which forces you to be **consistently aware** of how you place a router, and procedure. Elysia uses the schema you provide to generate the OpenAPI specification, and validates the request/response, and infers types automatically all from a **single source of truth**. Elysia also appends the schema registered in `model` to the OpenAPI spec, allowing you to reference the model in a dedicated section in Swagger or Scalar UI while this is missing in tRPC, which inlines the schema into the route. ## Testing Elysia uses Web Standard API to handle requests and responses while tRPC requires a lot of ceremony to run the request using `createCallerFactory`. tRPC ```ts import { describe, it, expect } from 'vitest' import { initTRPC } from '@trpc/server' import { z } from 'zod' const t = initTRPC.create() const publicProcedure = t.procedure const { createCallerFactory, router } = t const appRouter = router({ post: router({ add: publicProcedure .input( z.object({ title: z.string().min(2) }) ) .mutation(({ input }) => input) }) }) const createCaller = createCallerFactory(appRouter) const caller = createCaller({}) describe('post.add', () => { it('should return the new post', async () => { const newPost = await caller.post.add({ title: '74 Itoki Hana' }) expect(newPost).toEqual({ title: '74 Itoki Hana' }) }) }) ``` > tRPC requires `createCallerFactory`, and a lot of ceremony to run the request Elysia ```ts import { Elysia, t } from 'elysia' import { describe, it, expect } from 'vitest' const app = new Elysia() .post('/add', { body: t.Object({ title: t.String({ minLength: 2 }) }) }, ({ body }) => body) describe('POST /add', () => { it('should return the new post', async () => { const res = await app.handle( new Request('http://localhost/add', { method: 'POST', body: JSON.stringify({ title: '74 Itoki Hana' }), headers: { 'Content-Type': 'application/json' } }) ) expect(res.status).toBe(200) expect(await res.json()).toEqual({ title: '74 Itoki Hana' }) }) }) ``` > Elysia uses Web Standard API to handle requests and responses Alternatively, Elysia also offers a helper library called [Eden](/eden) for End-to-end type safety which is similar to `tRPC.createCallerFactory`, allowing us to test with auto-completion, and full type safety like tRPC without the ceremony. Elysia ```ts import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' import { describe, expect, it } from 'bun:test' const app = new Elysia().get('/hello', 'Hello World') const api = treaty(app) describe('GET /', () => { it('should return Hello World', async () => { const { data, error, status } = await api.hello.get() expect(status).toBe(200) expect(data).toBe('Hello World') }) }) ``` ## End-to-end type safety Both offer end-to-end type safety for client-server communication. tRPC ```ts import { initTRPC } from '@trpc/server' import { createHTTPServer } from '@trpc/server/adapters/standalone' import { z } from 'zod' import { createTRPCProxyClient, httpBatchLink } from '@trpc/client' const t = initTRPC.create() const appRouter = t.router({ mirror: t.procedure .input( z.object({ message: z.string() }) ) .output( z.object({ message: z.string() }) ) .mutation(({ input }) => input) }) const server = createHTTPServer({ router: appRouter }) server.listen(3000) const client = createTRPCProxyClient({ links: [ httpBatchLink({ url: 'http://localhost:3000' }) ] }) const { message } = await client.mirror.mutate({ message: 'Hello World' }) message ``` > tRPC uses `createTRPCProxyClient` to create a client with end-to-end type safety Elysia ```ts import { Elysia, t } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia() .post('/mirror', { body: t.Object({ message: t.String() }) }, ({ body }) => body) const api = treaty(app) const { data, error } = await api.mirror.post({ message: 'Hello World' }) if (error) // error.status: 422 // error.value: { type: 'validation', title: 'Validation Error', status: 422, on: 'body', detail, errors: [...] } throw error // { message: 'Hello World' } console.log(data) ``` > Elysia uses `treaty` to run the request, and offers end-to-end type safety While both offer end-to-end type safety, tRPC only handles the **happy path** where the request is successful, and doesn’t have type soundness in error handling, making it unsound. If type soundness is important for you, then Elysia is the right choice. *** While tRPC is a great framework for building type-safe APIs, it has its limitations in terms of RESTful compliance, and type soundness. Elysia is designed to be ergonomic and developer-friendly with a focus on developer experience, and **type soundness**, complying with RESTful, OpenAPI, and WinterCG standards, making it a better fit for building a universal API. Alternatively, if you are coming from a different framework, you can check out: ### [From Express](/migrate/from-express) [Comparison between Express and Elysia](/migrate/from-express) ### [From Fastify](/migrate/from-fastify) [Comparison between Fastify and Elysia](/migrate/from-fastify) ### [From Hono](/migrate/from-hono) [Comparison between Hono and Elysia](/migrate/from-hono) --- Source: [migrate/index.md](migrate/index.md) # Comparison with other frameworks Elysia is designed to feel familiar if you’ve used another web framework. These guides compare routing, validation, middleware, and type safety through examples. ## Before you start Read [Key concept](/key-concept/) for the core ideas behind Elysia. It takes about five minutes. ## Choose your framework ### [From Express](/migrate/from-express/) [Compare Express request handlers and middleware with Elysia routes and lifecycle hooks.](/migrate/from-express/) ### [From Fastify](/migrate/from-fastify/) [Compare validation, lifecycle hooks, plugin encapsulation, and type inference.](/migrate/from-fastify/) ### [From Hono](/migrate/from-hono/) [Compare routing, middleware context, validation, and typed clients.](/migrate/from-hono/) ### [From tRPC](/migrate/from-trpc/) [Compare RPC procedures with HTTP routes and Eden’s end-to-end type safety.](/migrate/from-trpc/) --- Source: [patterns/aot/index.md](patterns/aot/index.md) # Ahead-of-time compilation Elysia generates an optimized function for each route to make your app faster. The compilation happens only once, when the first request is received for each route. This is called Just-in-time (JIT) compilation. But instead, you can use Ahead-of-time (AOT) compilation to remove the said overhead while making the app fast by moving the compilation to the build step. ## Export your app To use the AOT plugin, export your Elysia instance. ### Export the instance In `src/index.ts`, export your Elysia instance as `app` so the AOT plugin can find it. ```typescript import { Elysia, t } from 'elysia' export const app = new Elysia() .get('/hello/:name', { params: t.Object({ name: t.String({ minLength: 2 }) }) }, ({ params }) => `Hello, ${params.name}!`) .listen(3000) ``` ### Create a build script Create `build.ts` with the same entry path in `entrypoints` and `aot()`. The script will dry run your app to capture the route registration and generate a bundle in `dist/`. ```typescript import { aot } from 'elysia/plugin/aot/bun' const result = await Bun.build({ entrypoints: ['src/index.ts'], outdir: 'dist', target: 'bun', plugins: [aot('src/index.ts', { target: 'bun' })] }) process.exit(0) ``` ### Build and run Run the build from your project root, then start the generated bundle: Terminal window ```sh bun run build.ts bun dist/index.js ``` ### Multiple apps The AOT plugin compiles your main app: the `app` export, else the `default` export, else the only Elysia instance the entry exports. Every other exported Elysia instance with a `name` and at least one route gets its own manifest of compiled routes, keyed by that name: src/index.ts ```ts import { Elysia, t } from 'elysia' export const api = new Elysia({ name: 'api', prefix: '/api' }) .post('/state', { body: t.Object({ theme: t.String() }) }, ({ body }) => body) export const app = new Elysia({ name: 'app' }).use(api) export default app ``` Both `app.handle()` and `api.handle()` serve from their own manifests. Any other Elysia instance in the bundle that builds its own router must be named and exported from the entry. This is any instance you call `.handle()`, `.listen()`, or `.compile()` on, read `.fetch` from, or mount with `.mount(sub.fetch)` or `.mount(sub.handle)`. Otherwise, the build still succeeds, but the bundle throws at runtime: ```text [elysia-aot] App "" has no AOT manifest. Name and export it, .mount() ones too ``` Plugins composed only with `.use()` need neither. Neither does `.mount()` with a plain function, such as [Better Auth](/integrations/better-auth/)’s `auth.handler`. When the build compiles more than one app, the main app must be named too, and each name must be unique. Keep app names static, the same at build and at runtime. Don’t derive a name from an environment variable. ## Benefits of AOT Moving compilation to the build step gives your app: - **Faster startup and first requests.** Route handlers and schemas are compiled before the server runs. - **Lower peak memory usage.** The server avoids the temporary allocations needed for runtime compilation. - **Less runtime code.** AOT analyzes unused Elysia modules and can remove TypeBox entirely when all TypeBox schemas can be compiled. - **Faster default merging.** AOT precomputes how schema defaults are merged, reducing work when filling in missing values. Generated route and schema code can increase bundle size and retained memory at startup. See the [Elysia 2.0 AOT overview](/blog/elysia-20#ahead-of-time-compilation) for details. ## Detect AOT build You can programmatically detect if the app is running in an AOT build. Use this to conditionally initialize external services, such as database connections or third-party APIs, that are not needed during the build. ```ts import { inAotBuild } from 'elysia' if (!inAotBuild()) { // Initialize external services here. } ``` ## Bundler and Runtime AOT is a bundler plugin that generates code for your Elysia app. It is compatible with Bun, Vite, esbuild, Rspack, and unplugin. build.ts ```ts import { aot } from 'elysia/plugin/aot/bun' await Bun.build({ entrypoints: ['src/index.ts'], outdir: 'dist', target: 'bun', plugins: [aot('src/index.ts', { target: 'bun' })] }) process.exit(0) ``` vite.config.ts ```ts import { defineConfig } from 'vite' import { aot } from 'elysia/plugin/aot/vite' export default defineConfig({ build: { ssr: 'src/index.ts' }, ssr: { noExternal: ['elysia'] }, plugins: [aot('src/index.ts', { target: 'bun' })] }) ``` build.ts ```ts import * as esbuild from 'esbuild' import { aot } from 'elysia/plugin/aot/esbuild' await esbuild.build({ entryPoints: ['src/index.ts'], bundle: true, platform: 'node', format: 'esm', outfile: 'dist/index.js', plugins: [aot('src/index.ts', { target: 'bun' })] }) ``` rspack.config.ts ```ts import { aot } from 'elysia/plugin/aot/rspack' export default { entry: './src/index.ts', target: 'node', plugins: [aot('src/index.ts', { target: 'bun' })] } ``` rollup.config.ts ```ts import { createUnplugin } from 'unplugin' import { aotFactory } from 'elysia/plugin/aot/unplugin' const aot = createUnplugin(aotFactory) export default { plugins: [aot.rollup({ entry: 'src/index.ts', target: 'bun' })] } ``` #### Target For specific runtime optimization, set the AOT plugin’s `target` to the runtime that executes the bundle: ```ts aot('src/index.ts', { target: 'bun' }) aot('src/index.ts', { target: 'node' }) aot('src/index.ts', { target: 'workerd' }) ``` For Cloudflare Workers, use `target: 'workerd'` and follow the [Cloudflare Workers integration](/integrations/cloudflare-worker/) for the deployment setup. --- Source: [patterns/configuration/index.md](patterns/configuration/index.md) # Configuration Pass an object to the constructor to configure an Elysia instance. Use `serve` for HTTP server defaults, or pass server options directly to `.listen()`. ```ts import { Elysia } from 'elysia' new Elysia({ prefix: '/v1', normalize: true }) ``` ## abortSignal Stop processing a request when the client aborts it. ```ts import { Elysia } from 'elysia' new Elysia({ abortSignal: true }) ``` Default: `true`. - `true` - Check the abort state after each lifecycle stage and skip the remaining hooks and handler - `false` - Run every hook and the handler even after the client disconnects ## adapter Runtime adapter for using Elysia in different environments. On Bun, `.listen()` selects the Bun adapter automatically. Supply an adapter for other runtimes. ```ts import { Elysia } from 'elysia' import { BunAdapter } from 'elysia/adapter/bun' new Elysia({ adapter: BunAdapter }) ``` ## allowUnsafeValidationDetails Choose whether production validation responses include schema details. ```ts import { Elysia } from 'elysia' new Elysia({ allowUnsafeValidationDetails: true }) ``` By default, production responses omit detailed issues, expected values, and submitted data. Enable this only when those schema details are intended to be public; see [error handling](/patterns/error-handling/#validation-detail-in-production). Default: `false`. - `true` - Include unsafe validation details in the error response in production - `false` - Exclude unsafe validation details in the error response in production ## as Set the default [scope](/essential/plugin/#scope) for lifecycle hooks registered on the instance without an explicit scope. ```ts import { Elysia } from 'elysia' new Elysia({ as: 'plugin' }) ``` Default: `local`. ## cookie Set default options for every cookie of the instance. See [cookie config](/patterns/cookie/#config). ## detail Set OpenAPI metadata shared by all routes in the instance. ```ts import { Elysia } from 'elysia' new Elysia({ detail: { hide: true, tags: ['elysia'] } }) ``` ## name Define the name of an instance which is used for debugging and [plugin deduplication](/essential/plugin/#deduplication) ```ts import { Elysia } from 'elysia' new Elysia({ name: 'service.thing' }) ``` ## nativeStaticResponse Use the runtime’s optimized handling for inline response values. Defaults to `true`. ```ts import { Elysia } from 'elysia' new Elysia({ nativeStaticResponse: true }) .get('/version', '1') .listen(3000) ``` On Bun, eligible static values are installed in `Bun.serve.routes`. For the route above, the native response has the same body as this standalone server: ```ts Bun.serve({ port: 3000, routes: { '/version': new Response('1') }, fetch: () => new Response('Not found', { status: 404 }) }) ``` Routes with hooks that need request-time execution may use Elysia’s normal request pipeline instead. ## normalize Choose how unknown properties are handled when a TypeBox schema disallows additional properties. This option does not apply to Standard Schema validators. ```ts import { Elysia } from 'elysia' new Elysia({ normalize: true }) ``` Default: `true` (equivalent to `exactMirror`). - `true` or `exactMirror`: Remove unknown properties using [exact mirror](/blog/elysia-13/#exact-mirror) - `typebox`: Elysia will remove unknown properties using [TypeBox’s Value.Clean](https://github.com/sinclairzx81/typebox) - `false`: Elysia will raise an error if a request or response contains fields that are not explicitly allowed in the schema of the respective handler. ## precompile Choose whether to [precompile all routes](/blog/elysia-10/#improved-startup-time) ahead of time before starting the server. ```ts import { Elysia } from 'elysia' new Elysia({ precompile: true }) ``` Default: `false`. - `true`: Run JIT on all routes before starting the server - `false`: Dynamically compile routes on demand Leave this as `false` unless you want route compilation to happen during warm-up. Set it on the root instance that starts the server. ## prefix Define a prefix for all routes of an instance ```ts import { Elysia } from 'elysia' new Elysia({ prefix: '/v1' }) ``` When a prefix is defined, all routes will be prefixed with the given value. ### Example ```ts import { Elysia } from 'elysia' new Elysia({ prefix: '/v1' }).get('/name', 'elysia') // Path is /v1/name ``` ## sanitize Transform schema-defined strings during validation with a function or an array of functions. Set this on the root instance and use exact-mirror normalization (`true` or `exactMirror`). ```ts import { Elysia } from 'elysia' new Elysia({ sanitize: (value) => Bun.escapeHTML(value) }) ``` ## seed Define a value that will be used to generate a checksum of an instance, used for [plugin deduplication](/essential/plugin/#deduplication) ```ts import { Elysia } from 'elysia' new Elysia({ seed: { value: 'service.thing' } }) ``` The value could be any type not limited to string, number, or object. ## strictPath Whether Elysia should handle paths strictly. According to [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.3), a path should be strictly equal to the path defined in the route. ```ts import { Elysia } from 'elysia' new Elysia({ strictPath: true }) ``` Default: `false`. - `true` - Follows [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.3) for path matching strictly - `false` - Tolerate suffix ’/’ or vice-versa. ### Example ```ts import { Elysia } from 'elysia' // Path can be either /name or /name/ new Elysia({ strictPath: false }).get('/name', 'elysia') // Path can be only /name new Elysia({ strictPath: true }).get('/name', 'elysia') ``` ## serve Configure the underlying HTTP server using Bun’s server options. The constructor’s `serve` object supplies defaults. Options passed to `.listen()` override matching fields. ```ts import { Elysia } from 'elysia' new Elysia({ serve: { idleTimeout: 60 } }) .get('/', 'Hello Elysia') .listen(3000) ``` ```ts import { Elysia } from 'elysia' new Elysia().listen({ hostname: 'elysiajs.com', tls: { cert: Bun.file('cert.pem'), key: Bun.file('key.pem') } }) ``` This configuration extends [Bun Serve API](https://bun.sh/docs/api/http) and [Bun TLS](https://bun.sh/docs/api/http#tls) ### Maximum body size Set `maxRequestBodySize` to limit the HTTP request body in bytes. ```ts import { Elysia } from 'elysia' new Elysia().listen({ maxRequestBodySize: 1024 * 1024 * 256 // 256MB }) ``` By default the maximum request body size is 128MB (1024 \* 1024 \* 128). WebSocket message limits are separate. Pass `maxPayloadLength` to the WebSocket plugin; see [WebSocket defaults](#websocket). ### HTTPS and TLS Enable TLS with a certificate and private key. Both `cert` and `key` are required. ```ts import { Elysia } from 'elysia' new Elysia().listen({ tls: { cert: Bun.file('cert.pem'), key: Bun.file('key.pem') } }) ``` ### Idle timeout Set `idleTimeout` in seconds to close idle connections after the chosen interval. ```ts import { Elysia } from 'elysia' new Elysia().listen({ // Increase idle timeout to 60 seconds idleTimeout: 60 }) ``` The default idle timeout comes from your Bun version. Set it explicitly when your application needs a particular limit. ### Server option reference The following names use the `serve.` prefix for reference; pass the fields directly to `.listen()`. ### serve.hostname Default: `0.0.0.0`. Set the hostname which the server listens on ### serve.id Uniquely identify a server instance with an ID This string will be used to hot reload the server without interrupting pending requests or websockets. If not provided, a value will be generated. To disable hot reloading, set this value to `null`. ### serve.idleTimeout The maximum time a connection can remain idle, in seconds. This is an inactivity timeout, not a total request deadline. The current adapter uses Bun’s default unless you set it. ### serve.maxRequestBodySize Default: `1024 * 1024 * 128` bytes (128 MiB). Set the maximum size of a request body (in bytes) ### serve.port Default: Bun uses its configured environment port, falling back to `3000`. Port to listen on ### serve.rejectUnauthorized `rejectUnauthorized` is a TLS option in the current Bun API. Place it inside `tls`, rather than at the top level. With client certificate verification enabled, setting it to `false` allows an untrusted client certificate. ### serve.reusePort Whether to set the `SO_REUSEPORT` flag This allows multiple processes to bind to the same port, which is useful for load balancing The current Elysia adapter does not force this on; Bun controls the default and platform support. ### serve.unix If set, the HTTP server will listen on a unix socket instead of a port. (Cannot be used with hostname+port) ### serve.tls Enable TLS with a certificate and private key. Both `cert` and `key` are required. ```ts import { Elysia } from 'elysia' new Elysia().listen({ tls: { cert: Bun.file('cert.pem'), key: Bun.file('key.pem') } }) ``` Elysia extends Bun configuration which supports TLS out of the box, powered by BoringSSL. ### serve.tls.ca Optionally override the trusted CA certificates. Default is to trust the well-known CAs curated by Mozilla. Mozilla’s CAs are completely replaced when CAs are explicitly specified using this option. ### serve.tls.cert Cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided private key, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, see ca). When providing multiple cert chains, they do not have to be in the same order as their private keys in key. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail. ### serve.tls.dhParamsFile File path to a .pem file containing custom Diffie-Hellman parameters ### serve.tls.key Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. Multiple keys can be provided as an array of strings, buffers, or objects of the form `{ pem, passphrase }`, where supported by Bun. The object form can only occur in an array. **object.passphrase** is optional. Encrypted keys will be decrypted with **object.passphrase** if provided, or **options.passphrase** if it is not. ### serve.tls.lowMemoryMode Default: `false`. This sets `OPENSSL_RELEASE_BUFFERS` to 1. It reduces overall performance but saves some memory. ### serve.tls.passphrase Shared passphrase for a single private key and/or a PFX. ### serve.tls.requestCert Default: `false`. If set to `true`, the server will request a client certificate. ### serve.tls.secureOptions Optionally affect the OpenSSL protocol behavior, which is not usually necessary. This should be used carefully if at all! Value is a numeric bitmask of the SSL\_OP\_\* options from OpenSSL Options ### serve.tls.serverName Explicitly set a server name ## tags Define tags for the OpenAPI schema for all routes of an instance similar to [detail](#detail) ```ts import { Elysia } from 'elysia' new Elysia({ tags: ['elysia'] }) ``` ## systemRouter The `systemRouter` constructor option has been removed in v2. The Bun adapter installs eligible static responses through `Bun.serve.routes` automatically and uses Elysia’s router for other requests. Use [`nativeStaticResponse`](#nativestaticresponse) to control static response optimization. ## websocket Elysia 2 has no `websocket` constructor option. Set server-wide WebSocket defaults on the plugin instead, and leave them unset unless you need to change compression, idle timeout, or message limits. Lifecycle handlers belong on individual `.ws()` routes. The plugin options extend [Bun’s WebSocket API](https://bun.sh/docs/api/websockets). ### Example ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket({ // enable compression and decompression perMessageDeflate: true, maxPayloadLength: 64 * 1024 })) .ws('/echo', (ws) => ws.body) .listen(3000) ``` See [WebSocket configuration](/patterns/websocket/#configuration) for every option. *** See [reference](/reference/classes/elysia/) for a complete list of options. --- Source: [patterns/cookie/index.md](patterns/cookie/index.md) # Cookie Read and update cookies through the `cookie` object in your handler. Extract a cookie by name, then use `.value` to read or replace its value. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { name } }) => { // Get name.value // Set name.value = "New Value" }) ``` Elysia encodes and decodes object values for you, so you can work with a cookie as an object. Cookie values can be missing A cookie property returns a `Cookie` object even when the request has no cookie with that name. Its `.value` can be `undefined`. Use a [cookie schema](#cookie-schema) to describe the value and keep TypeScript checking enabled. ## Reactivity Changing a cookie’s value or attributes schedules a `Set-Cookie` header on the response. You don’t need to assemble the header yourself. The cookie jar is a proxy. Reading a name returns a `Cookie` object; reading `.value` gives you its value. Iterating the jar visits existing cookie names. ## Cookie Interaction Cookies can be interacted with in two ways: 1. Set a property directly. 2. Use `set` or `update` to update several properties. See [cookie attribute config](#config) for more information. ### Assign property Read or assign cookie attributes like normal object properties. Elysia uses the updated attributes when it writes the cookie. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { name } }) => { // get name.domain // set name.domain = 'millennium.sh' name.httpOnly = true }) ``` ### set `set` builds a new cookie property object from its initial properties and current value, then applies the properties you provide. Use `update` when you want to merge into the current properties. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { name } }) => { name.set({ domain: 'millennium.sh', httpOnly: true }) }) ``` ### update Use `update` to merge attributes into the current cookie without resetting the other properties. Older documentation called this operation `add`; the Elysia 2 method is `update`. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { name } }) => { name.update({ httpOnly: true }) }) ``` ### remove Call `.remove()` to expire a cookie. Use the same path and domain with which it was set. If the cookie has no value, `remove()` does nothing. The JavaScript expression `delete cookie.name` does not expire a browser cookie in Elysia 2. Use `.remove()` so the response carries the expiration attributes. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { name } }) => { name.remove() }) ``` ## Cookie schema Use `cookie` to validate incoming cookie values and infer their types in the handler. Both `t.Cookie()` and `t.Object()` are supported, and additional cookie names are allowed by default. Read or update a validated cookie through its `.value` property. This example requires an existing `name` cookie with the specified object shape. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', { cookie: t.Cookie({ name: t.Object({ id: t.Numeric(), name: t.String() }) }) }, ({ cookie: { name } }) => { // Set name.value = { id: 617, name: 'Summoning 101' } }) ``` ## Optional cookie Wrap a cookie field in `t.Optional` to allow the cookie to be absent. This allows `undefined`; use `t.Nullable` when you want to accept `null`. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', { cookie: t.Cookie({ name: t.Optional( t.Object({ id: t.Numeric(), name: t.String() }) ) }) }, ({ cookie: { name } }) => { // Set name.value = { id: 617, name: 'Summoning 101' } }) ``` ## Cookie specs A cookie is a small value the browser stores and sends with matching requests. Use it for state that needs to travel with the client, such as a session identifier. Browser `fetch` manages the `Cookie` header itself. Use `credentials` when the request needs the browser’s cookies: ```ts fetch('http://localhost:3000/cookie', { credentials: 'include' }) ``` The browser’s cookie and cross-origin rules still apply. ## t.Cookie `t.Cookie()` describes an object of cookie values and accepts [cookie-specific options](#config) as its second argument: ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/cookie', { cookie: t.Cookie({ name: t.String() }, { secure: true, httpOnly: true }) }, ({ cookie }) => cookie.name.value) ``` `secure` and `httpOnly` configure cookies sent by the server; they aren’t properties you can prove about an incoming cookie string. ## Cookie signature A cookie signature lets Elysia verify that a cookie value was signed with your secret and has not been changed. Elysia signs outgoing values and verifies incoming signatures automatically. Signing does not encrypt the value. The client can still read its contents. ## Using cookie signatures Provide `secrets` and use `sign` to select the cookie names to sign and verify. The profile cookie below is optional so a first request can create it. Elysia signs the updated profile and checks its signature on later requests. The example secret is illustrative; keep your real signing secret private. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', { cookie: t.Cookie({ profile: t.Optional(t.Object({ id: t.Numeric(), name: t.String() })) }, { secrets: 'Fischl von Luftschloss Narfidort', sign: ['profile'] }) }, ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } }) ``` ## Constructor Set `cookie.secrets` and `cookie.sign` in the Elysia constructor to share signing configuration across routes. ```typescript import { Elysia, t } from 'elysia' new Elysia({ cookie: { secrets: 'Fischl von Luftschloss Narfidort', sign: ['profile'] } }) .get('/', { cookie: t.Cookie({ profile: t.Optional(t.Object({ id: t.Numeric(), name: t.String() })) }) }, ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } }) ``` ## Cookie rotation Elysia handles cookie secret rotation automatically. Put the new secret first and keep previous secrets after it. Elysia verifies incoming signatures against every entry and signs new values with the first one, which must be a non-empty string. ```typescript import { Elysia } from 'elysia' new Elysia({ cookie: { secrets: ['Vengeance will be mine', 'Fischl von Luftschloss Narfidort'] } }) ``` ### Unsigned cookie transition Elysia supports graceful transition from unsigned to signed cookies. By setting `null` in an array of `cookie.secrets`, Elysia will allow unsigned cookies to pass through while checking invalid cookie signatures when available. Elysia signs new cookie values with the first entry, so keep `null` after the signing secret. Remove `null` after the transition so unsigned cookies are rejected. ```typescript import { Elysia } from 'elysia' new Elysia({ cookie: { secrets: ['Vengeance will be mine', 'Fischl von Luftschloss Narfidort', null] } }) ``` ## Config Use these options in the constructor or in `t.Cookie`. All of them except `secrets` and `sign` can also be passed when updating cookie attributes; signing is configured on the app or the route, not on a single write. ### secrets The key used to sign and verify cookies. The option is named `secrets`, not `secret`. Accepts a string or an array of strings. An array enables [key rotation](#cookie-rotation). In the constructor, `secrets` may also be, or include, `null` to permit unsigned cookies during a transition; `t.Cookie` does not accept `null`. ### domain Specifies the value for the [Domain Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. ### encode Elysia 2 does not expose a custom `encode` cookie option. Cookie values are encoded during serialization; assign the value through `.value`. ### expires Specifies the Date object to be the value for the [Expires Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on conditions like exiting a web browser application. Tip The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but not all clients may obey this, so if both are set, they should point to the same date and time. ### httpOnly Default: `false`. Specifies the boolean value for the [HttpOnly Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy, the HttpOnly attribute is set. Otherwise, it is not. By default, the HttpOnly attribute is not set. Tip When `httpOnly` is `true`, client-side JavaScript cannot read the cookie through `document.cookie`. ### maxAge Default: `undefined`. Specifies the number (in seconds) to be the value for the [Max-Age Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.2). The given number will be converted to an integer by rounding down. By default, no maximum age is set. Tip The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but not all clients may obey this, so if both are set, they should point to the same date and time. ### path Specifies the value for the [Path Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4). When `path` is omitted, Elysia sets `Path=/`, so the cookie is available across the site. ### priority Specifies the string to be the value for the [Priority Set-Cookie attribute](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). `low` will set the Priority attribute to Low. `medium` will set the Priority attribute to Medium, the default priority when not set. `high` will set the Priority attribute to High. More information about the different priority levels can be found in [the specification](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). Tip Client support for this attribute varies. Check the behavior in the browsers your app supports. ### sameSite Specifies the boolean or string to be the value for the [SameSite Set-Cookie attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). `true` will set the SameSite attribute to Strict for strict same-site enforcement. `false` will not set the SameSite attribute. `'lax'` will set the SameSite attribute to Lax for lax same-site enforcement. `'none'` will set the SameSite attribute to None for an explicit cross-site cookie. `'strict'` will set the SameSite attribute to Strict for strict same-site enforcement. More information about the different enforcement levels can be found in [the specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). Tip Client support for this attribute varies. Check the behavior in the browsers your app supports. ### secure Specifies the boolean value for the [Secure Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy, the Secure attribute is set. Otherwise, it is not. By default, the Secure attribute is not set. Tip Be careful when setting this to true, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. --- Source: [patterns/deploy/index.md](patterns/deploy/index.md) # Deploy to production Build your server into an executable or JavaScript bundle, then run it directly or in a container. ## Cluster mode A single Elysia process runs its JavaScript on one main thread. Run several processes when you want to spread requests across CPU cores. Let’s create an **index.ts** file that imports our main server from **server.ts** and forks multiple workers based on the number of CPU cores available. ```ts import cluster from 'node:cluster' import os from 'node:os' import process from 'node:process' import { server } from './server' if (cluster.isPrimary) { for (let i = 0; i < os.availableParallelism(); i++) cluster.fork() } else { server.listen({ port: 3000, reusePort: true }) console.log(`Worker ${process.pid} started`) } ``` server.ts ```ts import { Elysia } from 'elysia' export const server = new Elysia() .get('/', () => 'Hello World!') ``` This will ensure that Elysia is running on multiple CPU cores. Tip This Bun example explicitly enables `reusePort` so workers can share port `3000` on Linux. On other platforms, use a process manager or load balancer with separate ports. ## Compile to binary We recommend running the build command before deploying to production as it could potentially reduce memory usage and file size significantly. We recommend compiling Elysia into a single binary using the command as follows: Terminal window ```bash bun build \ --compile \ --minify-whitespace \ --minify-syntax \ --target bun \ --outfile server \ src/index.ts ``` This generates an executable named `server` for the selected operating system and architecture. Compilation can reduce startup work and deployment size. Measure memory with your own production workload; the improvement is not a fixed multiplier. This command is a bit long, so let’s break it down: 1. **—compile** Compile TypeScript to binary 2. **—minify-whitespace** Remove unnecessary whitespace 3. **—minify-syntax** Minify JavaScript syntax to reduce file size 4. **—target bun** Optimize the binary for the Bun runtime 5. **—outfile server** Output the binary as `server` 6. **src/index.ts** The entry file of our server (codebase) To start our server, simply run the binary. Terminal window ```bash ./server ``` Once the binary is compiled, you don’t need `Bun` installed on the machine to run the server. This is great as the deployment server doesn’t need to install an extra runtime to run it, making the binary portable. ### Target Use a platform-specific `--target` value to build the binary for another platform. Terminal window ```bash bun build \ --compile \ --minify-whitespace \ --minify-syntax \ --target bun-linux-x64 \ --outfile server \ src/index.ts ``` Choose a target matching the deployment OS, architecture, and Linux C library: | Target | Operating system | Architecture | C library | | ---------------------- | ---------------- | ------------ | --------- | | `bun-linux-x64` | Linux | x64 | glibc | | `bun-linux-arm64` | Linux | arm64 | glibc | | `bun-windows-x64` | Windows | x64 | — | | `bun-windows-arm64` | Windows | arm64 | — | | `bun-darwin-x64` | macOS | x64 | — | | `bun-darwin-arm64` | macOS | arm64 | — | | `bun-linux-x64-musl` | Linux | x64 | musl | | `bun-linux-arm64-musl` | Linux | arm64 | musl | See [Bun’s current target list](https://bun.com/docs/bundler/executables#supported-targets) when choosing a build for another machine. ### Why not —minify Bun has a `--minify` flag that will minify the binary. However, if we are using [OpenTelemetry](/plugins/opentelemetry/), it will reduce a function name to a single character. This makes tracing harder than it should be, as OpenTelemetry relies on function names. However, if you’re not using OpenTelemetry, you may opt in for `--minify` instead Terminal window ```bash bun build \ --compile \ --minify \ --outfile server \ src/index.ts ``` ### Permission Some Linux distributions might not be able to run the binary. We suggest enabling execute permissions on the binary if you’re on Linux: Terminal window ```bash chmod +x ./server ./server ``` ### Unsupported CPU If an executable fails with an illegal-instruction error, check both its architecture and the Bun version used to build it. Current Bun x64 builds require SSE4.2 and select newer CPU instructions at runtime. Older releases distinguished baseline and modern AVX2 targets; current releases accept those suffixes for compatibility but resolve them to the same binary. Match the target to the deployment machine and check [Bun’s CPU requirements](https://bun.com/docs/bundler/executables#supported-targets) before rebuilding. An unfamiliar error message by itself doesn’t identify a missing CPU feature. ## Compile to JavaScript Bundle to JavaScript when you want to keep Bun as a separately installed runtime. This works on supported Windows, Linux, and macOS hosts. Terminal window ```bash bun build \ --target bun \ --minify-whitespace \ --minify-syntax \ --outfile ./dist/index.js \ src/index.ts ``` This will generate a single portable JavaScript file that you can deploy on your server. Terminal window ```bash NODE_ENV=production bun ./dist/index.js ``` ## Docker A compiled executable lets you use a small runtime image. This example builds it in a Bun image, then copies it into Distroless: `Dockerfile` ```dockerfile FROM oven/bun AS build WORKDIR /app # Cache packages installation COPY package.json package.json COPY bun.lock bun.lock RUN bun install --frozen-lockfile COPY ./src ./src ENV NODE_ENV=production RUN bun build \ --compile \ --minify-whitespace \ --minify-syntax \ --outfile server \ src/index.ts FROM gcr.io/distroless/base WORKDIR /app COPY --from=build /app/server server ENV NODE_ENV=production CMD ["./server"] EXPOSE 3000 ``` ### OpenTelemetry If you are using [OpenTelemetry](/patterns/opentelemetry) to deploy a production server, OpenTelemetry relies on monkey-patching `node_modules/`. To make instrumentation work properly, we need to specify libraries to be instrumented as external modules to exclude them from being bundled. For example, if you are using `@opentelemetry/instrumentation-pg` to instrument the `pg` library, we need to exclude `pg` from being bundled and make sure that it is importing `node_modules/pg`. To make this work, we may specify `pg` as an external module with `--external pg` Terminal window ```bash bun build --compile --external pg --outfile server src/index.ts ``` This tells Bun not to bundle `pg` into the final output file. The library will be imported from the `node_modules` directory at runtime. So on a production server, you must also keep the `node_modules` directory. It’s recommended to specify packages that should be available in a production server as `dependencies` in `package.json` and use `bun install --production` to install only production dependencies. Keep every package left external in `dependencies`: ```json { "dependencies": { "pg": "^8.23.1" }, "devDependencies": { "@elysia/opentelemetry": "2.0.0-beta.2", "@opentelemetry/instrumentation-pg": "^0.74.0", "@types/pg": "^8.23.1", "elysia": "2.0.0-beta.32" } } ``` Then, after running a build command, run this on a production server: Terminal window ```bash bun install --production ``` If the node\_modules directory still includes development dependencies, you may remove the node\_modules directory and reinstall production dependencies again. ### Monorepo If you are using Elysia with a monorepo, you may need to include dependent `packages`. If you are using Turborepo, you may place a Dockerfile inside your apps directory like **apps/server/Dockerfile**. This also applies to other monorepo managers such as Lerna, etc. Assuming that our monorepo uses Turborepo with a structure as follows: - apps - server - **Dockerfile (place a Dockerfile here)** - packages - config Then we can build our Dockerfile at the monorepo root (not app root): Terminal window ```bash docker build -f apps/server/Dockerfile -t elysia-mono . ``` With a Dockerfile as follows: `apps/server/Dockerfile` ```dockerfile FROM oven/bun:1 AS build WORKDIR /app # Cache packages COPY package.json package.json COPY bun.lock bun.lock COPY apps/server/package.json ./apps/server/package.json COPY packages/config/package.json ./packages/config/package.json RUN bun install --frozen-lockfile COPY apps/server ./apps/server COPY packages/config ./packages/config ENV NODE_ENV=production RUN bun build \ --compile \ --minify-whitespace \ --minify-syntax \ --outfile server \ apps/server/src/index.ts FROM gcr.io/distroless/base WORKDIR /app COPY --from=build /app/server server ENV NODE_ENV=production CMD ["./server"] EXPOSE 3000 ``` ## Railway [Railway](https://railway.app) is one of the popular deployment platforms. Read Railway’s `PORT` environment variable instead of assuming a fixed production port. We need to modify our Elysia server to accept the `PORT` environment variable to comply with Railway’s port configuration. Instead of a fixed port, we may use `process.env.PORT` and provide a fallback in development instead. ```ts import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .listen({ port: process.env.PORT ?? 3000, hostname: '0.0.0.0' }) ``` This should allow Elysia to intercept the port provided by Railway. Tip Listen on `0.0.0.0` so Railway can reach the server through the assigned port. --- Source: [patterns/error-handling/index.md](patterns/error-handling/index.md) # Error handling Elysia provides statically analyzable error handling. This allows you to predict most possible errors from your server similar to [Effect](https://effect.website) at home. This is especially useful for [Eden](/eden) and [OpenAPI Type Gen](/patterns/openapi/#openapi-from-types) to generate types for your API. This page covers the following topics: - [Prefer return over throw](#first-prefer-return-over-throw) - [Error hook](#error-hook) - [Custom error](#custom-error) - [Schema validation](#schema-validation) - [Error list](#error-list) We recommend visiting the [error lifecycle](/essential/life-cycle/#error-error-handling) or [error handling tutorial](/tutorial/patterns/error-handling/) before reading this page. ## First: Prefer return over throw Returning an error allows TypeScript to statically analyze the return type while a thrown error doesn’t allow this. At runtime, a returned `Error` is handled like a thrown one: it goes through your error hooks and produces the same response. `status()` and `problem()` are different. Returning them sends an ordinary response without entering error handling. ### Problem document `problem()` returns an [RFC 9457](https://www.rfc-editor.org/info/rfc9457) problem document with the `application/problem+json` content type. Elysia fills `type` and `title` for you: ```ts import { Elysia, problem } from 'elysia' new Elysia() .get('/', () => problem(409, { detail: 'Username is taken' })) ``` Both `status()` and `problem()` accept a status name in place of the number, such as `problem('Conflict', { detail: 'Username is taken' })`. ## Error hook Use `.onError()` to handle every error of an instance, or the `error` property to handle errors of a single route: ```ts import { Elysia } from 'elysia' new Elysia() .get('/', { error({ error, status }) { return status(503, 'Try again later') } }, () => { throw new Error('Maintenance window') }) ``` Returning a value from the hook sends it as the response. Returning nothing passes the error to the next hook, then to Elysia’s default response. Returning an `Error` sends it as if no hook handled it, at that error’s own status. An `HTTPError` sends its problem document, and an `Error` without a status becomes a `500`. It isn’t passed to other hooks. ### Mapping Errors We can use `.onError(ErrorClass, handler)` to map an error to a response. This is useful for mapping errors from libraries that you have no control over to a response. ```ts import { Elysia, problem } from 'elysia' import { DrizzleQueryError } from 'drizzle-orm/errors' import { db } from './db' import { users } from './schema' new Elysia() .onError(DrizzleQueryError, () => problem(502, { type: 'UPSTREAM_FAILURE', detail: 'The database connection is unavailable' })) .get('/users', async () => { try { return await db.select().from(users) } catch (error) { if (error instanceof DrizzleQueryError) return error throw error } }) ``` This maps `DrizzleQueryError` to a `502` response with a problem document. A thrown `DrizzleQueryError` would reach the hook too. The `try`/`catch` only exists to return the error, so the `502` response becomes part of the route’s type. If the response never changes, pass the value directly instead of a function: ```ts new Elysia() .onError(DrizzleQueryError, problem(502, { detail: 'Database unavailable' })) ``` ### Error status code Any `Error` with a `status` property is sent with that status and its message as a plain-text body. You don’t need `HTTPError` for this: ```ts import { Elysia } from 'elysia' class Teapot extends Error { status = 418 } new Elysia() .get('/', () => { throw new Teapot('Short and stout') }) ``` This responds `418` with `Short and stout`. ### Unhandled errors An error without a `status` that no hook handles becomes a `500` problem document: ```json { "type": "internal-server-error", "code": "internal-server-error", "title": "Internal Server Error", "status": 500, "detail": "Maintenance window", "name": "Error" } ``` With `NODE_ENV=production`, Elysia omits `detail` and `name` so internal messages don’t leak to clients. ## Custom error You can use `HTTPError.id()` to create a custom error class that Elysia can translate to HTTP responses. By default, Elysia returns an RFC 9457 problem document: ```ts import { Elysia, HTTPError } from 'elysia' class CustomError extends HTTPError.id('UNIQUE_ID', 400) {} new Elysia() .get('/return', () => new CustomError('Invalid request')) ``` ### Error response You can add optional properties to customize how the error behaves: | Property | Description | | ------------- | ----------------------------------------------- | | `headers` | add to the response headers | | `detail` | set the `detail` string in the problem document | | `extension()` | add members to the problem document | | `value()` | override the entire response body | `value()` can be `async`. The status can also be a name, such as `HTTPError.id('OUT_OF_CREDIT', 'Payment Required')`. Use `detail` to set the `detail` of the RFC 9457 problem document. It takes priority over the error message, so the message can stay in your logs: ```ts import { Elysia, HTTPError } from 'elysia' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) { headers = { 'x-billing-required': 'true' } detail = 'Top up to continue' } new Elysia() .get('/', () => new OutOfCredit()) ``` Use `extension()` to add your own members to the problem document, like the out-of-credit example from RFC 9457: ```ts import { Elysia, HTTPError } from 'elysia' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) { detail = 'Your current balance is 30, but that costs 50' constructor(readonly balance: number) { super() } extension() { return { balance: this.balance, accounts: ['/account/12345'] } } } new Elysia() .get('/', () => new OutOfCredit(30)) ``` ```json { "type": "OUT_OF_CREDIT", "code": "OUT_OF_CREDIT", "detail": "Your current balance is 30, but that costs 50", "status": 402, "balance": 30, "accounts": [ "/account/12345" ], "title": "Payment Required" } ``` `type`, `code` and `status` always come from the error, so `extension()` can’t change them. It can set `title` and `instance`, and a `detail` string when the class doesn’t set `detail`. `extension()` must return a plain object synchronously. Anything else, such as a Promise, is ignored, so use `value()` for async work. Members whose value is a function are left out. Use `value()` when you need to replace the entire response body: ```ts import { Elysia, HTTPError } from 'elysia' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) { value() { return { code: 'OUT_OF_CREDIT', ok: false } } } new Elysia() .get('/', () => new OutOfCredit()) ``` For a response that depends on the request, such as its path or headers, map the error with [`.onError(Class, handler)`](#mapping-errors) instead. The handler receives the request’s context: ```ts import { Elysia, HTTPError, problem } from 'elysia' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {} new Elysia() .onError(OutOfCredit, ({ path }) => problem(402, { detail: `Top up to access ${path}` }) ) .get('/', () => new OutOfCredit()) ``` Use `problem()` or `status()` to choose a status code from the error’s values. Select a balance below to compare the responses: ```ts import { Elysia, HTTPError, status } from 'elysia' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT') { constructor(public balance: number) { super() } value() { if (this.balance > 0) return status(500, { detail: 'Something went wrong on our side', balance: this.balance }) return status(402, { type: this.type, code: this.code, detail: 'Out of credit', balance: this.balance }) } } new Elysia() .get('/balance/0', () => new OutOfCredit(0)) .get('/balance/1', () => new OutOfCredit(1)) ``` Note: - Elysia uses `value()` first, then `detail`, then a `detail` from `extension()`, then the error message. - If `value()` returns `undefined`, Elysia falls through to `detail` and `extension()`. Otherwise, `extension()` isn’t called. - If `value()` or `extension()` throws, Elysia returns `500` without the error’s headers. - For a `detail` that needs async work, return `problem()` from an `async value()`. ### HTTPError.id is syntax sugar You can extend `HTTPError`, or extend `Error` and implement `HTTPError`, and it would work: ```ts import { HTTPError } from 'elysia' class Overdrawn extends HTTPError { readonly type = 'OVERDRAWN' readonly status = 402 } class OutOfCredit extends Error implements HTTPError { readonly type = 'OUT_OF_CREDIT' readonly status = 402 readonly detail = 'Top up to continue' } ``` It’s very important that `type` and `status` should have either `readonly` or `as const` to make it statically analyzable. If `status` cannot be determined at compile time, you can use `status()` in `value()` instead but you must leave `status` undefined. `HTTPError.id()` adds two things a hand-written class doesn’t get: a `code` member in the response, and the `typeBase` prefix below. A hand-written class sends its `type` as written. ### Problem type URL You can set `HTTPError.typeBase` to a URL to prefix the `type` of classes made by `HTTPError.id()` and of the [built-in errors](#built-in-errors). Validation errors keep `type: 'validation'`. This is useful for clients that want to look up the error type in a documentation page. ```ts import { Elysia, HTTPError } from 'elysia' HTTPError.typeBase = 'https://elysiajs.com/errors' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {} new Elysia() .get('/', () => new OutOfCredit('Top up to continue')) ``` ```json { "type": "https://elysiajs.com/errors/OUT_OF_CREDIT", "code": "OUT_OF_CREDIT", "detail": "Top up to continue", "status": 402, "title": "Payment Required" } ``` `code` keeps the bare identifier while `type` is an absolute URL if `typeBase` is set. Using `code` might be more convenient for clients that don’t need to look up the error type. ### Response type A returned error is part of the route’s response type, under its status. Eden narrows it the same way as a `status()` response: ```ts import { Elysia, HTTPError } from 'elysia' import { treaty } from '@elysia/eden' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {} const app = new Elysia() .get('/credit', () => new OutOfCredit('Top up to continue')) const { error } = await treaty(app).credit.get() if (error?.status === 402) console.log(error.value.code) // 'OUT_OF_CREDIT' ``` Members returned by `extension()` are part of the type too. ## Schema Validation Add `error` to a schema field to choose the message returned when that field fails validation. ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/:id', { params: t.Object({ id: t.Number({ error: 'id must be a number' }) }) }, ({ params: { id } }) => id) ``` | Request | Response | | --------- | -------------------------------- | | `/string` | `422` with `id must be a number` | | `/1` | `200` with `1` | ### Schema messages You can attach a message to the particular schema that may fail. ```ts import { t } from 'elysia' const Email = t.String({ format: 'email', error: 'Invalid email' }) const Names = t.Array( t.String({ error: 'All members must be a string' }), { error: 'Expected an array of names' } ) const Point = t.Object({ x: t.Number() }, { error: 'Expected an object with a numeric x' }) ``` `Names` uses its item message for `[1]` and its array message for `"hello"`. Object and field messages are separate. The field message applies when the object is valid but a field fails. The object’s own message applies when the value isn’t an object at all, so `Point`’s message is returned for `"hello"`. ### Error is called per field The error callback applies when its schema fails. A nested number’s callback doesn’t handle a value that isn’t an object at all: ```ts import { t } from 'elysia' const Point = t.Object({ x: t.Number({ error() { return 'Expected x to be a number' } }) }) ``` | JSON body | Message | | ------------------ | --------------------------------- | | `{ "x": "hello" }` | `Expected x to be a number` | | `"hello"` | Default object validation message | Add a callback to the object itself to customize the second case: ```ts import { t } from 'elysia' const Point = t.Object({ x: t.Number({ error() { return 'Expected x to be a number' } }) }, { error() { return 'Expected value to be an object' } }) ``` Now the JSON string `"hello"` produces `Expected value to be an object`. ### Validation detail Wrap your message in `validationDetail()` to include validation issues alongside it. This is useful when callers need to know which field failed and why. ```ts import { Elysia, t, validationDetail } from 'elysia' new Elysia() .get('/:id', { params: t.Object({ id: t.Number({ error: validationDetail('id must be a number') }) }) }, ({ params: { id } }) => id) ``` In development, `/string` returns a validation object containing your message and the validation issues. See the [validation error tutorial](/tutorial/patterns/validation-error/) for a walkthrough. ### Validation detail in production With `NODE_ENV=production`, Elysia omits detailed schema issues and submitted values from the validation response. This is to avoid exposing sensitive data. Your message is still returned, but the response is reduced to a problem document: ```json { "type": "validation", "code": "validation", "title": "Validation Error", "status": 422, "on": "params", "property": "/id" } ``` This only applies to the default validation message. A custom message is sent as written, as plain text for a string or JSON for an object, not as a problem document. Tip You can use [allowUnsafeValidationDetails](/patterns/configuration/#allowunsafevalidationdetails) to include them in production for public APIs. This is not recommended for private APIs, since it exposes submitted values and schema issues. ### Error message as function Use a function when you want to construct the message. You can return a string or another value suitable for your error response: ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/', { body: t.Object({ x: t.Number({ error() { return 'Expected x to be a number' } }) }) }, () => 'Hello World!') .listen(3000) ``` `error()` receives an object with the following fields: | field | description | | -------- | ----------------------------------------------------- | | `type` | The error type, always `validation` for schema errors | | `on` | Where the error was found, such as `body` or `params` | | `value` | The submitted value (omitted in production) | | `errors` | An array of validation issues (omitted in production) | In production, `value` and `errors` are omitted to avoid exposing sensitive data. Use [allowUnsafeValidationDetails](/patterns/configuration/#allowunsafevalidationdetails) to include them in production. ## Error list `error.all` is an array property, not a method. Each issue has a dotted `path` such as `name` or `user.email`, a `message`, a `schemaPath`, and `params`: ```ts import { Elysia, t, ValidationError } from 'elysia' new Elysia() .post('/', { body: t.Object({ name: t.String(), age: t.Number() }), error({ error, status }) { if (!(error instanceof ValidationError)) return const name = error.all.find((issue) => issue.path === 'name') return status(422, name?.message ?? error.message) } }, ({ body }) => body) .listen(3000) ``` For the raw issues from the schema provider, read `error.errors`. TypeBox issues locate the field with an `instancePath` such as `/name`, while a Standard Schema validator may use a different shape. ### Built-in errors Elysia uses these error classes. Each sends a problem document with the `code` below as `code`, and as `type` unless [`typeBase`](#problem-type-url) is set. Import a class to handle it with `.onError()`. | Class | Status | `code` | Used when | | --------------------- | ------ | ----------------------- | ---------------------------------- | | `NotFound` | `404` | `not-found` | No route matches | | `ParseError` | `400` | `parse` | The request body can’t be parsed | | `ValidationError` | `422` | `validation` | A request fails its schema | | `InvalidCookie` | `400` | `invalid-cookie` | A signed cookie fails verification | | `InternalServerError` | `500` | `internal-server-error` | You throw it for a server failure | `ParseError` includes the parser’s message as `detail` in development only. `InvalidCookie` returns `500` instead when the cookie secret itself is missing, since that is a server misconfiguration. A response that fails its schema returns `500` instead of `422`. In production, that response is a bare internal server error without validation details. You can throw `NotFound` and `InternalServerError` yourself. A message you pass, such as `new NotFound('No such tea')`, is sent as `detail`, in production too. --- Source: [patterns/fullstack-dev-server/index.md](patterns/fullstack-dev-server/index.md) # Bun fullstack dev server Bun’s [fullstack dev server](https://bun.com/docs/bundler/fullstack) bundles frontend assets and supports hot module replacement (HMR). This guide uses Bun 1.3 or later and the Elysia v2 static plugin. This allows us to directly use React without any bundler like Vite or Webpack. Something went wrong trying to load the video. You can use [this example](https://github.com/saltyaom/elysia-fullstack-example) to quickly try it out. Otherwise, install it manually: ## Set up the server Install the Elysia v2 static plugin: Terminal window ```bash bun add elysia@rc @elysia/static@next react react-dom bun add -d @types/react @types/react-dom ``` Tip Set `bunFullstack: true` and await `staticPlugin()` before starting the server. The plugin defaults to ordinary static-file serving; awaiting it alone does not enable the HTML bundler. ```typescript import { Elysia } from 'elysia' import { staticPlugin } from '@elysia/static' new Elysia() .use(await staticPlugin({ bunFullstack: true })) .listen(3000) ``` ### Create the HTML entry point Create `public/index.html` and load `public/index.tsx` as a module. ```html Elysia React App
``` ### Create the React app Create `public/index.tsx` with a counter component. ```tsx import { useState } from 'react' import { createRoot } from 'react-dom/client' function App() { const [count, setCount] = useState(0) const increase = () => setCount((c) => c + 1) return (

{count}

) } const root = createRoot(document.getElementById('root')!) root.render() ``` ### Enable JSX Enable JSX in `tsconfig.json`. Navigate to `http://localhost:3000/public` and see the result. Bun handles frontend bundling alongside the API, so you don’t need a separate bundler process. The source example combines HMR, [Tailwind](#tailwind-css), TanStack Query, [Eden Treaty](/eden/), and path aliases. Use versions compatible with your Bun and Elysia release. ```json { "compilerOptions": { "jsx": "react-jsx" } } ``` ## Custom prefix path We can change the default `/public` prefix by passing the `prefix` option to `staticPlugin`. This would serve the static files at `/` instead of `/public`. See [static plugin](/plugins/static/) for more configuration options. ```typescript import { Elysia } from 'elysia' import { staticPlugin } from '@elysia/static' new Elysia() .use( await staticPlugin({ prefix: '/', bunFullstack: true }) ) .listen(3000) ``` ## Tailwind CSS We can also use Tailwind CSS with Bun Fullstack Dev Server. ### Configure the Tailwind plugin Install dependencies: Terminal window ```bash bun add tailwindcss@4 bun add -d bun-plugin-tailwind ``` Create `bunfig.toml` with the Tailwind plugin. ```toml [serve.static] plugins = ["bun-plugin-tailwind"] ``` ### Add Tailwind directives Create `public/global.css` with Tailwind directives. ```css @import "tailwindcss"; ``` ### Add the stylesheet to HTML Add the stylesheet link to `public/index.html`. ```html Elysia React App
``` ### Import the stylesheet in React Alternatively, import `public/global.css` in `public/index.tsx` instead of adding the stylesheet link to HTML. ```tsx import { useState } from 'react' import { createRoot } from 'react-dom/client' import './global.css' function App() { const [count, setCount] = useState(0) const increase = () => setCount((c) => c + 1) return (

{count}

) } const root = createRoot(document.getElementById('root')!) root.render() ``` ## Path alias We can also use path aliases in Bun Fullstack Dev Server. ### Configure path aliases Add `paths` in `tsconfig.json`. ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@public/*": ["public/*"] } } } ``` ### Use the alias Use the alias in your code. This will work out of the box without any additional configuration. ```tsx import { useState } from 'react' import { createRoot } from 'react-dom/client' import '@public/global.css' function App() { const [count, setCount] = useState(0) const increase = () => setCount((c) => c + 1) return (

{count}

) } const root = createRoot(document.getElementById('root')!) root.render() ``` ## Build for production You can build a fullstack server as if it were a normal Elysia server. Terminal window ```bash bun build --compile --target bun --outfile server src/index.ts ``` This creates an executable named `server`. Test the production build as well as development HMR. When running the **server** executable, make sure to include the **public** folder as in the development environment. See [Deploy to Production](/patterns/deploy) for more information. --- Source: [patterns/macro/index.md](patterns/macro/index.md) # Macro A **macro** controls lifecycle hooks, schemas, and context through a typed route option. Define it once, then enable it on the routes that need it. ## Define a macro A macro packages hooks, schemas, and context extensions behind a route option. This one accepts the word to log. ```typescript import { Elysia } from 'elysia' const plugin = new Elysia({ name: 'plugin' }) .macro({ hi: (word: string) => ({ beforeHandle() { console.log(word) } }) }) ``` ## Enable it on a route Register the plugin, then add `hi` to the route’s options. Visiting `/` logs `Elysia` and returns `hi`. The macro options come before the handler. ```typescript import { Elysia } from 'elysia' const plugin = new Elysia({ name: 'plugin' }) .macro({ hi: (word: string) => ({ beforeHandle() { console.log(word) } }) }) const app = new Elysia() .use(plugin) .get('/', { hi: 'Elysia' }, () => 'hi') ``` ## Add a context property Return a `derive` function from the macro to add a typed context property. This route returns `Hello Elysia`. ```typescript import { Elysia } from 'elysia' const plugin = new Elysia({ name: 'plugin' }) .macro({ greeting: (name: string) => ({ derive: () => ({ greeting: `Hello ${name}` }) }) }) const app = new Elysia() .use(plugin) .get('/', { greeting: 'Elysia' }, ({ greeting }) => greeting) ``` ## Property shorthand Each property in the macro object can be a function or an object. Use an object when you only need to enable or disable a feature: ```ts import { Elysia } from 'elysia' const auth = new Elysia() .macro({ isAuth: { derive: () => ({ user: 'saltyaom' }) } }) .get('/', { isAuth: true }, ({ user }) => user) ``` Setting `isAuth: false` disables it. The object form is shorthand for a function that checks a boolean: ```ts import { Elysia } from 'elysia' const auth = new Elysia() .macro({ isAuth(enabled: boolean) { if (!enabled) return return { derive: () => ({ user: 'saltyaom' }) } } }) ``` These examples supply a fixed user to show context inference; replace that value with your authentication logic in an app. ## Error handling Return `status()` from a macro hook to stop the request with an HTTP status. ```ts import { Elysia, status } from 'elysia' new Elysia() .macro({ auth: { derive({ headers }) { if (!headers.authorization) return status(401, 'Unauthorized') return { user: 'SaltyAom' } } } }) .get('/', { auth: true }, ({ user }) => `Hello ${user}`) ``` Here a missing authorization header returns `401`. The example only checks that a header exists; your authentication function must verify it before returning a user. Return `status()` for expected HTTP outcomes. Throwing an ordinary unhandled `Error` produces a `500` response by default. Returning rather than throwing a status also lets [Eden](/eden/) and [OpenAPI type generation](/patterns/openapi/#openapi-from-types) infer the route’s possible responses. ## Derive Add a property to context by returning an object with a [`derive`](/essential/life-cycle/#derive) function. In Elysia 2, this replaces the old macro `resolve` option. ```ts import { Elysia } from 'elysia' new Elysia() .macro({ withUser: { derive: () => ({ user: 'Pardofelis' }) } }) .get('/', { withUser: true }, ({ user }) => user) ``` The route receives a typed `user` property. This is useful when you need to authenticate a user, load related database data, or compute another request-specific context value. ### Macro extension with derive A macro can enable another macro and add its own derived properties. The route receives the combined result: ```ts import { Elysia } from 'elysia' new Elysia() .macro({ withUser: { derive: () => ({ user: 'lilith' as const }) } }) .macro({ withMember: { withUser: true, derive: () => ({ role: 'member' as const }) } }) .get('/', { withMember: true }, ({ user, role }) => ({ user, role })) ``` A macro’s own hooks infer its own schema. They don’t automatically infer context derived by another macro. When one computation needs another’s result, keep them together in one `derive` callback, or read the combined properties in the route handler. The old `.macro(name, definition)` workaround is removed in Elysia 2. Use the object form, as above. ## Schema A macro can define a schema for the routes that enable it. ```ts import { Elysia, t } from 'elysia' new Elysia() .macro({ withFriends: { body: t.Object({ friends: t.Tuple([t.Literal('Fouco'), t.Literal('Sartre')]) }) } }) .post('/', { body: t.Object({ name: t.Literal('Lilith') }), withFriends: true }, ({ body }) => body.friends) ``` This route validates both the `name` and `friends` properties. A valid JSON body is: ```json { "name": "Lilith", "friends": ["Fouco", "Sartre"] } ``` Macro schemas are combined with the route’s schema and other enabled macro schemas. They can also be used with [Standard Schema validators](/essential/validation/#standard-schema). ### Schema with lifecycle in the same macro Object-form macros infer their own schema inside their lifecycle hooks: ```ts import { Elysia, t } from 'elysia' new Elysia() .macro({ withFriends: { body: t.Object({ friends: t.Tuple([t.Literal('Fouco'), t.Literal('Sartre')]) }), beforeHandle({ body: { friends } }) { console.log(friends) } } }) .post('/', { withFriends: true }, ({ body }) => body) ``` Here `friends` is typed from this macro’s body schema. You don’t need a named single-macro overload. This differs from inferring properties supplied by another macro, as described [above](#macro-extension-with-derive). ## Extension A macro can enable multiple macros and add more functionality of its own. ```ts import { Elysia, t } from 'elysia' new Elysia() .macro({ sartre: { body: t.Object({ sartre: t.Literal('Sartre') }) }, fouco: { body: t.Object({ fouco: t.Literal('Fouco') }) }, lilith: { fouco: true, sartre: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', { lilith: true }, ({ body }) => body) ``` Enabling `lilith` enables `fouco` and `sartre` too. The request body must satisfy all three schemas. ## Deduplication Elysia deduplicates repeated macro hooks so an enabled dependency doesn’t run multiple times just because more than one macro uses it. For a function-form macro, its option value is the default seed. Return a custom `seed` to choose the deduplication identity: ```ts import { Elysia, t } from 'elysia' new Elysia() .macro({ sartre: (role: string) => ({ seed: role, body: t.Object({ sartre: t.Literal('Sartre') }) }) }) ``` The macro name and seed identify a function-form expansion. Keep seeds stable, and avoid circular object values unless you supply a separate primitive seed. Elysia limits nested macro expansion to 16 levels to prevent an infinite loop. Avoid circular dependencies rather than relying on that limit. When a route and a macro both provide OpenAPI `detail`, Elysia merges them and gives the route’s detail precedence. ## Type lambda Type Lambda is an optional advanced feature for function-form macros. It lets you compute a macro’s types from the value a route sets on it. This is useful when you want to narrow the type of a context property based on the route’s option value. To use one, return `$type: macroType()` from the macro: ```ts import { Elysia, macroType, type MacroTypeLambda } from 'elysia' interface Role extends MacroTypeLambda { output: Record<'role', this['input']> } new Elysia() .macro({ role: (role: 'admin' | 'member') => ({ $type: macroType(), derive: () => ({ role }) }) }) .get('/admin', { role: 'admin' }, ({ role }) => role) .get('/member', { role: 'member' }, ({ role }) => role) ``` Here `role` is typed as `'admin'` on `/admin` and `'member'` on `/member`, not `'admin' | 'member'`. - `this['input']` - a value from the macro - `output` - value to add to the route’s context. `this['input']` keeps a literal type only when the macro’s parameter type has literals, such as `'admin' | 'member'` or a template literal: `` `room:${string}` ``. Note: - Lambda only changes types. - Elysia ignores `$type` at runtime. The macro still has to provide the value via `derive`. - TypeScript doesn’t check that `derive` returns what `output` promises. - TypeScript doesn’t allow `this` inside a nested object. Use the `Record` type instead. ### Option typed from the route Use the lambda’s `option` and `this['context']` to type a macro callback from the route: ```ts import { Elysia, macroType, t, type Context, type MacroTypeLambda } from 'elysia' interface Log extends MacroTypeLambda { option: (context: this['context']) => string } new Elysia() .macro({ log: (message: (context: Context) => string) => ({ $type: macroType(), beforeHandle(context) { console.log(message(context)) } }) }) .post('/user/:id', { body: t.Object({ name: t.String() }), log: ({ params, body }) => `${params.id} is now ${body.name}` }, ({ body }) => body) ``` In `log`, `params.id` and `body.name` are strings. Without the lambda, `body` is `unknown`. `message` keeps its declared type, `(context: Context) => string`, so the macro can call it with its hook’s context. Route options use the lambda’s `option` type. Keep these types compatible. `this['context']` includes: - The route’s schema and path parameters. - Values from earlier `decorate`, `derive`, and macros on an earlier `guard`. It excludes additions from macros enabled alongside it, including this macro’s own `derive`, `output`, and schema. This also applies to `guard`, `group`, and `ws`. A `group` includes path parameters from its prefix, not its child routes. Use `this['input']` in `output` and `this['context']` in `option`. Each is `unknown` in the other. A lambda can set both. --- Source: [patterns/mount/index.md](patterns/mount/index.md) # Mount [WinterTC](https://wintertc.org/) is a standard for building HTTP servers behind Cloudflare, Deno, Vercel, and others. It allows web servers to run interoperably across runtimes by using [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), and [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). Elysia is WinterTC compliant. It is optimized to run on Bun, but also supports other runtimes if possible. This allows any WinterTC-compliant framework or code to run together, allowing frameworks like Elysia, Hono, Remix, Itty Router to run together in a simple function. ## Mount Pass a `fetch` function to `.mount`: ```ts import { Elysia } from 'elysia' import { Hono } from 'hono' const hono = new Hono() .get('/', (c) => c.text('Hello from Hono!')) const app = new Elysia() .get('/', () => 'Hello from Elysia') .mount('/hono', hono.fetch) ``` Any framework that uses `Request` and `Response` can interoperate with Elysia, like: - Hono - Nitro - H3 - [Next.js API Route](/integrations/nextjs/) - [Nuxt API Route](/integrations/nuxt/) - [SvelteKit API Route](/integrations/sveltekit/) And these can be used on multiple runtimes like: - Bun - Deno - Vercel Edge Runtime - Cloudflare Worker - Netlify Edge Function If the framework supports the **.mount** function, you can also mount Elysia inside another framework: ```ts import { Elysia } from 'elysia' import { Hono } from 'hono' const elysia = new Elysia() .get('/', () => 'Hello from Elysia inside Hono inside Elysia') const hono = new Hono() .get('/', (c) => c.text('Hello from Hono!')) .mount('/elysia', elysia.fetch) const main = new Elysia() .get('/', () => 'Hello from Elysia') .mount('/hono', hono.fetch) .listen(3000) ``` The mounted app receives a standard Request and returns a Response. When composing two Elysia instances, prefer `.use()` so Elysia can preserve their types and plugin scopes. --- Source: [patterns/openapi/index.md](patterns/openapi/index.md) # OpenAPI Generate an OpenAPI document and a browsable API reference from your Elysia routes. Schemas describe the data; route details explain what each endpoint does. To generate the documentation page, install the plugin: Terminal window ```bash bun add @elysia/openapi@next ``` And register the plugin to the server: ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) ``` Accessing `/openapi` would show you a Scalar UI with the generated endpoint documentation from the Elysia server. For OpenAPI plugin configuration, see the [OpenAPI plugin page](/plugins/openapi/). ## OpenAPI from types > This is optional, but we highly recommend it for a much better documentation experience. By default, Elysia relies on runtime schema to generate OpenAPI documentation. However, you can also generate OpenAPI documentation from types by using a generator from the OpenAPI plugin as follows: 1. Specify your Elysia root file (if not specified, Elysia will use `src/index.ts`), and export an instance 2. Import a generator and provide a **file path from project root** to the type generator ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use( openapi({ references: fromTypes() }) ) .get('/', { test: 'hello' as const }) .post('/json', { body: t.Object({ hello: t.String() }) }, ({ body, status }) => body) .listen(3000) ``` Elysia will attempt to generate OpenAPI documentation by reading the type of an exported instance to generate OpenAPI documentation. This will co-exist with the runtime schema, and the runtime schema will take precedence over the type definition. ### Production In a production environment, it’s likely that you might compile Elysia to a [single executable with Bun](/patterns/deploy) or [bundle into a single JavaScript file](/patterns/deploy#compile-to-javascript). It’s recommended that you should pre-generate the declaration file (**.d.ts**) to provide type declarations to the generator. ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use( openapi({ references: fromTypes( process.env.NODE_ENV === 'production' ? 'dist/index.d.ts' : 'src/index.ts' ) }) ) ``` Having issues with type generation? ### Caveat: Explicit types OpenAPI Type Gen works best when using implicit types. Sometimes, explicit types may cause an issue because the generator is unable to resolve them properly. In this case, you can use `Prettify` to inline the type: ```ts import { Elysia, t } from 'elysia' // Your custom type interface User { id: number name: string } // Type helper to inline the type type Prettify = { [K in keyof T]: T[K] } & {} // Add Prettify to inline the type function getUser(): Prettify { return { id: 1, name: 'Nagisa' } } ``` This should fix the issue when a type is not showing up. ### Caveat: Root path As it’s unreliable to guess the root of the project, it’s recommended to provide the path to the project root to allow the generator to run correctly, especially when using a monorepo. ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' import path from 'node:path' export const app = new Elysia() .use( openapi({ references: fromTypes('src/index.ts', { projectRoot: path.join(import.meta.dir, '..') }) }) ) .get('/', { test: 'hello' as const }) .post('/json', { body: t.Object({ hello: t.String() }) }, ({ body, status }) => body) .listen(3000) ``` ### Custom tsconfig.json If you have multiple `tsconfig.json` files, it’s important that you must specify a correct `tsconfig.json` file to be used for type generation. ```ts import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' import path from 'node:path' export const app = new Elysia() .use( openapi({ references: fromTypes('src/index.ts', { // This is reference from root of the project tsconfigPath: 'tsconfig.dts.json' }) }) ) .get('/', { test: 'hello' as const }) .post('/json', { body: t.Object({ hello: t.String() }) }, ({ body, status }) => body) .listen(3000) ``` ## Standard Schema with OpenAPI Elysia will try to use a native method from each schema to convert to OpenAPI schema. However, if the schema doesn’t provide a native method, you can provide a custom schema to OpenAPI by providing a `mapJsonSchema` as follows: ### Zod OpenAPI Use the mapper for your Zod version. Zod 4 exports `toJSONSchema`; Zod 3 uses the separate `zod-to-json-schema` package. **Zod 4** ```ts import openapi from '@elysia/openapi' import * as z from 'zod' openapi({ mapJsonSchema: { zod: z.toJSONSchema } }) ``` **Zod 3** ```ts import openapi from '@elysia/openapi' import { zodToJsonSchema } from 'zod-to-json-schema' openapi({ mapJsonSchema: { zod: zodToJsonSchema } }) ``` ### Valibot OpenAPI Valibot uses a separate package (`@valibot/to-json-schema`) to convert a Valibot schema to JSON Schema. ```ts import openapi from '@elysia/openapi' import { toJsonSchema } from '@valibot/to-json-schema' openapi({ mapJsonSchema: { valibot: toJsonSchema } }) ``` ### Effect OpenAPI As Effect doesn’t have a `toJSONSchema` method on the schema, we need to provide a custom mapper to convert Effect schema to OpenAPI schema. ```ts import openapi from '@elysia/openapi' import { JSONSchema } from 'effect' openapi({ mapJsonSchema: { effect: JSONSchema.make } }) ``` ## Describing routes We can add route information by providing a schema type. However, sometimes defining only a type does not make it clear what the route might do. You can use [detail](/plugins/openapi/#detail) fields to explicitly describe the route. ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .post( '/sign-in', { body: t.Object( { username: t.String(), password: t.String({ minLength: 8, description: 'User password (at least 8 characters)' }) }, { description: 'Expected a username and password' } ), detail: { summary: 'Sign in the user', tags: ['authentication'] } }, ({ body }) => body.username) ``` The detail fields follow an OpenAPI V3 definition with auto-completion and type-safety by default. The plugin includes these details in the generated operation. This example describes a sign-in request; real sign-in logic must verify the credentials rather than simply return a username. ## Response headers We can add response headers by wrapping a schema with `withHeaders`: ```ts import { Elysia, t } from 'elysia' import { openapi, withHeaders } from '@elysia/openapi' new Elysia() .use(openapi()) .get( '/thing', { response: withHeaders( t.Literal('Hi'), { 'x-powered-by': t.Literal('Elysia') } ) }, ({ set }) => { set.headers['x-powered-by'] = 'Elysia' return 'Hi' } ) ``` Note that `withHeaders` is an annotation only, and does not enforce or validate the actual response headers. You need to set the headers manually. ## Hide route You can hide the route from the API documentation page by setting `detail.hide` to `true` ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .post( '/sign-in', { body: t.Object( { username: t.String(), password: t.String() }, { description: 'Expected a username and password' } ), detail: { hide: true } }, ({ body }) => ({ username: body.username }) ) ``` ## Tags Elysia can separate the endpoints into groups by using the OpenAPI tag system Firstly define the available tags in the OpenAPI configuration ```ts new Elysia().use( openapi({ documentation: { tags: [ { name: 'App', description: 'General endpoints' }, { name: 'Auth', description: 'Authentication endpoints' } ] } }) ) ``` Then assign tags in the route options. In this example, `db` is your application’s database client: ```ts new Elysia() .get('/', { detail: { tags: ['App'] } }, () => 'Hello Elysia') .group('/auth', (app) => app.post( '/sign-up', { body: t.Object({ username: t.String(), password: t.String() }), detail: { tags: ['Auth'] } }, ({ body }) => db.user.create({ data: body, select: { id: true, username: true } }) ) ) ``` The routes appear under their assigned tags: ![OpenAPI routes grouped by tags](/assets/swagger-demo.webp) ### Tags group Elysia may accept tags to add an entire instance or group of routes to a specific tag. ```ts import { Elysia, t } from 'elysia' new Elysia({ tags: ['user'] }) .get('/user', 'user') .get('/admin', 'admin') ``` ## Reference model Registered models appear as named schema components in the generated OpenAPI document, and routes reference those components. When routes share a schema, give it a name and reuse it. Start with the duplicated shape: ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/profile', { body: t.Object({ username: t.String(), displayName: t.String() }), response: t.Object({ username: t.String(), displayName: t.String() }) }, ({ body }) => body) ``` Extract it to a variable when that’s all you need: ```ts import { Elysia, t } from 'elysia' const Profile = t.Object({ username: t.String(), displayName: t.String() }) new Elysia() .post('/profile', { body: Profile, response: Profile }, ({ body }) => body) ``` For named references and autocomplete across routes, register it with `.model()`: ```ts import { Elysia, t } from 'elysia' new Elysia() .model({ profile: t.Object({ username: t.String(), displayName: t.String() }) }) .post('/profile', { body: 'profile', response: 'profile' }, ({ body }) => body) ``` ### Share models with a plugin Put related models in a plugin when several controllers need them: profile.model.ts ```ts import { Elysia, t } from 'elysia' export const profileModel = new Elysia() .model({ profile: t.Object({ username: t.String(), displayName: t.String() }) }) ``` Import the plugin and register it before the routes that use its models: index.ts ```ts import { Elysia } from 'elysia' import { profileModel } from './profile.model' new Elysia() .use(profileModel) .post('/profile', { body: 'profile', response: 'profile' }, ({ body }) => body) ``` This keeps the model definitions together and makes their names available to routes and OpenAPI generation. The profile examples use public fields; a sign-in response shouldn’t echo a submitted password. ### Multiple models Pass an object whose keys are model names and whose values are schemas: ```ts import { Elysia, t } from 'elysia' export const authModel = new Elysia() .model({ number: t.Number(), sign: t.Object({ username: t.String(), password: t.String() }) }) ``` ### Naming convention Use a namespace when different parts of your app register similarly named models. Names such as `admin.auth` and `user.auth` make their ownership clear: ```ts import { Elysia, t } from 'elysia' export const adminModels = new Elysia() .model({ 'admin.auth': t.Object({ username: t.String(), password: t.String() }) }) export const userModels = new Elysia() .model({ 'user.auth': t.Object({ username: t.String(), password: t.String() }) }) ``` Choose a convention with your team and avoid registering unrelated schemas under the same name. ## Guard Use a guard to apply the same `detail` to every route registered after it. ```ts import { Elysia, t } from 'elysia' new Elysia() .guard({ detail: { description: 'Require user to be logged in' } }) .get('/user', 'user') .get('/admin', 'admin') ``` ## Change OpenAPI endpoint You can change the OpenAPI endpoint by setting [path](/plugins/openapi/#path) in the plugin config. ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use( openapi({ path: '/v2/openapi' }) ) .listen(3000) ``` ## Customize OpenAPI info We can customize the OpenAPI information by setting [documentation.info](/plugins/openapi/#documentation) in the plugin config. ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use( openapi({ documentation: { info: { title: 'Elysia Documentation', version: '1.0.0' } } }) ) .listen(3000) ``` This can be useful for - adding a title - setting an API version - adding a description explaining what our API is about - explaining what tags are available, what each tag means ## Security configuration To secure your API endpoints, you can define security schemes in the OpenAPI configuration. The example below documents Bearer Authentication (JWT) for your endpoints: ```ts new Elysia().use( openapi({ documentation: { components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } } } }) ) export const addressController = new Elysia({ prefix: '/address', detail: { tags: ['Address'], security: [ { bearerAuth: [] } ] } }) ``` This documents bearer authentication for routes under `/address`; it does not validate a JWT or deny access. Add an authentication hook that verifies the token before the handler runs, and register `addressController` on the app whose OpenAPI plugin generates the document. --- Source: [patterns/opentelemetry/index.md](patterns/opentelemetry/index.md) # OpenTelemetry Use the Elysia v2 OpenTelemetry plugin to record request spans and export them to a tracing backend. The examples target the v2 plugin API, not the older v1 integration. Terminal window ```bash bun add @elysia/opentelemetry@next ``` Register the plugin on your application: ```ts import { Elysia } from 'elysia' import { opentelemetry } from '@elysia/opentelemetry' new Elysia() .use(opentelemetry()) ``` ![OpenTelemetry visualization via Axiom](/assets/axiom.webp) Why use OpenTelemetry with Elysia? - One plugin registration - Span name is the function name - Grouping relevant lifecycle events together - Wrap code to record a specific part - Support for server-sent events and response streaming - Compatible with any OpenTelemetry-compatible library You may export telemetry data to Jaeger, Zipkin, New Relic, Axiom or any other OpenTelemetry-compatible backend. ### Export OpenTelemetry data We can export OpenTelemetry data to any backend that supports the OpenTelemetry protocol. Here’s an example of exporting telemetry to [Axiom](https://axiom.co) ```ts import { Elysia } from 'elysia' import { opentelemetry } from '@elysia/opentelemetry' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' new Elysia().use( opentelemetry({ spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter({ url: 'https://api.axiom.co/v1/traces', headers: { Authorization: `Bearer ${Bun.env.AXIOM_TOKEN}`, 'X-Axiom-Dataset': Bun.env.AXIOM_DATASET } }) ) ] }) ) ``` ## OpenTelemetry SDK Elysia OpenTelemetry is for applying OpenTelemetry to an Elysia server only. You may use the OpenTelemetry SDK normally. If a span runs under Elysia’s request span, it will automatically appear in the Elysia trace. Use the plugin’s `getTracer` and `record` utilities when you want to add spans around your own work. In the next example, `db` is your application’s database client. ```ts import { Elysia } from 'elysia' import { record } from '@elysia/opentelemetry' export const plugin = new Elysia().get('/users', () => { return record('database.query', () => { return db.query('SELECT * FROM users') }) }) ``` ## Record utility `record` is equivalent to OpenTelemetry’s `startActiveSpan` but it will handle auto-closing and capture exceptions automatically. You may think of `record` as a label for your code that will be shown in the trace. ### Prepare your codebase for observability Elysia OpenTelemetry groups lifecycle work and uses each hook’s **function name** as its span name. Here, `getProfile` is your application’s profile lookup function. It’s a good time to **name your function**. If your hook handler is an arrow function, you may refactor it to a named function to understand the trace better. Otherwise, your trace span will be named as `anonymous`. ```ts const bad = new Elysia() // Note: span name will be anonymous .derive(async ({ cookie: { session } }) => { return { user: await getProfile(session.value) } }) const good = new Elysia() // Yes span name will be loadProfile .derive(async function loadProfile({ cookie: { session } }) { return { user: await getProfile(session.value) } }) ``` ## getCurrentSpan `getCurrentSpan` is a utility to get the current span of the current request when you are outside of the handler. ```ts import { getCurrentSpan } from '@elysia/opentelemetry' function utility() { const span = getCurrentSpan() span?.setAttributes({ 'custom.attribute': 'value' }) } ``` This uses the active OpenTelemetry context. Outside an active request or span it can return `undefined`, so check before using it. ## setAttributes `setAttributes` is a utility to set attributes to the current span. ```ts import { setAttributes } from '@elysia/opentelemetry' function utility() { setAttributes({ 'custom.attribute': 'value' }) } ``` This is syntax sugar for `getCurrentSpan()?.setAttributes`, so it does nothing outside an active span. ## Configuration See [opentelemetry plugin](/plugins/opentelemetry/) for configuration options and definitions. ## Instrumentations Many instrumentation libraries require that the SDK **MUST** run before importing the module. For example, to use `PgInstrumentation`, the `OpenTelemetry SDK` must run before importing the `pg` module. To achieve this in Bun, we can 1. Separate an OpenTelemetry setup into a different file 2. create `bunfig.toml` to preload the OpenTelemetry setup file Let’s create a new file in `src/instrumentation.ts` ```ts import { opentelemetry } from '@elysia/opentelemetry' import { PgInstrumentation } from '@opentelemetry/instrumentation-pg' export const instrumentation = opentelemetry({ instrumentations: [new PgInstrumentation()] }) ``` Then we can apply this `instrumentation` plugin into our main instance in `src/index.ts` ```ts import { Elysia } from 'elysia' import { instrumentation } from './instrumentation.ts' new Elysia().use(instrumentation).listen(3000) ``` Then create a `bunfig.toml` with the following: `bunfig.toml` ```toml preload = ["./src/instrumentation.ts"] ``` This will tell Bun to load and set up `instrumentation` before running the `src/index.ts` allowing OpenTelemetry to do its setup as needed. ### Deploying to production If you are using `bun build` or other bundlers, OpenTelemetry relies on monkey-patching `node_modules/`. To make instrumentation work properly, we need to specify libraries to be instrumented as external modules to exclude them from being bundled. For example, if you are using `@opentelemetry/instrumentation-pg` to instrument the `pg` library, we need to exclude `pg` from being bundled and make sure that it is importing `node_modules/pg`. To make this work, we may specify `pg` as an external module with `--external pg` Terminal window ```bash bun build --compile --external pg --outfile server src/index.ts ``` This tells Bun not to bundle `pg` into the final output file. The library will be imported from the **node\_modules** directory at runtime. So on a production server, you must also keep the **node\_modules** directory. It’s recommended to specify packages that should be available in a production server as **dependencies** in **package.json** and use `bun install --production` to install only production dependencies. Keep every package left external in `dependencies`: ```json { "dependencies": { "pg": "^8.15.6" }, "devDependencies": { "@elysia/opentelemetry": "2.0.0-beta.2", "@opentelemetry/instrumentation-pg": "^0.52.0", "@types/pg": "^8.11.14", "elysia": "2.0.0-beta.32" } } ``` Then, after running a build command, run this on a production server: Terminal window ```bash bun install --production ``` If the node\_modules directory still includes development dependencies, you may remove the node\_modules directory and reinstall production dependencies again. --- Source: [patterns/trace/index.md](patterns/trace/index.md) # Trace Use `.trace()` to measure the time spent in each request lifecycle and its handlers. It helps you find the slow part before changing code. ## Trace Trace uses callback listeners to report when each lifecycle event starts and ends. An `onStop` callback finishes before the next lifecycle event runs. First install the trace capability with `.use(trace())`, imported from `elysia/trace`. Then register a `.trace()` callback on the instance to subscribe to lifecycle events. You may listen to each lifecycle by adding an `on` prefix followed by the lifecycle name, for example `onHandle` to listen to the `handle` event. ```ts import { Elysia } from 'elysia' import { trace } from 'elysia/trace' const app = new Elysia() .use(trace()) .trace(async ({ onHandle }) => { onHandle(({ begin, onStop }) => { onStop(({ end }) => { console.log('handle took', end - begin, 'ms') }) }) }) .get('/', () => 'Hi') .listen(3000) ``` Please refer to [Life Cycle Events](/essential/life-cycle/) for more information: ## Children Lifecycle groups expose child events for their registered hooks. `handle` represents the route handler itself; use `onEvent` on a hook group to measure each hook. You can use `onEvent` to listen to each child event in order ```ts import { Elysia } from 'elysia' import { trace } from 'elysia/trace' const sleep = (time = 1000) => new Promise((resolve) => setTimeout(resolve, time)) const app = new Elysia() .use(trace()) .trace(async ({ onBeforeHandle }) => { onBeforeHandle(({ total, onEvent }) => { console.log('total children:', total) onEvent(({ onStop }) => { onStop(({ elapsed }) => { console.log('child took', elapsed, 'ms') }) }) }) }) .get('/', { beforeHandle: [ function prepare() {}, async function delay() { await sleep() } ] }, () => 'Hi') .listen(3000) ``` In this example, total children will be `2` because there are 2 children in the `beforeHandle` event. Then we listen to each child event by using `onEvent` and print the duration of each child event. ## Trace parameter The callback passed to `.trace()` receives the request’s trace context: ```ts import { Elysia } from 'elysia' import { trace } from 'elysia/trace' const app = new Elysia() .use(trace()) // This is trace parameter // hover to view the type .trace((parameter) => { }) .get('/', () => 'Hi') .listen(3000) ``` `trace` accepts the following parameters: ### id - `string` A per-request UUID string, useful for correlating logs. ### context - `Context` Elysia’s [Context](/essential/handler#context), eg. `set`, `store`, `query`, `params` ### set - `Context.set` Shortcut for `context.set`, to set headers or the status of the context ### store - `Singleton.store` Shortcut for `context.store`, to access data in the context ### time - `number` Timestamp of when the request is called ### onEvent listeners An event listener for each life-cycle event. You may listen to the following life-cycle events: - **onRequest** - get notified of every new request - **onParse** - array of functions to parse the body - **onTransform** - transform request and context before validation - **onBeforeHandle** - custom requirement to check before the main handler, can skip the main handler if a response is returned. - **onHandle** - function assigned to the path - **onAfterHandle** - interact with the response before sending it back to the client - **onMapResponse** - map returned value into a Web Standard Response - **onError** - handle errors thrown while processing a request - **onAfterResponse** - cleanup function after the response is sent ## Trace listener A listener for each life-cycle event ```ts import { Elysia } from 'elysia' import { trace } from 'elysia/trace' const app = new Elysia() .use(trace()) .trace(({ onBeforeHandle }) => { // This is trace listener // hover to view the type onBeforeHandle((parameter) => { }) }) .get('/', () => 'Hi') .listen(3000) ``` Each lifecycle listener accepts the following ### name - `string` The name of the function. If the function is anonymous, the name will be `anonymous` ### begin - `number` The time when the function is started ### end - `Promise` The time when the function ends. The promise will be resolved when the function ends ### error - `Promise` Error that was thrown in the lifecycle. The promise will be resolved when the function ends ### onStop - `callback?: (detail: TraceEndDetail) => any` A callback that will be executed when the lifecycle ends ```ts import { Elysia } from 'elysia' import { trace } from 'elysia/trace' const app = new Elysia() .use(trace()) .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers['X-Elapsed'] = elapsed.toString() }) }) }) .get('/', () => 'Hi') .listen(3000) ``` It’s recommended to mutate context in this function as there’s a lock mechanism to ensure the context is mutated successfully before moving on to the next lifecycle event ## TraceEndDetail A parameter that is passed to the `onStop` callback ### end - `number` The time when the function is ended ### error - `Error | null` Error that was thrown in the lifecycle ### elapsed - `number` Elapsed time of the lifecycle or `end - begin` --- Source: [patterns/typebox/index.md](patterns/typebox/index.md) # TypeBox Import `t` from `elysia` to write schemas that validate values at runtime and infer their TypeScript types. This reference covers [primitive types](#primitive-type), [Elysia types](#elysia-type), and [Elysia’s conversion behavior](#elysia-behavior). ## Primitive type TypeBox schemas use familiar TypeScript names, such as `String`, `Number`, `Boolean`, and `Object`. It also provides `Intersect`, `KeyOf`, and `Tuple` for more complex types. To validate a request body as a string, pass `t.String()` in the route’s schema before the handler. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/', { body: t.String() }, ({ body }) => `Hello ${body}`) .listen(3000) ``` Elysia runs the handler when the body matches. Otherwise, it raises a validation error through the [error lifecycle](/essential/life-cycle/#error-error-handling). ### Basic type | TypeBox | TypeScript | | ----------------------------- | --------------- | | `t.String()` | `string` | | `t.Number()` | `number` | | `t.Boolean()` | `boolean` | | `t.Array(t.Number())` | `number[]` | | `t.Object({ x: t.Number() })` | `{ x: number }` | | `t.Null()` | `null` | | `t.Literal(42)` | `42` | Elysia re-exports TypeBox types alongside its HTTP-specific types. See the [TypeBox reference](https://github.com/sinclairzx81/typebox) for the full API. ### Attribute Pass options to add validation constraints. These follow JSON Schema keywords. ```ts import { t } from 'elysia' // Accepts 'saltyaom@elysia.com' t.String({ format: 'email' }) // Accepts numbers from 10 through 100 t.Number({ minimum: 10, maximum: 100 }) // Accepts one through five numbers t.Array(t.Number(), { minItems: 1, maxItems: 5 }) // Accepts { x: 100, y: 200 } t.Object({ x: t.Number() }, { additionalProperties: true }) ``` See the [JSON Schema validation specification](https://json-schema.org/draft/2020-12/json-schema-validation) for the individual keywords. ## Honorable mentions These patterns help you combine schemas and make fields optional. ### Union Use `t.Union` to accept any of the listed types. It can be used on its own or as a field in `t.Object`. ```ts import { t } from 'elysia' // TypeScript: string | number // Accepts 'Hello' or 123 t.Union([ t.String(), t.Number() ]) ``` ### Optional Wrap a field in `t.Optional` to allow it to be omitted. ```ts import { t } from 'elysia' // TypeScript: { x: number, y?: number } // Accepts { x: 123 } t.Object({ x: t.Number(), y: t.Optional(t.Number()) }) ``` ### Partial Use `t.Partial` to make every field in an object optional. ```ts import { t } from 'elysia' // TypeScript: { x?: number, y?: number } // Accepts { y: 123 } t.Partial( t.Object({ x: t.Number(), y: t.Number() }) ) ``` TypeBox 1.0 Elysia 2 uses TypeBox 1.0. Use `t.Codec` in place of `t.Transform`; `t.Recursive`, `t.Not`, and `t.RegExp` are no longer exported. ## Elysia type Elysia adds types for HTTP data, including files, cookies, and values encoded as strings. | Type | Accepts | | ------------------------------- | ----------------------------------------------- | | [UnionEnum](#unionenum) | One of a list of literal values | | [File](#file) | A single file | | [Files](#files) | Files in a single field | | [Cookie](#cookie) | A cookie jar with validation and cookie options | | [Nullable](#nullable) | A matching value or `null` | | [MaybeEmpty](#maybeempty) | A matching value, `null`, or `undefined` | | [Form](#form) | A FormData response | | [Uint8Array](#uint8array) | A `Uint8Array` or an `ArrayBuffer` to convert | | [ArrayBuffer](#arraybuffer) | An `ArrayBuffer` | | [ObjectString](#objectstring) | An object or a JSON object string | | [BooleanString](#booleanstring) | A boolean or its string representation | | [Numeric](#numeric) | A number or numeric string | ### UnionEnum Accept one of the listed values. Values can be strings, numbers, or `null`. ```ts import { t } from 'elysia' t.UnionEnum(['rapi', 'anis', 1, null]) ``` ### File Validate a single uploaded file. ```ts import { t } from 'elysia' t.File() ``` #### type Restrict the file’s media type, such as `image/png` or `image`. An array accepts any of its listed types. ```ts import { t } from 'elysia' t.File({ type: ['image/png', 'image/jpeg'] }) ``` File content detection Elysia 2 doesn’t bundle `file-type`. A `type` check requires a detector: install `file-type` and register it once with `setFileTypeDetector`. Without one, every upload checked against `type` fails validation. See [file validation](/essential/validation/#file). #### minSize Set the lower file-size bound in bytes or use a `k` or `m` suffix. The file size must be at least the bound. ```ts import { t } from 'elysia' t.File({ minSize: '1k' }) ``` #### maxSize Set the upper file-size bound in bytes or use a `k` or `m` suffix. The file size must not exceed the bound. ```ts import { t } from 'elysia' t.File({ maxSize: '5m' }) ``` #### File unit suffix | Suffix | Bytes | | ------ | --------: | | `k` | 1,024 | | `m` | 1,048,576 | Both size options accept a number or a string such as `'512k'`. ### Files Use `t.Files` for files in one field. It supports the [File](#file) options and array length constraints such as `minItems` and `maxItems`. ```ts import { t } from 'elysia' t.Files() ``` ### Cookie Describe a cookie jar with object properties and cookie configuration. ```ts import { t } from 'elysia' t.Cookie({ name: t.String() }) ``` See [cookies](/patterns/cookie/) for reading, writing, and signing cookie values. #### secrets Pass `secrets` in the second argument to sign cookies. A string uses one key; an array supports [key rotation](/patterns/cookie/#cookie-rotation). ```ts import { t } from 'elysia' t.Cookie({ name: t.String() }, { secrets: ['current-secret', 'previous-secret'], sign: ['name'] }) ``` New cookies use the first secret, which must be a non-empty string. Replace the example secrets with private application secrets. ### Nullable Accept a matching value or `null`, but not `undefined`. ```ts import { t } from 'elysia' t.Nullable(t.String()) ``` ### MaybeEmpty Accept a matching value, `null`, or `undefined`. An empty string is accepted only if the wrapped schema accepts it. ```ts import { t } from 'elysia' t.MaybeEmpty(t.String()) ``` ### Form Use `t.Form` to validate a FormData response returned by the [form helper](/essential/handler/#form-data). ```ts import { t } from 'elysia' t.Form({ someValue: t.File() }) ``` ### Uint8Array Accept an `ArrayBuffer` or `Uint8Array` and decode it to a `Uint8Array`. Pair it with the `arrayBuffer` body parser for binary uploads. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/upload', { parse: 'arrayBuffer', body: t.Uint8Array() }, ({ body }) => body.byteLength) ``` The API spelling is `t.Uint8Array()`, not `t.UInt8Array()`. ### ArrayBuffer Validate an `ArrayBuffer` without converting it to a typed array. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/upload', { parse: 'arrayBuffer', body: t.ArrayBuffer() }, ({ body }) => body.byteLength) ``` ### ObjectString Accept an object or a JSON string that decodes to the specified object. This is useful in query strings, headers, and FormData fields. ```ts import { t } from 'elysia' // Accepts { name: 'Rapi' } or '{"name":"Rapi"}' t.ObjectString({ name: t.String() }) ``` ### BooleanString Accept a boolean or the strings `'true'` and `'false'`, then decode the result to a boolean. ```ts import { t } from 'elysia' t.BooleanString() ``` ### Numeric Accept a number or numeric string, then decode the result to a number. This is useful for path parameters and query values. ```ts import { t } from 'elysia' t.Numeric({ minimum: 1, maximum: 100 }) ``` `Numeric` supports the same numeric constraints as `t.Number`. ## Elysia behavior Elysia converts common string-based HTTP inputs before your handler runs. Creating a schema outside a route doesn’t apply these route conversions. ### Optional request schema You can wrap the entire request schema in `t.Optional`, as well as individual object fields. This also applies to body and header schemas. ```ts import { Elysia, t } from 'elysia' new Elysia() .get('/optional', { query: t.Optional( t.Object({ name: t.String() }) ) }, ({ query }) => query) ``` `/optional` can omit the query entirely. When a non-empty query is provided, it must contain a string `name`. ### Number to Numeric Elysia converts `t.Number` at the root and direct properties of string-based request schemas, such as params, query, and headers, to numeric conversion. It also converts a root numeric body, but a number inside a JSON body object stays a number. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/:id', { params: t.Object({ // The path string becomes a number id: t.Number() }), body: t.Object({ // JSON must contain a number, not a string id: t.Number() }) }, ({ params, body }) => ({ pathId: params.id, bodyId: body.id })) // No route conversion here t.Number() ``` For `/42` with `{"id": 10}`, both handler values are numbers. A JSON body of `{"id": "10"}` fails validation. ### Boolean to BooleanString Boolean conversion follows the same rule as [numeric conversion](#number-to-numeric). Direct path, query, and header fields accept string booleans; a boolean inside a JSON object stays a boolean. ```ts import { Elysia, t } from 'elysia' new Elysia() .post('/:enabled', { params: t.Object({ // The path string becomes a boolean enabled: t.Boolean() }), body: t.Object({ // JSON must contain a boolean, not a string enabled: t.Boolean() }) }, ({ params, body }) => ({ pathEnabled: params.enabled, bodyEnabled: body.enabled })) // No route conversion here t.Boolean() ``` For `/true` with `{"enabled": false}`, the handler receives `true` and `false`. A JSON body of `{"enabled": "false"}` fails validation. --- Source: [patterns/typescript/index.md](patterns/typescript/index.md) # TypeScript Elysia has first-class support for TypeScript out of the box. Most of the time, you wouldn’t need to add any TypeScript annotations manually. ## Inference Elysia infers the type of request and response based on the schema you provide. ```ts import { Elysia, t } from 'elysia' import { z } from 'zod' const app = new Elysia() .post('/user/:id', { body: t.Object({ id: t.String() }), query: z.object({ name: z.string() }) }, ({ body }) => body) ``` Elysia can automatically infer types from schema like TypeBox and [your favorite validation library](/essential/validation#standard-schema) like: - Zod - Valibot - ArkType - Effect Schema - Yup - Joi ### Schema to type All schema libraries supported by Elysia can be converted to TypeScript types. #### TypeBox ```ts import { Elysia, t, type UnwrapSchema } from 'elysia' const User = t.Object({ id: t.String(), name: t.String() }) type User = UnwrapSchema ``` #### Zod ```ts import { z } from 'zod' const User = z.object({ id: z.string(), name: z.string() }) type User = z.infer ``` #### Valibot ```ts import * as v from 'valibot' const User = v.object({ id: v.string(), name: v.string() }) type User = v.InferOutput ``` #### ArkType ```ts import { type } from 'arktype' const User = type({ id: 'string', name: 'string' }) type User = typeof User.infer ``` ## Type performance Elysia is built with type inference performance in mind. Before every release, we have a local benchmark to ensure that type inference is always snappy, fast, and doesn’t blow up your IDE with a “Type instantiation is excessively deep and possibly infinite” error. Most of the time when writing Elysia, you wouldn’t encounter any type performance issue. However, if you do, here is how to break down what’s slowing down your type inference: 1. Navigate to the root of your project and run ```plaintext tsc --generateTrace trace --noEmit --incremental false ``` This should generate a `trace` folder in your project root. 2. Open [Perfetto UI](https://ui.perfetto.dev) and drag the `trace/trace.json` file ![Perfetto](/assets/perfetto.webp) > It should show you a flame graph like this Then you can find a chunk that takes a long time to be evaluated. Click on it and it should show you how long the inference takes, and which file and line number it is coming from. This should help you to identify the bottleneck of your type inference. ### Eden If you are having a slow type inference issue when using [Eden](/eden/), you can try using a sub app of Elysia to isolate the type inference. ```ts import { Elysia } from 'elysia' import { plugin1, plugin2, plugin3 } from './plugin' const app = new Elysia() .use([plugin1, plugin2, plugin3]) .listen(3000) export type app = typeof app // Export sub app export type subApp = typeof plugin1 ``` And on your frontend, you can import the sub app instead of the whole app. ```ts import { treaty } from '@elysia/eden' import type { subApp } from 'backend/src' const api = treaty('localhost:3000') ``` This should make your type inference faster as it doesn’t need to evaluate the whole app. See [Eden Treaty](/eden/) to learn more about Eden. --- Source: [patterns/unit-test/index.md](patterns/unit-test/index.md) # Unit test Test routes with Web Standard `Request` and `Response` objects. You don’t need to open a port for a handler test. Elysia provides the **Elysia.handle** method, which accepts a Web Standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and returns a promise of a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), simulating an HTTP Request. Bun includes a built-in [test runner](https://bun.sh/docs/cli/test) that offers a Jest-like API through the `bun:test` module, facilitating the creation of unit tests. Create **test/index.test.ts** in the root of the project directory with the following: test/index.test.ts ```ts import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' describe('Elysia', () => { it('returns a response', async () => { const app = new Elysia().get('/', () => 'hi') const response = await app .handle(new Request('http://localhost/')) .then((res) => res.text()) expect(response).toBe('hi') }) }) ``` Then we can perform tests by running **bun test**. Terminal window ```bash bun test ``` Pass an absolute URL to `new Request()`. A relative path such as `/user` is not enough. We can also use other testing libraries like Jest to create Elysia unit tests. ## Eden Treaty test We may use Eden Treaty to create an end-to-end type safety test for an Elysia server as follows: test/index.test.ts ```ts import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' const app = new Elysia().get('/hello', 'hi') const api = treaty(app) describe('Elysia', () => { it('returns a response', async () => { const { data, error } = await api.hello.get() expect(error).toBeNull() expect(data).toBe('hi') }) }) ``` See [Eden Treaty Unit Test](/eden/treaty/unit-test/) for setup and more information. --- Source: [patterns/websocket/index.md](patterns/websocket/index.md) # WebSocket WebSocket keeps a connection open so your client and server can send messages to each other. Unlike repeated HTTP requests, each message doesn’t need a new request and response exchange. Elysia uses Bun’s WebSocket support, built on [uWebSockets](https://github.com/uNetworking/uWebSockets). Socket.IO uses its own protocol on top of transports; use a standard WebSocket client for these routes. ## Open a socket Register the WebSocket plugin once, then add a `.ws()` route. Connect to `ws://localhost:3000/ws`; each message is sent back to its client. ```typescript import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', ({ body }) => body) .listen(3000) ``` ## Validate messages The `body` schema validates incoming messages. Send `{"message":"Hello"}` as JSON and the server replies with `Hello`. ```typescript import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { body: t.Object({ message: t.String() }) }, ({ body }) => body.message) .listen(3000) ``` ## Send more than one reply A generator can yield multiple replies. Each yield is a separate message, checked against the response schema. ```typescript import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { body: t.Object({ message: t.String() }), response: t.String() }, function* ({ body }) { yield 'Message received' yield body.message }) .listen(3000) ``` ## WebSocket message validation Like HTTP routes, WebSocket routes accept schemas for request data and responses. Put the options before the message handler: ```ts import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' const app = new Elysia() .use(websocket()) .ws('/ws', { body: t.Object({ message: t.String() }), query: t.Object({ id: t.String() }), response: t.Object({ id: t.String(), message: t.String(), time: t.Number() }) }, ({ query, body }) => ({ id: query.id, message: body.message, time: Date.now() })) .listen(3000) ``` Connect to `ws://localhost:3000/ws?id=42` and send `{"message":"Hello"}`. The response includes that message, the query’s `id`, and the current timestamp. Context properties such as `query` and `params` sit directly on the socket argument in Elysia 2. Use `ws.query`, not the old `ws.data.query` shape. | Schema property | Validates | | --------------- | -------------------------------------------------- | | `body` | An incoming message | | `query` | The connection URL’s query string | | `params` | Path parameters | | `headers` | The upgrade request’s headers | | `cookie` | The upgrade request’s cookies | | `response` | Messages returned, yielded, or sent by the handler | Elysia parses JSON-shaped string messages before validation. It also recognizes booleans, `null`, and numeric strings that can be converted safely. Other text remains a string; binary messages remain binary. ## Configuration Pass server-wide WebSocket options to `websocket()`: ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket({ idleTimeout: 30 })) .ws('/ws', ({ body }) => body) .listen(3000) ``` This replaces the old constructor `websocket` option. Elysia’s Bun adapter uses [Bun’s WebSocket configuration](https://bun.sh/docs/api/websockets). The following defaults are Bun defaults. ### perMessageDeflate Default: `false`. Enable compression for clients that support it. Compression is disabled unless you enable it. ### maxPayloadLength The maximum incoming message size in bytes. Bun’s default is `16 * 1024 * 1024` (16 MiB). ### idleTimeout Default: `120` seconds. Close an idle connection after this time without messages or pings. ### backpressureLimit Default: `16 * 1024 * 1024` bytes (16 MiB). The maximum number of bytes buffered for one connection. ### closeOnBackpressureLimit Default: `false`. Close the connection when its backpressure limit is reached. ## Methods Use `.ws()` to register a WebSocket route. The socket argument provides methods such as `send`, `publish`, `subscribe`, and `close`, alongside the route’s context properties. ## ws `.ws(path, handler)` registers a message handler. Use `.ws(path, options, handler)` when it also needs schemas or lifecycle hooks. ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', ({ body }) => body) .listen(3000) ``` The two-argument object form also works: ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { message(ws, message) { ws.send(message) } }) .listen(3000) ``` - `path` is the route exposed to WebSocket clients. - `options` configures schemas and lifecycle callbacks. - `handler` handles each incoming message. Don’t also supply `options.message` when passing a separate handler. Return or yield a value when possible so response types can be inferred. `ws.send()` remains available when you need to send explicitly. ## WebSocketHandler The route options can contain connection callbacks, message callbacks, schemas, and WebSocket tuning options. The callbacks receive Elysia’s socket wrapper with the request context at the top level. The following sections describe those callbacks. ## open Called when a connection opens. It receives the socket argument. ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { open(ws) { console.log('Connected', ws.id) }, message: ({ body }) => body }) ``` `ws.id` identifies the connection. Other socket properties include `remoteAddress`, `readyState`, and your route’s `query`, `params`, and derived values. ## message Called for each incoming message. The parsed, validated value is available as `ws.body` and as the second callback argument. ```ts import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { body: t.String(), message(ws, message) { return message.toUpperCase() } }) ``` The `body` schema determines the message type. Without a schema, handle the parsed value as unknown rather than assuming every message is a string. ## close Called when a connection closes. It receives the socket, close code, and reason. ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { close(ws, code, reason) { console.log('Disconnected', ws.id, code, reason) }, message: ({ body }) => body }) ``` ## drain Called when the socket can accept more data after backpressure. It receives the socket; close codes and reasons belong to `close`, not `drain`. ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { drain(ws) { console.log('Ready for more data', ws.id) }, message: ({ body }) => body }) ``` ## parse The WebSocket `parse` hook processes each message after default parsing and before message validation. It receives the socket and parsed value; return a replacement value or `undefined` to keep it. ```ts import { Elysia, t } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { body: t.String(), parse(_ws, message) { if (typeof message === 'string') return message.trim() } }, ({ body }) => body) ``` This is different from an HTTP body parser on the upgrade request. ## beforeHandle Runs before the connection upgrades, and participates in message handling before the message handler. Return a status to reject the upgrade before opening a socket. ```ts import { Elysia, status } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { beforeHandle() { return status(403, 'This socket is closed to new connections') } }, ({ body }) => body) ``` For authentication, validate the connection’s credentials here. Message validation belongs in the `body` schema. ## transform Runs before validation. In the current WebSocket lifecycle it runs during the upgrade and again for each message, so don’t assume it executes only once per connection. ## transformMessage The old `transformMessage` option is not part of Elysia 2’s WebSocket route options. Use [`parse`](#parse) to transform an individual message before validation. ## upgrade Use `upgrade` to add response headers to the WebSocket handshake. It accepts a static object or a function of the upgrade context. ```ts import { Elysia } from 'elysia' import { websocket } from 'elysia/websocket' new Elysia() .use(websocket()) .ws('/ws', { upgrade: { 'x-powered-by': 'Elysia' } }, ({ body }) => body) ``` To validate incoming request headers, use the `headers` schema instead. --- Source: [plugins/bearer/index.md](plugins/bearer/index.md) # Bearer plugin Plugin for [elysia](https://github.com/elysiajs/elysia) for retrieving the Bearer token. Install with: Terminal window ```bash bun add @elysia/bearer@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { bearer } from '@elysia/bearer' const app = new Elysia() .use(bearer()) .get('/sign', { beforeHandle({ bearer, set, status }) { if (!bearer) { set.headers[ 'WWW-Authenticate' ] = `Bearer realm="sign"` return status(401, 'Unauthorized') } } }, ({ bearer }) => bearer) .listen(3000) ``` This plugin is for retrieving a Bearer token specified in [RFC6750](https://www.rfc-editor.org/rfc/rfc6750#section-2). This plugin extracts the token. It does not authenticate the request or validate the token; add those checks in your application. --- Source: [plugins/compress/index.md](plugins/compress/index.md) # Compress plugin The official [`@elysia/compress`](https://github.com/elysiajs/elysia-compress) plugin compresses eligible HTTP responses. It negotiates an encoding from the request’s `Accept-Encoding` header. The runtime determines which codecs are available. ## Installation Terminal window ```bash bun add @elysia/compress ``` ## Basic use Register `compress` before the routes that use it: ```ts import { Elysia } from 'elysia' import { compress } from '@elysia/compress' new Elysia() .use(compress()) .get('/', () => 'Hello Elysia! '.repeat(100)) .listen(3000) ``` By default, the plugin uses a 1,024-byte threshold for responses with a known length and buffered responses. The response must pass the media type check. If the client does not accept an available encoding, the plugin returns the response without compression. ## Route control The `compress` route option belongs in the hook argument before the handler. Disable compression for one route while keeping the default behavior elsewhere: ```ts new Elysia() .use(compress()) .get('/compressed', () => 'Hello Elysia! '.repeat(100)) .get('/plain', { compress: false }, () => 'Hello Elysia! '.repeat(100)) ``` Set `default` to `false` to require each route to opt in: ```ts new Elysia() .use(compress({ default: false })) .get('/compressed', { compress: true }, () => 'Hello Elysia! '.repeat(100)) .get('/plain', () => 'Hello Elysia! '.repeat(100)) ``` An opted-in route still follows the configured media types, threshold, encoding negotiation, and skip conditions. ## Options ```ts new Elysia().use( compress({ encodings: ['gzip', 'deflate'], threshold: 2048, options: { gzip: { level: 6 } } }) ) ``` | Option | Default | Description | | ----------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `default` | `true` | Compress routes unless a route sets `compress: false`. Set it to `false` to require `compress: true`. | | `encodings` | `['br', 'zstd', 'gzip', 'deflate']` | Set the server preference order. The plugin removes codecs that the runtime does not support. Client quality values take priority, and the server order breaks ties. | | `threshold` | `1024` | Set the minimum number of bytes for responses with a known length and buffered responses. | | `types` | Built-in predicate | Return `true` for media types that the plugin may compress. The predicate receives a lowercase media type without parameters. It replaces the built-in predicate. | | `stream` | `true` | Compress responses marked with `Transfer-Encoding: chunked`. Set it to `false` to leave those responses uncompressed. | | `options` | `{}` | Set options for `br`, `zstd`, `gzip`, or `deflate`. Brotli and Zstandard accept their codec parameters. `gzip` and deflate accept zlib options such as `level`. | The built-in `types` predicate makes these media types eligible for compression: - `text/*` - `application/json` - media types that end in `+json` The plugin always excludes `text/event-stream`. The threshold applies to a declared `Content-Length` or a buffered body. A chunked response without a known length can start compression before the plugin knows its final size. ## Runtime codec support The runtime determines which codecs the plugin can use. The plugin uses Brotli, gzip, and deflate when the runtime provides the matching zlib codec and a Web stream bridge. Zstandard requires its matching zlib codec too. If zlib is unavailable, the Web `CompressionStream` API can provide gzip and deflate. The `CompressionStream` fallback does not apply custom gzip or deflate `options`. Do not assume that every runtime supports all four encodings. ## Skip behavior The plugin leaves a response uncompressed when any of these conditions apply: - The request method is `HEAD`. - The status is `204`, `205`, `206`, or `304`. - The response already has `Content-Encoding`. - `Cache-Control` contains `no-transform`. - The response has no body or uses `text/event-stream`. - The `types` predicate rejects the media type. - A known or buffered body is smaller than `threshold`. - The response is chunked and `stream` is `false`. ## Response headers A compressed response gets `Content-Encoding` and includes `Accept-Encoding` in `Vary`. The plugin removes `Content-Length`, `Content-Range`, `Accept-Ranges`, and `Transfer-Encoding` because those values describe the uncompressed response. It also changes a strong `ETag` to a weak `ETag`. If an otherwise eligible response has no accepted encoding, the response remains uncompressed and includes `Accept-Encoding` in `Vary`. ## Protect responses that contain secrets Compression can expose secrets through a BREACH-style response-size attack when one response contains both a secret and attacker-controlled input. Add `no-transform` to that response: ```ts app.get('/secret', ({ query, set }) => { set.headers['cache-control'] = 'private, no-transform' return { echo: query.q, token: session.token } }) ``` The plugin detects `no-transform` and returns the response without compression. --- Source: [plugins/cors/index.md](plugins/cors/index.md) # CORS plugin This plugin adds support for customizing [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) behavior. Install with: Terminal window ```bash bun add @elysia/cors@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { cors } from '@elysia/cors' new Elysia().use(cors()).listen(3000) ``` This will set Elysia to accept requests from any origin. ## Config Below is the config that is accepted by the plugin ### origin Default: `true` Indicates whether the response can be shared with the requesting code from the given origins. Value can be one of the following: - **string** - Origin compared against the request’s `Origin` header (the scheme is optional, eg: `'example.com'`); on a match, the request’s origin is echoed in the [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header, otherwise the header is omitted. `'*'` allows any origin. - **boolean** - `true` accepts any origin and reflects the request’s `Origin` header, falling back to `*` when absent. `false` disables the allow-origin header. - **RegExp** - Pattern matched against the request’s `Origin` header; allowed if matched. - **Function** - Custom logic to allow resource sharing. Allow it if `true` is returned. - Expected to have the type of: ```ts type OriginCheck = (request: Request) => boolean | void ``` - `Array` - iterate through all cases above in order, allowed if any of the values are `true`. *** ### methods Default: `true` Allowed methods for cross-origin requests. The default `true` reflects the method requested by a preflight. Set `false` to omit this header. Assign the [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) header. Value can be one of the following: - **false | null | ”** - Omit the allow-methods header. Leaving the option `undefined` uses the default. - **\*** - Allows all methods. - **string** - Expects either a single method or a comma-delimited string - (eg: `'GET, PUT, POST'`) - **string\[]** - Allow multiple HTTP methods. - eg: `['GET', 'PUT', 'POST']` *** ### allowedHeaders Default: `true` Allowed headers for an incoming request. The default `true` reflects `Access-Control-Request-Headers` on preflight requests. Assign the [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) header. Value can be one of the following: - **string** - Expects either a single header or a comma-delimited string - eg: `'Content-Type, Authorization'`. - **string\[]** - Allow multiple HTTP headers. - eg: `['Content-Type', 'Authorization']` *** ### exposeHeaders Default: `true` Expose the specified headers to browser JavaScript. The default `true` reflects the incoming request’s header names on every response, preflight included; provide a string or array to choose explicit names. Assign the [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) header. Value can be one of the following: - **string** - Expects either a single header or a comma-delimited string. - eg: `'Content-Type, X-Powered-By'`. - **string\[]** - Allow multiple HTTP headers. - eg: `['Content-Type', 'X-Powered-By']` *** ### credentials Default: `true` The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request’s credentials mode [Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) is `include`. When a request’s credentials mode [Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) is `include`, browsers will only expose the response to the frontend JavaScript code if the Access-Control-Allow-Credentials value is true. Credentials are cookies, authorization headers, or TLS client certificates. Assign the [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header. *** ### maxAge Default: `5` Indicates how long the results of a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) (that is the information contained in the [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) and [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) headers) can be cached. Assign the [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) header. *** ### preflight Default: `true` The preflight request is a request sent to check if the CORS protocol is understood and if a server is aware of using specific methods and headers. It is sent as an **OPTIONS** request with 3 HTTP request headers: - **Access-Control-Request-Method** - **Access-Control-Request-Headers** - **Origin** This config indicates if the server should respond to preflight requests. ## Pattern Below you can find the common patterns to use the plugin. ## Allow CORS by domain ```typescript import { Elysia } from 'elysia' import { cors } from '@elysia/cors' const app = new Elysia() .use( cors({ origin: /^https:\/\/(?:[a-z0-9-]+\.)*saltyaom\.com$/i }) ) .get('/', () => 'Hi') .listen(3000) ``` This allows HTTPS origins at `saltyaom.com` and its subdomains. The anchors keep unrelated hostnames from matching. --- Source: [plugins/cron/index.md](plugins/cron/index.md) # Cron plugin This plugin adds support for running cron jobs in the Elysia server. Install with: Terminal window ```bash bun add @elysia/cron@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { cron } from '@elysia/cron' new Elysia() .use( cron({ name: 'heartbeat', pattern: '*/10 * * * * *', run() { console.log('Heartbeat') } }) ) .listen(3000) ``` The code logs `Heartbeat` every 10 seconds. ## cron Create a cron job for the Elysia server. type: ```text cron(config: CronConfig): (app: Elysia) => Elysia ``` `CronConfig` accepts the parameters specified below: ### name Job name to register to `store`. This will register the cron instance to `store` with a specified name, which can be referenced in later processes, e.g., to stop the job. ### pattern Time to run the job as specified by [cron syntax](https://en.wikipedia.org/wiki/Cron) specified below: ```text ┌────────────── second (optional) │ ┌──────────── minute │ │ ┌────────── hour │ │ │ ┌──────── day of the month │ │ │ │ ┌────── month │ │ │ │ │ ┌──── day of week │ │ │ │ │ │ * * * * * * ``` This can be generated by tools like [Crontab Guru](https://crontab.guru/) ### run Function to execute each time the job is triggered. *** This plugin registers a scheduled job using [Croner](https://github.com/hexagon/croner). Below are the supported Croner options. ### timezone Time zone in Europe/Stockholm format ### startAt Schedule start time for the job ### stopAt Schedule stop time for the job ### maxRuns Maximum number of executions ### catch Continue execution even if an unhandled error is thrown by a triggered function. ### interval The minimum interval between executions, in seconds. ## Pattern Below you can find the common patterns to use the plugin. ## Stop cron job You can stop the cron job manually by accessing the cron job name registered to `store`. ```ts import { Elysia } from 'elysia' import { cron } from '@elysia/cron' const app = new Elysia() .use( cron({ name: 'heartbeat', pattern: '*/1 * * * * *', run() { console.log('Heartbeat') } }) ) .get( '/stop', ({ store: { cron: { heartbeat } } }) => { heartbeat.stop() return 'Stop heartbeat' } ) .listen(3000) ``` ## Predefined patterns You can import `Patterns` from `@elysia/cron` ```ts import { Elysia } from 'elysia' import { cron, Patterns } from '@elysia/cron' const app = new Elysia() .use( cron({ name: 'heartbeat', pattern: Patterns.everySecond(), run() { console.log('Heartbeat') } }) ) .get( '/stop', ({ store: { cron: { heartbeat } } }) => { heartbeat.stop() return 'Stop heartbeat' } ) .listen(3000) ``` ### Functions | Function | Description | | ---------------------------------------- | ----------------------------------------------------- | | `.everySeconds(2)` | Run the task every 2 seconds | | `.everyMinutes(5)` | Run the task every 5 minutes | | `.everyHours(3)` | Run the task every 3 hours | | `.everyHoursAt(3, 15)` | Run the task every 3 hours at 15 minutes | | `.everyDayAt('04:19')` | Run the task every day at 04:19 | | `.everyWeekOn(Patterns.MONDAY, '19:30')` | Run the task every Monday at 19:30 | | `.everyWeekdayAt('17:00')` | Run the task every day from Monday to Friday at 17:00 | | `.everyWeekendAt('11:00')` | Run the task on Saturday and Sunday at 11:00 | ### Function aliases to constants | Function | Constant | | ----------------- | ---------------------------------------- | | `.everySecond()` | EVERY\_SECOND | | `.everyMinute()` | EVERY\_MINUTE | | `.hourly()` | EVERY\_HOUR | | `.daily()` | EVERY\_DAY\_AT\_MIDNIGHT | | `.everyWeekday()` | EVERY\_WEEKDAY | | `.everyWeekend()` | EVERY\_WEEKEND | | `.weekly()` | EVERY\_WEEK | | `.monthly()` | EVERY\_1ST\_DAY\_OF\_MONTH\_AT\_MIDNIGHT | | `.everyQuarter()` | EVERY\_QUARTER | | `.yearly()` | EVERY\_YEAR | ### Constants | Constant | Pattern | | ---------------------------------------- | -------------------- | | `.EVERY_SECOND` | `* * * * * *` | | `.EVERY_5_SECONDS` | `*/5 * * * * *` | | `.EVERY_10_SECONDS` | `*/10 * * * * *` | | `.EVERY_30_SECONDS` | `*/30 * * * * *` | | `.EVERY_MINUTE` | `*/1 * * * *` | | `.EVERY_5_MINUTES` | `0 */5 * * * *` | | `.EVERY_10_MINUTES` | `0 */10 * * * *` | | `.EVERY_30_MINUTES` | `0 */30 * * * *` | | `.EVERY_HOUR` | `0 0-23/1 * * *` | | `.EVERY_2_HOURS` | `0 0-23/2 * * *` | | `.EVERY_3_HOURS` | `0 0-23/3 * * *` | | `.EVERY_4_HOURS` | `0 0-23/4 * * *` | | `.EVERY_5_HOURS` | `0 0-23/5 * * *` | | `.EVERY_6_HOURS` | `0 0-23/6 * * *` | | `.EVERY_7_HOURS` | `0 0-23/7 * * *` | | `.EVERY_8_HOURS` | `0 0-23/8 * * *` | | `.EVERY_9_HOURS` | `0 0-23/9 * * *` | | `.EVERY_10_HOURS` | `0 0-23/10 * * *` | | `.EVERY_11_HOURS` | `0 0-23/11 * * *` | | `.EVERY_12_HOURS` | `0 0-23/12 * * *` | | `.EVERY_DAY_AT_1AM` | `0 01 * * *` | | `.EVERY_DAY_AT_2AM` | `0 02 * * *` | | `.EVERY_DAY_AT_3AM` | `0 03 * * *` | | `.EVERY_DAY_AT_4AM` | `0 04 * * *` | | `.EVERY_DAY_AT_5AM` | `0 05 * * *` | | `.EVERY_DAY_AT_6AM` | `0 06 * * *` | | `.EVERY_DAY_AT_7AM` | `0 07 * * *` | | `.EVERY_DAY_AT_8AM` | `0 08 * * *` | | `.EVERY_DAY_AT_9AM` | `0 09 * * *` | | `.EVERY_DAY_AT_10AM` | `0 10 * * *` | | `.EVERY_DAY_AT_11AM` | `0 11 * * *` | | `.EVERY_DAY_AT_NOON` | `0 12 * * *` | | `.EVERY_DAY_AT_1PM` | `0 13 * * *` | | `.EVERY_DAY_AT_2PM` | `0 14 * * *` | | `.EVERY_DAY_AT_3PM` | `0 15 * * *` | | `.EVERY_DAY_AT_4PM` | `0 16 * * *` | | `.EVERY_DAY_AT_5PM` | `0 17 * * *` | | `.EVERY_DAY_AT_6PM` | `0 18 * * *` | | `.EVERY_DAY_AT_7PM` | `0 19 * * *` | | `.EVERY_DAY_AT_8PM` | `0 20 * * *` | | `.EVERY_DAY_AT_9PM` | `0 21 * * *` | | `.EVERY_DAY_AT_10PM` | `0 22 * * *` | | `.EVERY_DAY_AT_11PM` | `0 23 * * *` | | `.EVERY_DAY_AT_MIDNIGHT` | `0 0 * * *` | | `.EVERY_WEEK` | `0 0 * * 0` | | `.EVERY_WEEKDAY` | `0 0 * * 1-5` | | `.EVERY_WEEKEND` | `0 0 * * 6,0` | | `.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT` | `0 0 1 * *` | | `.EVERY_1ST_DAY_OF_MONTH_AT_NOON` | `0 12 1 * *` | | `.EVERY_2ND_HOUR` | `0 */2 * * *` | | `.EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM` | `0 1-23/2 * * *` | | `.EVERY_2ND_MONTH` | `0 0 1 */2 *` | | `.EVERY_QUARTER` | `0 0 1 */3 *` | | `.EVERY_6_MONTHS` | `0 0 1 */6 *` | | `.EVERY_YEAR` | `0 0 1 1 *` | | `.EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM` | `0 */30 9-17 * * *` | | `.EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM` | `0 */30 9-18 * * *` | | `.EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM` | `0 */30 10-19 * * *` | --- Source: [plugins/graphql-apollo/index.md](plugins/graphql-apollo/index.md) # GraphQL Apollo plugin Plugin for [elysia](https://github.com/elysiajs/elysia) for using GraphQL Apollo. Install with: Terminal window ```bash bun add graphql @elysia/apollo@next @apollo/server ``` Then use it: ```ts import { Elysia } from 'elysia' import { apollo, gql } from '@elysia/apollo' const app = new Elysia() .use( apollo({ typeDefs: gql` type Book { title: String author: String } type Query { books: [Book] } `, resolvers: { Query: { books: () => { return [ { title: 'Elysia', author: 'saltyAom' } ] } } } }) ) .listen(3000) ``` Accessing `/graphql` should show the Apollo GraphQL playground to work with. ## Context The `context` callback receives the Elysia context, so read `request` from it. ```ts const app = new Elysia() .use( apollo({ typeDefs, resolvers, context: async ({ request }) => { const authorization = request.headers.get('Authorization') return { authorization } } }) ) .listen(3000) ``` ## Config This plugin extends Apollo’s [ApolloServerOptions](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#options) (which is `ApolloServer`’s constructor parameter). Below are the extended parameters for configuring Apollo Server with Elysia. ### path Default: `"/graphql"` Path to expose Apollo Server. ### enablePlayground Default: `process.env.ENV !== 'production'` Determine whether Apollo should provide Apollo Playground. --- Source: [plugins/graphql-yoga/index.md](plugins/graphql-yoga/index.md) # GraphQL Yoga plugin This plugin integrates GraphQL Yoga with Elysia Install with: Terminal window ```bash bun add @elysia/graphql-yoga@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { yoga } from '@elysia/graphql-yoga' const app = new Elysia() .use( yoga({ typeDefs: /* GraphQL */ ` type Query { hi: String } `, resolvers: { Query: { hi: () => 'Hello from Elysia' } } }) ) .listen(3000) ``` Accessing `/graphql` in the browser (GET request) would show you a GraphiQL instance for the GraphQL-enabled Elysia server. Optionally, you can install a custom version of optional peer dependencies as well: Terminal window ```bash bun add graphql graphql-yoga ``` ## Resolver Elysia uses [Mobius](https://github.com/saltyaom/mobius) to infer types from the **typeDefs** field automatically, allowing you to get full type-safety and auto-complete when typing **resolver** types. ## Context You can add custom context to the resolver function by adding **context** ```ts import { Elysia } from 'elysia' import { yoga } from '@elysia/graphql-yoga' const app = new Elysia() .use( yoga({ typeDefs: /* GraphQL */ ` type Query { hi: String } `, context: { name: 'Mobius' }, // Required for resolvers to infer the context type useContext(_) {}, resolvers: { Query: { hi: async (parent, args, context) => context.name } } }) ) .listen(3000) ``` ## Config This plugin extends [GraphQL Yoga’s createYoga options, please refer to the GraphQL Yoga documentation](https://the-guild.dev/graphql/yoga-server/docs) by inlining the `schema` config at the root. Below is a config which is accepted by the plugin ### path Default: `/graphql` Endpoint to expose GraphQL handler --- Source: [plugins/html/index.md](plugins/html/index.md) # HTML plugin Allows you to use [JSX](#jsx) and HTML with proper headers and support. Install with: Terminal window ```bash bun add @elysia/html@next ``` Then use it: ```tsx import { Elysia } from 'elysia' import { html, Html } from '@elysia/html' new Elysia() .use(html()) .get( '/html', () => ` Hello World

Hello World

` ) .get('/jsx', () => ( Hello World

Hello World

)) .listen(3000) ``` This plugin will automatically add the `Content-Type: text/html; charset=utf8` header to the response, add ``, and convert it into a Response object. ## JSX Elysia HTML is based on [@kitajs/html](https://github.com/kitajs/html) allowing us to convert JSX to a string at compile time to achieve high performance. Name your file that needs to use JSX to end with the suffix **“x”**: - .js -> .jsx - .ts -> .tsx To register the TypeScript type, please append the following to **tsconfig.json**: tsconfig.json ```jsonc { "compilerOptions": { "jsx": "react", "jsxFactory": "Html.createElement", "jsxFragmentFactory": "Html.Fragment" } } ``` That’s it, now you can use JSX as your template engine: ```tsx import { Elysia } from 'elysia' import { html, Html } from '@elysia/html' new Elysia() .use(html()) .get('/', () => ( Hello World

Hello World

)) .listen(3000) ``` If the error `Cannot find name 'Html'. Did you mean 'html'?` occurs, this import must be added to the JSX template: ```tsx import { Html } from '@elysia/html' ``` It is important that it is written in uppercase. ## XSS Kita HTML escapes values marked with `safe`. Its TypeScript plugin can flag potentially unsafe JSX expressions while you edit. Use the `safe` attribute to escape untrusted text before inserting it into HTML: ```tsx import { Elysia, t } from 'elysia' import { html, Html } from '@elysia/html' new Elysia() .use(html()) .post( '/', { body: t.String() }, ({ body }) => ( Hello World

{body}

) ) .listen(3000) ``` However, when building a large-scale app, it’s best to have a type reminder to detect possible XSS vulnerabilities in your codebase. To add a type-safe reminder, please install: Terminal window ```sh bun add @kitajs/ts-html-plugin ``` Then append the following to **tsconfig.json** tsconfig.json ```jsonc { "compilerOptions": { "jsx": "react", "jsxFactory": "Html.createElement", "jsxFragmentFactory": "Html.Fragment", "plugins": [{ "name": "@kitajs/ts-html-plugin" }] } } ``` ## Options ### contentType - Type: `string` - Default: `'text/html; charset=utf8'` The content-type of the response. ### autoDetect - Type: `boolean` - Default: `true` Whether to automatically detect HTML content and set the content-type. ### autoDoctype - Type: `boolean | 'full'` - Default: `true` Whether to automatically add `` to a response starting with ``, if not found. Use `full` to also add the doctype to HTML responses returned without the `html()` helper. ```ts // Automatic response detection with html({ autoDoctype: 'full' }) app.get('/', () => '') // Explicit html helper app.get('/', ({ html }) => html('')) ``` ### isHtml - Type: `(value: string) => boolean` - Default: `isHtml` (exported function) The function is used to detect if a string is HTML or not. The built-in detector trims whitespace, requires at least 7 characters, and checks that the string starts with `<` and ends with `>`. This is a heuristic, not an HTML validator. Automatic response detection uses the function you supply here. --- Source: [plugins/jwt/index.md](plugins/jwt/index.md) # JWT plugin This plugin adds support for using JWT in Elysia handlers. Install with: Terminal window ```bash bun add @elysia/jwt@next ``` Set `JWT_SECRET` to a private signing secret before starting the server. The routes below demonstrate signing and verification; a real sign-in endpoint must verify credentials before issuing a token. Then use it: cookie ```ts import { Elysia } from 'elysia' import { jwt } from '@elysia/jwt' const app = new Elysia() .use( jwt({ name: 'jwt', secret: process.env.JWT_SECRET! }) ) .get('/sign/:name', async ({ jwt, params: { name }, cookie: { auth } }) => { const value = await jwt.sign({ name }) auth!.set({ value, httpOnly: true, secure: true, sameSite: 'lax', maxAge: 7 * 86400, path: '/profile', }) return `Sign in as ${value}` }) .get('/profile', async ({ jwt, status, cookie: { auth } }) => { const profile = await jwt.verify(auth!.value) if (!profile) return status(401, 'Unauthorized') return `Hello ${profile.name}` }) .listen(3000) ``` headers ```ts import { Elysia } from 'elysia' import { jwt } from '@elysia/jwt' const app = new Elysia() .use( jwt({ name: 'jwt', secret: process.env.JWT_SECRET! }) ) .get('/sign/:name', ({ jwt, params: { name } }) => { return jwt.sign({ name }) }) .get('/profile', async ({ jwt, status, headers: { authorization } }) => { const profile = await jwt.verify(authorization?.startsWith('Bearer ') ? authorization.slice(7) : undefined) if (!profile) return status(401, 'Unauthorized') return `Hello ${profile.name}` }) .listen(3000) ``` These cookie settings require HTTPS. The header example expects `Authorization: Bearer `. ## Config This plugin extends config from [jose](https://github.com/panva/jose). Below is a config that is accepted by the plugin. ### name Name to register the `jwt` function as. For example, the `jwt` function will be registered with a custom name. ```ts app .use( jwt({ name: 'myJWTNamespace', secret: process.env.JWT_SECRET! }) ) .get('/sign/:name', ({ myJWTNamespace, params }) => { return myJWTNamespace.sign(params) }) ``` Because some might need to use multiple `jwt` instances with different configs in a single server, explicitly registering the JWT function with a different name is needed. ### secret The private key to sign JWT payload with. ### schema Strict type validation for JWT payload. *** The following options describe JWT claims and JWS headers supported by jose. ### alg Default: `HS256` Signing Algorithm to sign JWT payload with. Possible properties for jose are: HS256 HS384 HS512 PS256 PS384 PS512 RS256 RS384 RS512 ES256 ES256K ES384 ES512 EdDSA ### iss The issuer claim identifies the principal that issued the JWT as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1) TLDR; it is usually (the domain) name of the signer. ### sub The subject claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2) ### aud The audience claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3) ### jti JWT ID claim provides a unique identifier for the JWT as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7) ### nbf The “not before” claim identifies the time before which the JWT must not be accepted for processing as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5) ### exp The expiration time claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4) ### iat The “issued at” claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT as per [RFC7519](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6) ### b64 This JWS Extension Header Parameter modifies the JWS Payload representation and the JWS Signing input computation as per [RFC7797](https://www.rfc-editor.org/rfc/rfc7797). ### kid A hint indicating which key was used to secure the JWS. This parameter allows originators to explicitly signal a change of key to recipients as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.4) ### x5t (X.509 certificate SHA-1 thumbprint) header parameter is a base64url-encoded SHA-1 digest of the DER encoding of the X.509 certificate [RFC5280](https://www.rfc-editor.org/rfc/rfc5280) corresponding to the key used to digitally sign the JWS as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.7) ### x5c (X.509 certificate chain) header parameter contains the X.509 public key certificate or certificate chain [RFC5280](https://www.rfc-editor.org/rfc/rfc5280) corresponding to the key used to digitally sign the JWS as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.6) ### x5u (X.509 URL) header parameter is a URI [RFC3986](https://www.rfc-editor.org/rfc/rfc3986) that refers to a resource for the X.509 public key certificate or certificate chain \[RFC5280] corresponding to the key used to digitally sign the JWS as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.5) ### jwk The `jwk` header contains the public JSON Web Key corresponding to the signing key. The separate `jku` header is a URL for a JWK Set. See [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.3). ### typ The `typ` (type) Header Parameter is used by JWS applications to declare the media type \[IANA.MediaTypes] of this complete JWS. This is intended for use by the application when more than one kind of object could be present in an application data structure that can contain a JWS as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.9) ### cty Content-Type parameter is used by JWS applications to declare the media type \[IANA.MediaTypes] of the secured content (the payload). This is intended for use by the application when more than one kind of object could be present in the JWS Payload as per [RFC7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.9) ## Handler Below are the values added to the handler. ### jwt.sign Signs a payload to create a JWT. Type: ```ts sign(payload: Omit & JWTPayloadInput): Promise ``` `Claims` is the shape of [schema](#schema), or any record when no schema is set. `JWTPayloadInput` holds the registered claims [iss](#iss), [sub](#sub), [aud](#aud), [jti](#jti), [nbf](#nbf), [exp](#exp) and [iat](#iat); a value set here overrides the one from config. ### jwt.verify Verify a token with the provided JWT config Type: ```ts verify(jwt?: string, options?: JWTVerifyOptions): Promise<(Claims & Omit) | false> ``` Returns the decoded payload, or `false` when the token is missing or invalid. `JWTPayloadSpec` holds the registered claims `iss`, `sub`, `aud`, `jti`, `nbf`, `exp` and `iat` as decoded values. `options` is passed to jose’s `jwtVerify`. ## Pattern Below you can find the common patterns to use the plugin. ## Set JWT expiration date Set `exp` to control token expiration. A cookie’s lifetime and a JWT’s expiration are separate settings. ```ts const app = new Elysia() .use( jwt({ name: 'jwt', secret: process.env.JWT_SECRET!, exp: '7d' }) ) .get('/sign/:name', async ({ jwt, params }) => jwt.sign(params)) ``` This will sign a JWT with an expiration date 7 days from now. --- Source: [plugins/openapi/index.md](plugins/openapi/index.md) # OpenAPI plugin [OpenAPI tutorial](/tutorial/features/openapi/) Plugin for [elysia](https://github.com/elysiajs/elysia) to auto-generate an API documentation page. Install with: Terminal window ```bash bun add @elysia/openapi@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .get('/', () => 'hello') .post('/hello', () => 'OpenAPI') .listen(3000) ``` Accessing `/openapi` would show you a Scalar UI with the generated endpoint documentation from the Elysia server. You can also access the raw OpenAPI spec at `/openapi/json`. Tip This page is the plugin configuration reference. If you’re looking for common patterns or advanced usage of OpenAPI, check out [Patterns: OpenAPI](/patterns/openapi) ## Detail `detail` extends the [OpenAPI Operation Object](https://spec.openapis.org/oas/v3.1.2.html#operation-object) The detail field is an object that can be used to describe information about the route for API documentation. It may contain the following fields: ### detail.hide You can hide the route from the generated documentation by setting `detail.hide` to `true` ```ts import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia().use(openapi()).post('/sign-in', { body: t.Object( { username: t.String(), password: t.String() }, { description: 'Expected a username and password' } ), detail: { hide: true } }, ({ body }) => ({ username: body.username })) ``` ### detail.deprecated Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is `false`. ### detail.description A verbose explanation of the operation behavior. ### detail.summary A short summary of what the operation does. ## Config Below is the config that is accepted by the plugin ### enabled Default: true Enable/Disable the plugin ### documentation OpenAPI documentation information @see ### exclude Configuration to exclude paths or methods from documentation ### exclude.methods List of methods to exclude from documentation ### exclude.paths List of paths to exclude from documentation ### exclude.staticFile Default: true Exclude static file routes from documentation ### exclude.tags List of tags to exclude from documentation ### mapJsonSchema A custom mapping function from standard schema to OpenAPI schema #### Example ```ts import { openapi } from '@elysia/openapi' import { toJsonSchema } from '@valibot/to-json-schema' openapi({ mapJsonSchema: { valibot: toJsonSchema } }) ``` ### path Default: ‘/openapi’ The endpoint to expose the OpenAPI documentation frontend ### provider Default: ‘scalar’ OpenAPI documentation frontend provider between: - `scalar`: [Scalar](https://github.com/scalar/scalar) - `swagger-ui`: [Swagger UI](https://github.com/swagger-api/swagger-ui) - `null`: disable frontend ### references Additional OpenAPI reference for each endpoint ### scalar Scalar configuration, refers to [Scalar config](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) #### Self-hosted Scalar bundle Self-host the Scalar bundle and disable CDN Fonts. Note: `cdn` is an Elysia OpenAPI plugin option (not part of Scalar’s own config); it overrides the URI to the Scalar bundle. ```ts openapi({ scalar: { cdn: "/public/scalar-standalone.min.js", // plugin override for Scalar bundle URI (self-hosted) withDefaultFonts: false, // disable Scalar’s default font CDN }, }) ``` ### specPath Default: `${path}/json` The endpoint to expose the OpenAPI specification in JSON format ### swagger Swagger config, refers to [Swagger config](https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/) --- Source: [plugins/opentelemetry/index.md](plugins/opentelemetry/index.md) # OpenTelemetry Tip This page is a **config reference** for **OpenTelemetry**. If you’re looking to set up and integrate with OpenTelemetry, we recommend taking a look at [Integrate with OpenTelemetry](/patterns/opentelemetry) instead. To start using OpenTelemetry, install `@elysia/opentelemetry` and apply the plugin to any instance. Terminal window ```bash bun add @elysia/opentelemetry@next @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto ``` ```ts import { Elysia } from 'elysia' import { opentelemetry } from '@elysia/opentelemetry' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' new Elysia() .use( opentelemetry({ spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter() ) ] }) ) ``` ![Jaeger showing collected traces](/blog/elysia-11/jaeger.webp) Elysia OpenTelemetry will **collect spans from any library compatible with the OpenTelemetry standard**, and will apply parent and child spans automatically. ## Usage See [opentelemetry](/patterns/opentelemetry) for usage and utilities ## Config This plugin extends OpenTelemetry SDK parameter options. Below is a config which is accepted by the plugin ### autoDetectResources - boolean Detect resources automatically from the environment using the default resource detectors. Default: `true` ### contextManager - ContextManager Use a custom context manager. Default: `AsyncLocalStorageContextManager`, registered by `NodeTracerProvider` ### textMapPropagator - TextMapPropagator Use a custom propagator. Default: `CompositePropagator` using W3C Trace Context and Baggage ### metricReader - MetricReader Add a MetricReader that will be passed to the MeterProvider. ### views - View\[] A list of views to be passed to the MeterProvider. Accepts an array of View-instances. This parameter can be used to configure explicit bucket sizes of histogram metrics. ### instrumentations - (Instrumentation | Instrumentation\[])\[] Configure instrumentations. No instrumentations are registered by default. Pass a list, such as `getNodeAutoInstrumentations()` from `@opentelemetry/auto-instrumentations-node` (installed separately), to enable them. Default: none ### resource - IResource Configure a resource. Resources may also be detected by using the autoDetectResources method of the SDK. ### resourceDetectors - Array\ Configure resource detectors. By default, the resource detectors are \[envDetector, processDetector, hostDetector]. NOTE: In order to enable the detection, the parameter autoDetectResources has to be true. If resourceDetectors is not set, you can also use the environment variable OTEL\_NODE\_RESOURCE\_DETECTORS to enable only certain detectors, or completely disable them: - env - host - os - process - serviceinstance (experimental) - all - enable all resource detectors above - none - disable resource detection For example, to enable only the env and host detectors: Terminal window ```bash export OTEL_NODE_RESOURCE_DETECTORS="env,host" ``` ### sampler - Sampler Configure a custom sampler. By default, all traces will be sampled. ### serviceName - string Namespace to be identified as. ### spanProcessors - SpanProcessor\[] An array of span processors to register to the tracer provider. ### traceExporter - SpanExporter Configure a trace exporter. If an exporter is configured, it will be used with a `BatchSpanProcessor`. If an exporter OR span processor is not configured programmatically, this package will automatically set up the default otlp exporter with http/protobuf protocol with a BatchSpanProcessor. ### spanLimits - SpanLimits Configure tracing parameters. These are the same trace parameters used to configure a tracer. ### checkIfShouldTrace - (request: Request) => boolean Decide whether a request is traced. Return `false` to skip tracing for that request. Default: every request is traced ### spanUrlRedaction - false | object Redact credentials and sensitive query values (such as `token`, `password`, `api_key`) in the recorded URL. Accepts `{ stripCredentials?: boolean, sensitiveQueryParams?: string[] }`: `stripCredentials` (default `true`) removes `user:password@` from the URL, and `sensitiveQueryParams` adds query keys to the built-in list. Set `false` to record raw URLs, which may leak secrets. Default: redaction enabled with the built-in list ### recordBody - boolean | object Record request and/or response body content on spans. `true` records both; `{ request?: boolean, response?: boolean }` records one side. Default: `false` ### headersToSpanAttributes - object Header names (case-insensitive) to record as span attributes, as `{ request?: string[], response?: string[] }`. `"*"` records every header, including sensitive ones. Default: no headers recorded --- Source: [plugins/overview/index.md](plugins/overview/index.md) # Plugin overview Elysia is designed to be modular and lightweight. Following the same idea as Arch Linux (btw, I use Arch): > Design decisions are made on a case-by-case basis through developer consensus This is to ensure developers end up with a performant web server they intend to create. By extension, Elysia includes pre-built common pattern plugins for convenient developer usage: ## Official plugins Here are some of the official plugins maintained by the Elysia team: ### [Bearer](/plugins/bearer) [Retrieve a Bearer token from request headers.](/plugins/bearer) ### [CORS](/plugins/cors) [Configure cross-origin resource sharing.](/plugins/cors) ### [Compress](/plugins/compress) [Compress HTTP responses with an encoding supported by the client.](/plugins/compress) ### [Cron](/plugins/cron) [Run scheduled jobs.](/plugins/cron) ### [Eden](/eden) [Use an end-to-end type-safe client for Elysia.](/eden) ### [GraphQL Apollo](/plugins/graphql-apollo) [Run Apollo GraphQL on Elysia.](/plugins/graphql-apollo) ### [GraphQL Yoga](/plugins/graphql-yoga) [Run GraphQL Yoga on Elysia.](/plugins/graphql-yoga) ### [HTML](/plugins/html) [Return HTML responses.](/plugins/html) ### [JWT](/plugins/jwt) [Sign and verify JSON Web Tokens.](/plugins/jwt) ### [OpenAPI](/plugins/openapi) [Generate OpenAPI documentation for an Elysia application.](/plugins/openapi) ### [OpenTelemetry](/plugins/opentelemetry) [Collect traces with OpenTelemetry.](/plugins/opentelemetry) ### [Server Timing](/plugins/server-timing) [Measure request lifecycle performance with Server-Timing response headers.](/plugins/server-timing) ### [Static](/plugins/static) [Serve static files and folders.](/plugins/static) Plugin versions Use plugin versions whose Elysia peer dependency matches your application. The Elysia 2 plugin branches use prerelease versions; older plugin releases can depend on removed Elysia 1 APIs. Community entries below are a directory, not a compatibility guarantee. ## Community plugins - [Create ElysiaJS](https://github.com/kravetsone/create-elysiajs) - scaffold your Elysia project with the environment easily (help with ORM, Linters and Plugins)! - [Lucia Auth](https://github.com/pilcrowOnPaper/lucia) - authentication that is simple and clean - [Elysia Clerk](https://github.com/wobsoriano/elysia-clerk) - unofficial Clerk authentication plugin - [Elysia Polyfills](https://github.com/bogeychan/elysia-polyfills) - run Elysia ecosystem on Node.js and Deno - [Vite server](https://github.com/kravetsone/elysia-vite-server) - plugin which starts and decorates the [`vite`](https://vitejs.dev/) dev server in `development` and in `production` modes and serves static files (if needed) - [Vite](https://github.com/timnghg/elysia-vite) - serve entry HTML file with Vite’s scripts injected - [Nuxt](https://github.com/trylovetom/elysiajs-nuxt) - easily integrate Elysia with Nuxt! - [Remix](https://github.com/kravetsone/elysia-remix) - use [Remix](https://remix.run/) with `HMR` support (powered by [`vite`](https://vitejs.dev/))! Closes a really long-standing plugin request [#12](https://github.com/elysiajs/elysia/issues/12) - [Sync](https://github.com/johnny-woodtke/elysiajs-sync) - a lightweight offline-first data synchronization framework powered by [Dexie.js](https://dexie.org/) - [Connect middleware](https://github.com/kravetsone/elysia-connect-middleware) - plugin which allows you to use [`express`](https://www.npmjs.com/package/express)/[`connect`](https://www.npmjs.com/package/connect) middleware directly in Elysia! - [Elysia HTTP Exception](https://github.com/codev911/elysia-http-exception) - Elysia plugin for HTTP 4xx/5xx error handling with structured exception classes - [Elysia Helmet](https://github.com/DevTobias/elysia-helmet) - secure Elysia apps with various HTTP headers - [Vite Plugin SSR](https://github.com/timnghg/elysia-vite-plugin-ssr) - Vite SSR plugin using Elysia server - [OAuth 2.0](https://github.com/kravetsone/elysia-oauth2) - a plugin for [OAuth 2.0](https://en.wikipedia.org/wiki/OAuth) Authorization Flow with more than **42** providers and **type-safety**! - [OAuth2](https://github.com/bogeychan/elysia-oauth2) - handle OAuth 2.0 authorization code flow - [OAuth2 Resource Server](https://github.com/ap-1/elysia-oauth2-resource-server) - a plugin for validating JWT tokens from OAuth2 providers against JWKS endpoints with support for issuer, audience, and scope verification - [Elysia OpenID Client](https://github.com/macropygia/elysia-openid-client) - OpenID client based on [openid-client](https://github.com/panva/node-openid-client) - [Rate Limit](https://github.com/rayriffy/elysia-rate-limit) - simple, lightweight rate limiter - [Elysia Nazli](https://github.com/esbuker/elysia-nazli) - Ergonomic, store-pluggable rate limiter with global and per-route rules. - [LLMs.txt](https://github.com/opuu/elysia-llms-txt) - generate `/llms.txt` for LLM-friendly docs from OpenAPI/Swagger - [Logysia](https://github.com/tristanisham/logysia) - classic logging middleware - [Logestic](https://github.com/cybercoder-naj/logestic) - an advanced and customizable logging library for ElysiaJS - [Logger](https://github.com/bogeychan/elysia-logger) - [pino](https://github.com/pinojs/pino)-based logging middleware - [Elysia Line](https://github.com/KrataiB/elysia-line) - LINE Messaging API and LINE Login integration for Elysia (wrapper around the official [@line/bot-sdk](https://github.com/line/line-bot-sdk-nodejs)) - [Elylog](https://github.com/eajr/elylog) - simple stdout logging library with some customization - [Logify for Elysia.js](https://github.com/0xrasla/logify) - a beautiful, fast, and type-safe logging middleware for Elysia.js applications - [Nice Logger](https://github.com/tanishqmanuja/nice-logger) - not the nicest, but a pretty nice and sweet logger for Elysia - [LogTape for Elysia](https://logtape.org/manual/integrations#elysia) - structured logging middleware with support for multiple sinks (transports) through [LogTape](https://logtape.org/) - [Sentry](https://github.com/johnny-woodtke/elysiajs-sentry) - capture traces and errors with this [Sentry](https://docs.sentry.io/) plugin - [Apitally](https://github.com/apitally/apitally-js) - capture metrics, logs, and traces in [Apitally](https://apitally.io/elysia), a simple API monitoring and analytics tool - [Elysia Lambda](https://github.com/TotalTechGeek/elysia-lambda) - deploy on AWS Lambda - [Decorators](https://github.com/gaurishhs/elysia-decorators) - use TypeScript decorators - [Elysia FSR](https://github.com/deadlinecode/elysia-fsr) - file-system routing with Eden-compatible type generation, monorepo/workspace support, and logical route grouping - [Autoload](https://github.com/kravetsone/elysia-autoload) - filesystem router based on a directory structure that generates types for [Eden](/eden) with [`Bun.build`](https://github.com/kravetsone/elysia-autoload?tab=readme-ov-file#bun-build-usage) support - [Msgpack](https://github.com/kravetsone/elysia-msgpack) - allows you to work with [MessagePack](https://msgpack.org) - [XML](https://github.com/kravetsone/elysia-xml) - allows you to work with XML - [Autoroutes](https://github.com/wobsoriano/elysia-autoroutes) - filesystem routes - [Group Router](https://github.com/itsyoboieltr/elysia-group-router) - filesystem and folder-based router for groups - [Basic Auth](https://github.com/itsyoboieltr/elysia-basic-auth) - basic HTTP authentication - [Body Limit](https://github.com/hexadecimal233/elysia-body-limit) - limit body sizes in Elysia - [ETag](https://github.com/bogeychan/elysia-etag) - automatic HTTP [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) generation - [CDN Cache](https://github.com/johnny-woodtke/elysiajs-cdn-cache) - Cache-Control plugin for Elysia - no more manually setting HTTP headers - [Basic Auth](https://github.com/eelkevdbos/elysia-basic-auth) - basic HTTP authentication (using `request` event) - [i18n](https://github.com/eelkevdbos/elysia-i18next) - [i18n](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n) wrapper based on [i18next](https://www.i18next.com/) - [Intlify](https://github.com/intlify/srvmid/blob/main/packages/elysia/README.md) - Internationalization server middleware & utilities - [Elysia Request ID](https://github.com/gtramontina/elysia-requestid) - add/forward request IDs (`X-Request-ID` or custom) - [Elysia HTMX](https://github.com/gtramontina/elysia-htmx) - context helpers for [HTMX](https://htmx.org/) - [Elysia HMR HTML](https://github.com/gtrabanco/elysia-hmr-html) - reload HTML files when changing any file in a directory - [Elysia Inject HTML](https://github.com/gtrabanco/elysia-inject-html) - inject HTML code in HTML files - [Elysia HTTP Error](https://github.com/yfrans/elysia-http-error) - return HTTP errors from Elysia handlers - [Elysia Http Status Code](https://github.com/sylvain12/elysia-http-status-code) - integrate HTTP status codes - [NoCache](https://github.com/gaurishhs/elysia-nocache) - disable caching - [Elysia Tailwind](https://github.com/gtramontina/elysia-tailwind) - compile [Tailwindcss](https://tailwindcss.com/) in a plugin. - [tailwind-serve](https://github.com/Moshyfawn/tailwind-serve) - compile and serve [Tailwind CSS v4](https://tailwindcss.com/) at runtime with no build step, works with any server framework - [Elysia Compression](https://github.com/gusb3ll/elysia-compression) - compress response - [Elysia IP](https://github.com/gaurishhs/elysia-ip) - get the IP Address - [OAuth2 Server](https://github.com/myazarc/elysia-oauth2-server) - develop an OAuth2 server with Elysia - [Elysia Flash Messages](https://github.com/gtramontina/elysia-flash-messages) - enable flash messages - [Elysia AuthKit](https://github.com/gtramontina/elysia-authkit) - unofficial [WorkOS’ AuthKit](https://www.authkit.com/) authentication - [Elysia Error Handler](https://github.com/gtramontina/elysia-error-handler) - simpler error handling - [Elysia env](https://github.com/yolk-oss/elysia-env) - typesafe environment variables with typebox - [Elysia Drizzle Schema](https://github.com/Edsol/elysia-drizzle-schema) - helps use Drizzle ORM schema inside Elysia OpenAPI model - [Unify-Elysia](https://github.com/qlaffont/unify-elysia) - unify error codes for Elysia - [Unify-Elysia-GQL](https://github.com/qlaffont/unify-elysia-gql) - unify error codes for Elysia GraphQL Server (Yoga & Apollo) - [Elysia Auth Drizzle](https://github.com/qlaffont/elysia-auth-drizzle) - library that handles authentication with JWT (Header/Cookie/QueryParam) - [graceful-server-elysia](https://github.com/qlaffont/graceful-server-elysia) - library inspired by [graceful-server](https://github.com/gquittet/graceful-server) - [Logixlysia](https://github.com/PunGrumpy/logixlysia) - a beautiful and simple logging middleware for ElysiaJS with colors and timestamps. - [Elysia Fault](https://github.com/vitorpldev/elysia-fault) - a simple and customizable error handling middleware with the possibility of creating your own HTTP errors - [Elysia Compress](https://github.com/vermaysha/elysia-compress) - ElysiaJS plugin to compress responses inspired by [@fastify/compress](https://github.com/fastify/fastify-compress) - [@labzzhq/compressor](https://github.com/labzzhq/compressor/) - Compact Brilliance, Expansive Results: HTTP Compressor for Elysia and Bunnyhop with gzip, deflate and brotli support. - [Elysia Accepts](https://github.com/morigs/elysia-accepts) - Elysia plugin for accept headers parsing and content negotiation - [Elysia Compression](https://github.com/chneau/elysia-compression) - Elysia plugin for compressing responses - [Elysia Logger](https://github.com/chneau/elysia-logger) - Elysia plugin for logging HTTP requests and responses inspired by [hono/logger](https://hono.dev/docs/middleware/builtin/logger) - [Elysia CQRS](https://github.com/jassix/elysia-cqrs) - Elysia plugin for CQRS pattern - [Elysia Supabase](https://github.com/mastermakrela/elysia-supabase) - Seamlessly integrate [Supabase](https://supabase.com/) authentication and database functionality into Elysia, allowing easy access to authenticated user data and Supabase client instance. Especially useful for [Edge Functions](https://supabase.com/docs/guides/functions). - [Elysia XSS](https://www.npmjs.com/package/elysia-xss) - a plugin for Elysia.js that provides XSS (Cross-Site Scripting) protection by sanitizing request body data. - [Elysiajs Helmet](https://www.npmjs.com/package/elysiajs-helmet) - a comprehensive security middleware for Elysia.js applications that helps secure your apps by setting various HTTP headers. - [Decorators for Elysia.js](https://github.com/Ateeb-Khan-97/better-elysia) - seamlessly develop and integrate APIs, WebSocket, and Streaming APIs with this small library. - [Elysia Protobuf](https://github.com/ilyhalight/elysia-protobuf) - protobuf support for Elysia. - [Elysia Prometheus](https://github.com/m1handr/elysia-prometheus) - Elysia plugin for exposing HTTP metrics for Prometheus. - [Elysia Remote DTS](https://github.com/rayriffy/elysia-remote-dts) - A plugin that provides .d.ts types remotely for Eden Treaty to consume. - [Cap Checkpoint plugin for Elysia](https://capjs.js.org/guide/middleware/elysia.html) - Cloudflare-like middleware for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW. - [Elysia Background](https://github.com/staciax/elysia-background) - A background task processing plugin for Elysia.js - [Elysia External Session - TS only GitHub package](https://github.com/extend-therapy/elysia-external-session) - A sessions package for Redis and SQLite that is extensible and uses the current Elysia architecture. - [@fedify/elysia](https://github.com/fedify-dev/fedify/tree/main/packages/elysia) - A plugin that provides seamless integration with [Fedify](https://fedify.dev/), the ActivityPub server framework. - [elysia-healthcheck](https://github.com/iam-medvedev/elysia-healthcheck) - Healthcheck plugin for Elysia.js - [elysia-csrf](https://github.com/lauhon/elysia-csrf) - A CSRF plugin, ported from [express-csrf](https://github.com/expressjs/csurf) - [elysia-local-https](https://github.com/mrtcmn/elysia-local-https) - Automatic local HTTPS for Elysia — certs generated, managed, and refreshed in one line. - [elysia-circuit-breaker](https://github.com/camargo-leonardo/elysia-circuit-breaker) - A Circuit Breaker plugin that brings fault tolerance to Elysia.js. - [Eden TanStack Query](https://github.com/xkelxmc/eden-tanstack-query) - type-safe TanStack Query integration for Eden, like @trpc/react-query but for Elysia - [Socket.IO Proxy](https://www.npmjs.com/package/@synchjs/elysocket) - Socket.IO Proxy for Elysia. - [ModernCSRF](https://github.com/auto-medica-labs/modern-csrf) - A lightweight, token-free CSRF protection middleware for ElysiaJS - [elysia-wide-event](https://github.com/choiexe1/elysia-wide-event) - Wide event logging plugin for structured request-level observability. - [elysia-beta-headers](https://github.com/P0u4a/elysia-beta-headers) - Elysia plugin for gating your app’s beta/experimental features via type-safe API headers - [elysia-better-session](https://github.com/0x0arash/elysia-better-session) - Elysia plugin for server-side session management with different store adapters. ## Complementary projects - [prismabox](https://github.com/m1212e/prismabox) - Generator for TypeBox schemas based on your database models, works well with Elysia ## Project templates - [ElysiaTemplate](https://github.com/QLing-yes/ElysiaTemplate) - MVC backend, auto route & middleware, more coming. *** If you have a plugin written for Elysia, feel free to add your plugin to the list by [editing the plugin list on GitHub](https://github.com/elysiajs/documentation/edit/main/docs/plugins/overview.md) --- Source: [plugins/server-timing/index.md](plugins/server-timing/index.md) # Server Timing plugin This plugin adds support for auditing performance bottlenecks with the Server Timing API Install with: Terminal window ```bash bun add @elysia/server-timing@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { serverTiming } from '@elysia/server-timing' new Elysia() .use(serverTiming()) .get('/', () => 'hello') .listen(3000) ``` Server Timing will then append the ‘Server-Timing’ header with log duration, function name, and detail for each life-cycle function. To inspect, open browser developer tools > Network > \[Request made through Elysia server] > Timing. ![Developer tools showing Server Timing](/assets/server-timing.webp) Use the timing entries to find which lifecycle stages take the most time. ## Config Below is a config which is accepted by the plugin ### enabled Default: `NODE_ENV !== 'production'` Determine whether or not Server Timing should be enabled ### allow Default: `undefined` A condition for whether server timing should be logged ### trace Default: every event enabled Allow Server Timing to log specified life-cycle events: Trace accepts an object of booleans, each defaulting to `true`; set one to `false` to omit it: - request: capture duration from request - parse: capture duration from parse - transform: capture duration from transform - beforeHandle: capture duration from beforeHandle - handle: capture duration from the handle - afterHandle: capture duration from afterHandle - error: capture duration from error - mapResponse: capture duration from mapResponse - total: capture total duration from start to finish ## Pattern Below you can find the common patterns to use the plugin. - [Allow Condition](#allow-condition) ## Allow condition You may disable Server Timing on specific routes via the `allow` property ```ts import { Elysia } from 'elysia' import { serverTiming } from '@elysia/server-timing' new Elysia() .use( serverTiming({ allow: ({ request }) => { return new URL(request.url).pathname !== '/no-trace' } }) ) ``` --- Source: [plugins/static/index.md](plugins/static/index.md) # Static plugin This plugin can serve static files/folders for an Elysia server Install with: Terminal window ```bash bun add @elysia/static@next ``` Then use it: ```ts import { Elysia } from 'elysia' import { staticPlugin } from '@elysia/static' new Elysia() .use(staticPlugin()) .listen(3000) ``` By default, the static plugin’s folder is `public`, and it is registered with the `/public` prefix. Suppose your project structure is: ```text | - src | - index.ts | - public | - takodachi.png | - nested | - takodachi.png ``` The available paths will become: - /public/takodachi.png - /public/nested/takodachi.png ## Config Below is a config which is accepted by the plugin ### assets Default: `"public"` Path to the folder to expose as static ### prefix Default: `"/public"` Path prefix to register public files ### ignorePatterns Default: `['.DS_Store', '.git', '.env']` List of files to ignore from serving as static files Caution A custom list replaces the default. Include `.env` and `.git` again, or they will be served. ### staticLimit Default: `1024` When [alwaysStatic](#alwaysstatic) is enabled, the static plugin registers up to this many paths to the Router with a static name. Files past the limit are resolved lazily on request to reduce memory usage. Trade off memory against performance. ### alwaysStatic Default: `process.env.NODE_ENV === 'production'` If set to true, static file paths are registered to the Router at startup, up to [staticLimit](#staticlimit). Otherwise, files are resolved lazily on request. ### headers Default: `{}` Set response headers of files ### indexHTML Default: `true` If enabled, directory requests can serve the `index.html` file inside that directory. This is not a catch-all single-page application fallback for arbitrary missing paths. ## Pattern Below you can find the common patterns to use the plugin. - [Single File](#single-file) ## Single file Suppose you want to return just a single file. You can use `file` instead of using the static plugin ```ts import { Elysia, file } from 'elysia' new Elysia() .get('/file', file('public/takodachi.png')) ``` --- Source: [quick-start/index.md](quick-start/index.md) # Quick start Elysia is a TypeScript backend framework optimized for Bun. You can also run it on Node.js or any runtime that supports Web Standard Request. ## Bun Install Bun using the command for your operating system. Terminal window ```bash curl -fsSL https://bun.com/install | bash ``` Terminal window ```powershell powershell -c "irm bun.sh/install.ps1 | iex" ``` Open a new terminal and check the installation: Terminal window ```bash bun --version ``` ### Automatic installation You can create a project using `bun create elysia`. The template use `latest` but we want to use the `rc` version of Elysia v2 instead. Terminal window ```bash bun create elysia cd app bun add elysia@rc ``` Start the server: Terminal window ```bash bun run dev ``` Open [localhost:3000](http://localhost:3000). You should see `Hello Elysia`. Keep the server running The `--watch` flag restarts your server when files change. `dev` is a script you add to the project; it isn’t a command supplied by the Elysia package itself. ### Manual installation If you’d rather start with your own files, create an empty project and install Elysia: Terminal window ```bash mkdir app cd app bun init -y bun add elysia@rc bun add -d @types/bun typescript ``` Create `src/index.ts`: ```ts import { Elysia } from 'elysia' new Elysia() .get('/', () => 'Hello Elysia') .listen(3000, ({ hostname, port }) => { console.log(`Elysia is running at ${hostname}:${port}`) }) ``` Merge these scripts into your `package.json`: ```json { "scripts": { "dev": "bun --watch src/index.ts", "build": "bun build src/index.ts --target bun --outdir ./dist", "start": "NODE_ENV=production bun dist/index.js", "test": "bun test" } } ``` | Script | What it does | | --------------- | --------------------------------------------------- | | `bun run dev` | Starts the server and reloads it when code changes. | | `bun run build` | Bundles the server for production. | | `bun run start` | Runs the previously built bundle. | | `bun run test` | Runs tests with Bun’s test runner. | Keep `strict` enabled in `tsconfig.json` so Elysia can infer your types correctly: ```json { "compilerOptions": { "strict": true } } ``` Merge this option into the configuration created by `bun init`; keep its other settings. Run `bun run dev` and open [localhost:3000](http://localhost:3000). ## Node.js Use a supported Node.js release with the Node adapter. Elysia 2 requires Node `22.18` or later; choose a maintained LTS release from the [Node.js downloads page](https://nodejs.org/en/download). If you already use a system package manager, these are the corresponding installation commands: Terminal window ```bash brew install node ``` Terminal window ```powershell choco install nodejs ``` Terminal window ```bash sudo apt install nodejs npm ``` Terminal window ```bash sudo pacman -S nodejs npm ``` Check `node --version` afterward. A distribution’s default package may be older than Elysia’s requirement; use an installer for a supported release if needed. ### TypeScript setup Start in a new project directory with a `package.json` (for example, run `npm init -y`). Choose the commands for your package manager: Terminal window ```bash bun add elysia@rc @elysia/node@rc bun add -d tsx @types/node typescript ``` Terminal window ```bash pnpm add elysia@rc @elysia/node@rc typebox exact-mirror openapi-types pnpm add -D tsx @types/node typescript ``` Terminal window ```bash npm install elysia@rc @elysia/node@rc npm install --save-dev tsx @types/node typescript ``` Terminal window ```bash yarn add elysia@rc @elysia/node@rc typebox exact-mirror openapi-types yarn add -D tsx @types/node typescript ``` The pnpm and Yarn commands list Elysia’s required runtime peers explicitly, so they also work when automatic peer installation is disabled. Elysia v2 uses `typebox`, not `@sinclair/typebox`. `tsx` runs TypeScript during development and provides watch mode. TypeScript itself checks your types and creates the production JavaScript. Create `src/index.ts`: ```ts import { Elysia } from 'elysia' import { node } from '@elysia/node' new Elysia({ adapter: node() }) .get('/', () => 'Hello Elysia') .listen(3000, ({ hostname, port }) => { console.log(`Elysia is running at ${hostname}:${port}`) }) ``` Set the package to use ES modules and add these scripts: ```json { "type": "module", "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc -p tsconfig.json", "start": "node dist/index.js" } } ``` Create a TypeScript configuration with `npx tsc --init`, then configure these options in `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "types": ["node"], "rootDir": "src", "outDir": "dist", "strict": true, "skipLibCheck": true }, "include": ["src/**/*.ts"] } ``` The build script reads this configuration, including `strict` and the output directory. Use `.js` extensions when importing your own modules with NodeNext, even from TypeScript source files. Run the development server: Terminal window ```bash npm run dev ``` You can use `bun run dev`, `pnpm dev`, or `yarn dev` instead. Open [localhost:3000](http://localhost:3000) to see `Hello Elysia`. | Script | What it does | | ------- | --------------------------------------------------- | | `dev` | Runs TypeScript with automatic restarts on changes. | | `build` | Checks types and emits JavaScript into `dist`. | | `start` | Runs the built JavaScript with Node.js. | Set `NODE_ENV=production` in your deployment environment before running `start`. On a POSIX shell, you can use `NODE_ENV=production npm run start`; in PowerShell, set `$env:NODE_ENV = 'production'` first. ### JavaScript setup You can use JavaScript too. The request handling is the same; TypeScript adds schema inference and end-to-end type checking while you’re writing code. Choose your package manager: **Bun** Terminal window ```bash bun add elysia@rc @elysia/node@rc ``` **pnpm** Terminal window ```bash pnpm add elysia@rc @elysia/node@rc typebox exact-mirror openapi-types ``` **npm** Terminal window ```bash npm install elysia@rc @elysia/node@rc ``` **Yarn** Terminal window ```bash yarn add elysia@rc @elysia/node@rc typebox exact-mirror openapi-types ``` Create `src/index.js`: ```js import { Elysia } from 'elysia' import { node } from '@elysia/node' new Elysia({ adapter: node() }) .get('/', () => 'Hello Elysia') .listen(3000, ({ hostname, port }) => { console.log(`Elysia is running at ${hostname}:${port}`) }) ``` Add ES module support and the following scripts to `package.json`: ```json { "type": "module", "scripts": { "dev": "node --watch src/index.js", "start": "node src/index.js" } } ``` Run `npm run dev` to develop with automatic restarts. Use `npm run start` in production with `NODE_ENV=production` set in the environment. You can add types later JavaScript works without a compile step. If you want stronger editor checks, start with the TypeScript setup above; both examples expose the same route. ## Web Standard Elysia exposes a request handler that takes a Web Standard `Request` and produces a `Response`. Use it with a compatible framework or server runtime instead of opening a separate listener. Install Elysia with your package manager: Terminal window ```bash bun add elysia@rc ``` Terminal window ```bash pnpm add elysia@rc typebox exact-mirror openapi-types ``` Terminal window ```bash npm install elysia@rc ``` Terminal window ```bash yarn add elysia@rc typebox exact-mirror openapi-types ``` Choose an integration to see how its runtime connects to Elysia: ### [Cloudflare Workers](/integrations/cloudflare-worker) [Run an Elysia API on Cloudflare Workers.](/integrations/cloudflare-worker) ### [Next.js](/integrations/nextjs) [Use Elysia in Next.js route handlers.](/integrations/nextjs) ### [Expo](/integrations/expo) [Use Elysia in Expo API routes.](/integrations/expo) ### [Astro](/integrations/astro) [Use Elysia in Astro API routes.](/integrations/astro) ### [Nuxt](/integrations/nuxt) [Use Elysia in Nuxt server routes.](/integrations/nuxt) ### [SvelteKit](/integrations/sveltekit) [Use Elysia in SvelteKit API routes.](/integrations/sveltekit) ### [TanStack Start](/integrations/tanstack-start) [Use Elysia in TanStack Start server routes.](/integrations/tanstack-start) ### Not on the list? Export `app.fetch` and pass incoming requests to it using your runtime’s server API: ```ts import { Elysia } from 'elysia' const app = new Elysia() .get('/', () => 'Hello Elysia') export default app.fetch ``` Don’t call `.listen()` when the host framework owns the server. A direct request to this handler returns the same greeting: ```ts const response = await app.fetch(new Request('http://localhost/')) console.log(await response.text()) // Hello Elysia ``` Request and response compatibility is the starting point; use the integration guide for runtime-specific setup and supported features. --- Source: [reference/classes/compiled/index.md](reference/classes/compiled/index.md) # Compiled ## Members ### Accessors | Accessor | Description | | ------------------------------------------------------- | ----------- | | [reconstruct](/reference/classes/compiled/#reconstruct) | - | ### Methods | Method | Description | | ----------------------------------------------------------------- | ----------- | | [claim](/reference/classes/compiled/#claim) | - | | [getHandler](/reference/classes/compiled/#gethandler) | - | | [getPlanRebuilder](/reference/classes/compiled/#getplanrebuilder) | - | | [getValidator](/reference/classes/compiled/#getvalidator) | - | | [hasProgram](/reference/classes/compiled/#hasprogram) | - | | [hasValidator](/reference/classes/compiled/#hasvalidator) | - | | [register](/reference/classes/compiled/#register) | - | | [release](/reference/classes/compiled/#release) | - | Defined in: elysia/dist/compile/aot.d.ts:131 ## Constructors ### Constructor ```ts new Compiled(): Compiled; ``` #### Returns `Compiled` ## Accessors ### reconstruct #### Set Signature ```ts set static reconstruct(impl): void; ``` Defined in: elysia/dist/compile/aot.d.ts:134 ##### Parameters | Parameter | Type | | --------- | ----------------- | | `impl` | `ReconstructImpl` | ##### Returns `void` ## Methods ### claim() ```ts static claim( id, fingerprint, key? ): boolean; ``` Defined in: elysia/dist/compile/aot.d.ts:133 #### Parameters | Parameter | Type | | ------------- | --------------------------------------------------------- | | `id` | [`ProgramId`](/reference/interfaces/programid/) | | `fingerprint` | [`AotFingerprint`](/reference/interfaces/aotfingerprint/) | | `key?` | `string` | #### Returns `boolean` *** ### getHandler() ```ts static getHandler( id, method, path ): FrozenHandler; ``` Defined in: elysia/dist/compile/aot.d.ts:135 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `id` | [`ProgramId`](/reference/interfaces/programid/) | | `method` | `string` | | `path` | `string` | #### Returns `FrozenHandler` *** ### getPlanRebuilder() ```ts static getPlanRebuilder(id?): (original, plan) => any; ``` Defined in: elysia/dist/compile/aot.d.ts:139 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `id?` | [`ProgramId`](/reference/interfaces/programid/) | #### Returns (`original`, `plan`) => `any` *** ### getValidator() ```ts static getValidator( method, path, slot, id? ): FrozenValidator; ``` Defined in: elysia/dist/compile/aot.d.ts:137 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `method` | `string` | | `path` | `string` | | `slot` | `ValidatorSlot` | | `id?` | [`ProgramId`](/reference/interfaces/programid/) | #### Returns `FrozenValidator` *** ### hasProgram() ```ts static hasProgram(id): boolean; ``` Defined in: elysia/dist/compile/aot.d.ts:136 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `id` | [`ProgramId`](/reference/interfaces/programid/) | #### Returns `boolean` *** ### hasValidator() ```ts static hasValidator( method, path, slot, id? ): boolean; ``` Defined in: elysia/dist/compile/aot.d.ts:138 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `method` | `string` | | `path` | `string` | | `slot` | `ValidatorSlot` | | `id?` | [`ProgramId`](/reference/interfaces/programid/) | #### Returns `boolean` *** ### register() ```ts static register(manifest, key?): void; ``` Defined in: elysia/dist/compile/aot.d.ts:132 #### Parameters | Parameter | Type | | ---------- | ----------------------------- | | `manifest` | `CompiledProgramRegistration` | | `key?` | `string` | #### Returns `void` *** ### release() ```ts static release(id): void; ``` Defined in: elysia/dist/compile/aot.d.ts:140 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------- | | `id` | [`ProgramId`](/reference/interfaces/programid/) | #### Returns `void` --- Source: [reference/classes/elysia/index.md](reference/classes/elysia/index.md) # Elysia ## Members ### Properties | Property | Description | | ------------------------------------------- | ----------- | | [server](/reference/classes/elysia/#server) | - | ### Accessors | Accessor | Description | | --------------------------------------------- | ----------- | | [fetch](/reference/classes/elysia/#fetch) | - | | [handle](/reference/classes/elysia/#handle) | - | | [history](/reference/classes/elysia/#history) | - | | [models](/reference/classes/elysia/#models) | - | | [modules](/reference/classes/elysia/#modules) | - | | [pending](/reference/classes/elysia/#pending) | - | | [routes](/reference/classes/elysia/#routes) | - | ### Methods | Method | Description | | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | [all](/reference/classes/elysia/#all) | - | | [as](/reference/classes/elysia/#as) | - | | [compile](/reference/classes/elysia/#compile) | Force all route handlers to compile immediately (sets `precompile: true` on the config and triggers a fresh build of the fetch handler). | | [decorate](/reference/classes/elysia/#decorate) | Define custom property to `Context` accessible for all handler. | | [delete](/reference/classes/elysia/#delete) | Register handler for path with method \[DELETE] | | [derive](/reference/classes/elysia/#derive) | - | | [get](/reference/classes/elysia/#get) | Register handler for path with method \[GET] | | [group](/reference/classes/elysia/#group) | - | | [guard](/reference/classes/elysia/#guard) | Apply a hook and schema to every route defined after it on this instance. | | [handler](/reference/classes/elysia/#handler) | - | | [has](/reference/classes/elysia/#has) | - | | [head](/reference/classes/elysia/#head) | Register handler for path with method \[HEAD] | | [headers](/reference/classes/elysia/#headers) | - | | [listen](/reference/classes/elysia/#listen) | - | | [macro](/reference/classes/elysia/#macro) | Declare a custom route property: applying it on a route or guard folds the definition’s schema, lifecycle hooks, and `derive` result into that route | | [mapDerive](/reference/classes/elysia/#mapderive) | - | | [mapResponse](/reference/classes/elysia/#mapresponse) | - | | [method](/reference/classes/elysia/#method) | Register handler for path with custom method | | [model](/reference/classes/elysia/#model) | - | | [mount](/reference/classes/elysia/#mount) | - | | [onAfterHandle](/reference/classes/elysia/#onafterhandle) | - | | [onAfterResponse](/reference/classes/elysia/#onafterresponse) | - | | [onBeforeHandle](/reference/classes/elysia/#onbeforehandle) | - | | [onError](/reference/classes/elysia/#onerror) | - | | [onParse](/reference/classes/elysia/#onparse) | - | | [onRequest](/reference/classes/elysia/#onrequest) | - | | [onStart](/reference/classes/elysia/#onstart) | Called after server is ready for serving | | [onStop](/reference/classes/elysia/#onstop) | Called after server stop serving request | | [onTransform](/reference/classes/elysia/#ontransform) | - | | [options](/reference/classes/elysia/#options) | Register handler for path with method \[OPTIONS] | | [parser](/reference/classes/elysia/#parser) | - | | [patch](/reference/classes/elysia/#patch) | Register handler for path with method \[PATCH] | | [post](/reference/classes/elysia/#post) | Register handler for path with method \[POST] | | [put](/reference/classes/elysia/#put) | Register handler for path with method \[PUT] | | [query](/reference/classes/elysia/#query) | Register handler for path with method \[QUERY] | | [Ref](/reference/classes/elysia/#ref) | - | | [state](/reference/classes/elysia/#state) | Assign global mutable state accessible for all handler. | | [stop](/reference/classes/elysia/#stop) | Stop the underlying server (if any), then run every `onStop` handler | | [trace](/reference/classes/elysia/#trace) | - | | [use](/reference/classes/elysia/#use) | Merge a plugin instance | | [wrap](/reference/classes/elysia/#wrap) | Add a higher order function over Elysia.fetch | | [ws](/reference/classes/elysia/#ws) | Register a WebSocket route as `(path, options)`, `(path, handler)`, or `(path, options, handler)`. Requires `.use(websocket())` from `elysia/websocket`. | Defined in: elysia/dist/base.d.ts:44 ## Type Parameters | Type Parameter | Default type | | ---------------------------------------- | ------------------------------------------------------------- | | `BasePath` *extends* `string` | `""` | | `Scope` *extends* `EventScope` | `"local"` | | `Singleton` *extends* `SingletonBase` | [`DefaultSingleton`](/reference/interfaces/defaultsingleton/) | | `Definitions` *extends* `DefinitionBase` | `object` | | `Metadata` *extends* `MetadataBase` | [`DefaultMetadata`](/reference/interfaces/defaultmetadata/) | | `Routes` *extends* `RouteBase` | `object` | | `Ephemeral` *extends* `EphemeralType` | [`DefaultEphemeral`](/reference/interfaces/defaultephemeral/) | | `Volatile` *extends* `EphemeralType` | [`DefaultEphemeral`](/reference/interfaces/defaultephemeral/) | ## Constructors ### Constructor ```ts new Elysia(config?): Elysia; ``` Defined in: elysia/dist/base.d.ts:127 #### Parameters | Parameter | Type | | --------- | ----------------------------------- | | `config?` | `ElysiaConfig`<`BasePath`, `Scope`> | #### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ## Properties ### server? ```ts optional server?: Server; ``` Defined in: elysia/dist/base.d.ts:94 ## Accessors ### fetch #### Get Signature ```ts get fetch(): (request) => MaybePromise; ``` Defined in: elysia/dist/base.d.ts:1047 ##### Returns (`request`) => `MaybePromise`<`Response`> *** ### handle #### Get Signature ```ts get handle(): (url, options?) => Promise; ``` Defined in: elysia/dist/base.d.ts:1049 ##### Returns (`url`, `options?`) => `Promise`<`Response`> *** ### history #### Get Signature ```ts get history(): readonly HistoryEntry[]; ``` Defined in: elysia/dist/base.d.ts:99 ##### Returns readonly `HistoryEntry`\[] *** ### models #### Get Signature ```ts get models(): Definitions["typebox"]; ``` Defined in: elysia/dist/base.d.ts:830 Registered reusable models (via `.model()`), keyed by name. ##### Returns `Definitions`\[`"typebox"`] *** ### modules #### Get Signature ```ts get modules(): Promise; ``` Defined in: elysia/dist/base.d.ts:813 ##### Returns `Promise`<`void`> *** ### pending #### Get Signature ```ts get pending(): boolean; ``` Defined in: elysia/dist/base.d.ts:814 ##### Returns `boolean` *** ### routes #### Get Signature ```ts get routes(): PublicRoute[]; ``` Defined in: elysia/dist/base.d.ts:134 Every registered route with its merged hooks. Development shares a frozen cache. Production rebuilds the list on every read so its metadata can be released after startup. ##### Returns `PublicRoute`\[] ## Methods ### all() #### Call Signature ```ts all( path, hook, fn ): this; ``` Defined in: elysia/dist/base.d.ts:1011 ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | - | | `Schema` *extends* `IntersectIfObjectSchema`<`object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`], `""`, `false`>, `""`, `false`> ? [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>> : `object`, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `this` #### Call Signature ```ts all(path, fn): this; ``` Defined in: elysia/dist/base.d.ts:1014 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`], `""`, `false`>, `""`, `false`> ? [`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>> : `object`, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `this` *** ### as() #### Call Signature ```ts as(type): Elysia; schema: MergeSchema; schemas: Ephemeral["schemas"] & Volatile["schemas"]; }, DefaultEphemeral>; ``` Defined in: elysia/dist/base.d.ts:436 ##### Parameters | Parameter | Type | | --------- | ---------- | | `type` | `"plugin"` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`] & `Volatile`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `...Volatile["error"][]`]; `response`: `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>; `schema`: `MergeSchema`<`Volatile`\[`"schema"`], `Ephemeral`\[`"schema"`]>; `schemas`: `Ephemeral`\[`"schemas"`] & `Volatile`\[`"schemas"`]; }, [`DefaultEphemeral`](/reference/interfaces/defaultephemeral/)> #### Call Signature ```ts as(type): Elysia>; schema: MergeSchema, Metadata["schema"]>; schemas: Metadata["schemas"] & Ephemeral["schemas"] & Volatile["schemas"]; }, Routes, DefaultEphemeral, DefaultEphemeral>; ``` Defined in: elysia/dist/base.d.ts:443 ##### Parameters | Parameter | Type | | --------- | ---------- | | `type` | `"global"` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `Ephemeral`\[`"derive"`] & `Volatile`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, { `error`: \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`]; `typebox`: `Definitions`\[`"typebox"`]; }, { `macro`: `Metadata`\[`"macro"`]; `macroFn`: `Metadata`\[`"macroFn"`]; `parser`: `Metadata`\[`"parser"`]; `response`: `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>; `schema`: `MergeSchema`<`MergeSchema`<`Volatile`\[`"schema"`], `Ephemeral`\[`"schema"`]>, `Metadata`\[`"schema"`]>; `schemas`: `Metadata`\[`"schemas"`] & `Ephemeral`\[`"schemas"`] & `Volatile`\[`"schemas"`]; }, `Routes`, [`DefaultEphemeral`](/reference/interfaces/defaultephemeral/), [`DefaultEphemeral`](/reference/interfaces/defaultephemeral/)> *** ### compile() ```ts compile(): this; ``` Defined in: elysia/dist/base.d.ts:1043 Force all route handlers to compile immediately (sets `precompile: true` on the config and triggers a fresh build of the fetch handler). #### Returns `this` *** ### decorate() #### Call Signature ```ts decorate(name, value): Elysia; ``` Defined in: elysia/dist/base.d.ts:147 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------- | | `Name` *extends* `string` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------- | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Singleton\[“decorator”] & { \[k in string]: Value })\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate(decorators): Elysia; ``` Defined in: elysia/dist/base.d.ts:152 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------------- | | `NewDecorators` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `decorators` | `NewDecorators` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Singleton\[“decorator”] & NewDecorators)\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate(mapper): Elysia; ``` Defined in: elysia/dist/base.d.ts:157 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------------- | | `NewDecorators` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `mapper` | (`decorators`) => `NewDecorators` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `NewDecorators`; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate( type, name, value ): Elysia; ``` Defined in: elysia/dist/base.d.ts:162 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------- | | `Name` *extends* `string` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ---------- | | `type` | `"append"` | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Singleton\[“decorator”] & { \[k in string]: Value })\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate( type, name, value ): Elysia & { [k in string]: Value })[K] }; derive: Singleton["derive"]; store: Singleton["store"]; }, Definitions, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:167 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------- | | `Name` *extends* `string` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------------ | | `type` | `"override"` | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Omit\ & { \[k in string]: Value })\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate(type, decorators): Elysia; ``` Defined in: elysia/dist/base.d.ts:172 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------------- | | `NewDecorators` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `type` | `"append"` | | `decorators` | `NewDecorators` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Singleton\[“decorator”] & NewDecorators)\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` #### Call Signature ```ts decorate(type, decorators): Elysia & NewDecorators)[K] }; derive: Singleton["derive"]; store: Singleton["store"]; }, Definitions, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:177 Define custom property to `Context` accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------------- | | `NewDecorators` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `type` | `"override"` | | `decorators` | `NewDecorators` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: { \[K in string | number | symbol]: (Omit\ & NewDecorators)\[K] }; `derive`: `Singleton`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .decorate('getDate', () => Date.now()) .get('/', ({ getDate }) => getDate()) ``` *** ### delete() #### Call Signature ```ts delete( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:926 Register handler for path with method \[DELETE] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"delete"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .delete('/', () => 'hi') .delete('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts delete(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:929 Register handler for path with method \[DELETE] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"delete"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .delete('/', () => 'hi') .delete('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### derive() #### Call Signature ```ts derive(transform): LocalHookReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:278 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts derive(scope, transform): LocalHookReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:279 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"local"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts derive(scope, transform): PluginHookReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:280 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"plugin"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `PluginHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts derive(scope, transform): GlobalHookReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:281 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"global"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `GlobalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts derive(options, transform): ScopedHookReturn, BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, DeriveResponse, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:283 Deprecated 1.x syntax, use `.derive('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ------------ | ------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `ScopedHookReturn`<`LegacyScope`<`As`>, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts derive(scope, transform): ScopedHookReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:286 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HookScope` *extends* `EventScope` | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `HookScope` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `ScopedHookReturn`<`HookScope`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> *** ### get() #### Call Signature ```ts get( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:846 Register handler for path with method \[GET] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) \| `MaybePromise`<`InlineResponse`> | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"get"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', () => 'hi') .get('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts get(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:849 Register handler for path with method \[GET] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"get"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', () => 'hi') .get('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### group() #### Call Signature ```ts group(prefix, run): Elysia; ``` Defined in: elysia/dist/base.d.ts:602 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------- | | `Prefix` *extends* `string` | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | --------- | ------------------------ | | `prefix` | `Prefix` | | `run` | (`group`) => `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes` & `NewElysia`\[`"~Routes"`], `Ephemeral`, `Volatile`> #### Call Signature ```ts group( prefix, schema, run ): Elysia; ``` Defined in: elysia/dist/base.d.ts:610 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Prefix` *extends* `string` | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Prefix`>> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\> : `object` *extends* `Input` ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : `object` *extends* `Input` ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `prefix` | `Prefix` | | `schema` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | | `run` | (`group`) => `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes` & `NewElysia`\[`"~Routes"`], `Ephemeral`, `Volatile`> #### Call Signature ```ts group( prefix, schema, run ): Elysia; ``` Defined in: elysia/dist/base.d.ts:626 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Prefix` *extends* `string` | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Prefix`>> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\> : `object` *extends* `Input` ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : `object` *extends* `Input` ? `ResolvePath`<`JoinPath`<`BasePath`, `Prefix`>> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prefix` | `Prefix` | | `schema` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | | `run` | (`group`) => `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes` & `NewElysia`\[`"~Routes"`], `Ephemeral`, `Volatile`> *** ### guard() #### Call Signature ```ts guard(hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: Volatile["schema"]; schemas: Volatile["schemas"] & UnwrapRoute & MacroContext; }>; ``` Defined in: elysia/dist/base.d.ts:470 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Volatile`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`> & `MacroContext`; }> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: object extends Pick>> ? Volatile["schema"] : MergeSchema, Volatile["schema"]>; schemas: Volatile["schemas"] & MacroContext; }>; ``` Defined in: elysia/dist/base.d.ts:481 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Volatile`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `object` *extends* `Pick`<`Input`, `Extract`\>> ? `Volatile`\[`"schema"`] : `MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`>, `Volatile`\[`"schema"`]>; `schemas`: `Volatile`\[`"schemas"`] & `MacroContext`; }> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(hook, run): Elysia; ``` Defined in: elysia/dist/base.d.ts:490 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | | `run` | (`group`) => `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes` & `NewElysia`\[`"~Routes"`], `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(hook, run): Elysia; ``` Defined in: elysia/dist/base.d.ts:506 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | | `run` | (`group`) => `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes` & `NewElysia`\[`"~Routes"`], `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: Volatile["schema"]; schemas: Volatile["schemas"] & UnwrapRoute & MacroContext; }>; ``` Defined in: elysia/dist/base.d.ts:532 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `scope` | `"local"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Volatile`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`> & `MacroContext`; }> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: object extends Pick>> ? Volatile["schema"] : MergeSchema, Volatile["schema"]>; schemas: Volatile["schemas"] & MacroContext; }>; ``` Defined in: elysia/dist/base.d.ts:543 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | `"local"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Volatile`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `object` *extends* `Pick`<`Input`, `Extract`\>> ? `Volatile`\[`"schema"`] : `MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`>, `Volatile`\[`"schema"`]>; `schemas`: `Volatile`\[`"schemas"`] & `MacroContext`; }> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: Ephemeral["schema"]; schemas: Ephemeral["schemas"] & UnwrapRoute & MacroContext; }, Volatile>; ``` Defined in: elysia/dist/base.d.ts:552 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `scope` | `"plugin"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Ephemeral`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `Ephemeral`\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`] & [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`> & `MacroContext`; }, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: object extends Pick>> ? Ephemeral["schema"] : MergeSchema, Ephemeral["schema"]>; schemas: Ephemeral["schemas"] & MacroContext; }, Volatile>; ``` Defined in: elysia/dist/base.d.ts:563 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | `"plugin"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`] & `MacroContext`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `...CatchAllEntries[]`, `...MacroCatchAll[]`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Ephemeral`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `object` *extends* `Pick`<`Input`, `Extract`\>> ? `Ephemeral`\[`"schema"`] : `MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`>, `Ephemeral`\[`"schema"`]>; `schemas`: `Ephemeral`\[`"schemas"`] & `MacroContext`; }, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]>, { macro: Metadata["macro"]; macroFn: Metadata["macroFn"]; parser: Metadata["parser"]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: Metadata["schema"]; schemas: Metadata["schemas"] & UnwrapRoute & MacroContext; }, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:572 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `scope` | `"global"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `MacroContext`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `WithErrorEntries`<`Definitions`, \[`...CatchAllEntries[]`, `...MacroCatchAll[]`]>, { `macro`: `Metadata`\[`"macro"`]; `macroFn`: `Metadata`\[`"macroFn"`]; `parser`: `Metadata`\[`"parser"`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Metadata`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `Metadata`\[`"schema"`]; `schemas`: `Metadata`\[`"schemas"`] & [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`> & `MacroContext`; }, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` #### Call Signature ```ts guard(scope, hook): Elysia[], ...MacroCatchAll[]]>, { macro: Metadata["macro"]; macroFn: Metadata["macroFn"]; parser: Metadata["parser"]; response: UnionResponseStatus>, UnionResponseStatus, UnionResponseStatus, WithoutMacroCatch & object>>>; schema: object extends Pick>> ? Metadata["schema"] : MergeSchema, Metadata["schema"]>; schemas: Metadata["schemas"] & MacroContext; }, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:588 Apply a hook and schema to every route defined after it on this instance. *** ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* \| [`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `BasePath`> \| { `body`: `undefined` *extends* `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"body"`] : `UnwrapBodySchema`<`Input`\[`"body"`], `Definitions`\[`"typebox"`], `"output"`>; `cookie`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"cookie"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"cookie"`], `Definitions`\[`"typebox"`], `"output"`>; `headers`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"headers"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"headers"`], `Definitions`\[`"typebox"`], `"output"`>; `params`: `IsNever`\ : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>> *extends* `true` ? `IsNever`\>\[`"params"`]> *extends* `true` ? `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"params"`] : `object` *extends* `Input`\[`"params"`] ? `ResolvePath`<`BasePath`> : `object` *extends* `Input` ? `ResolvePath`<`BasePath`> : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"params"`], `Definitions`\[`"typebox"`], `"output"`>; `query`: `undefined` *extends* [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`> ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"query"`] : [`UnwrapSchema`](/reference/type-aliases/unwrapschema/)<`Input`\[`"query"`], `Definitions`\[`"typebox"`], `"output"`>; `response`: `object` *extends* `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema\ extends A ? A extends File ? File \| ElysiaFile : A : unknown } : `unknown` ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] & `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `object` : `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] : `object` *extends* `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>\[`"response"`] ? `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…)\[(…)], (…)\[(…)]> extends A ? A extends File ? (…) \| (…) : A : unknown } : `unknown` : `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : `Input`\[`"response"`] *extends* `object` ? { \[k in string \| number \| symbol]: UnwrapResponseSchema<(…), (…)> extends A ? (…) extends (…) ? (…) : (…) : unknown } : `unknown` & `Omit`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<…\[…], …\[…]>>\[`"response"`], keyof `Input`\[`"response"`] *extends* string \| AnySchema ? `object` : …\[…] *extends* `object` ? `{ [k in (...)]: (...) }` : `unknown`>; } & [`RouteSchema`](/reference/interfaces/routeschema/) | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `BeforeHandle` *extends* `MaybeArray`<`OptionalHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `AfterHandle` *extends* `MaybeArray`<`AfterHandler`<`Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | | `ErrorHandle` *extends* `MaybeArray`<`ErrorHandler`<`Definitions`\[`"error"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, `MacroContext`>>> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | `"global"` | | `hook` | `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `object` & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `MacroContext`\[`"derive"`]; `store`: `Singleton`\[`"store"`]; }, `WithErrorEntries`<`Definitions`, \[`...CatchAllEntries[]`, `...MacroCatchAll[]`]>, { `macro`: `Metadata`\[`"macro"`]; `macroFn`: `Metadata`\[`"macroFn"`]; `parser`: `Metadata`\[`"parser"`]; `response`: `UnionResponseStatus`<`UnionResponseStatus`<`Metadata`\[`"response"`], `ElysiaHandlerToResponseSchemaAmbiguous`<`BeforeHandle`>>, `UnionResponseStatus`<`ElysiaHandlerToResponseSchemaAmbiguous`<`AfterHandle`>, `UnionResponseStatus`<`ErrorHookStatus`<`ErrorHandle`>, `WithoutMacroCatch`<`MacroContext`\[`"return"` & keyof `MacroContext`]> & `object`>>>; `schema`: `object` *extends* `Pick`<`Input`, `Extract`\>> ? `Metadata`\[`"schema"`] : `MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `""`>, `Metadata`\[`"schema"`]>; `schemas`: `Metadata`\[`"schemas"`] & `MacroContext`; }, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .guard({ body: t.Object({ name: t.String() }) }) .post('/', ({ body }) => body) ``` *** ### handler() ```ts handler( index, immediate?, route?, aliases?, table? ): CompiledHandler; ``` Defined in: elysia/dist/base.d.ts:1044 #### Parameters | Parameter | Type | | ------------ | ------------------ | | `index` | `number` | | `immediate?` | `boolean` | | `route?` | `InternalRoute` | | `aliases?` | `StaticMapAliases` | | `table?` | `RouteTable` | #### Returns `CompiledHandler` *** ### has() ```ts has(plugin): boolean; ``` Defined in: elysia/dist/base.d.ts:812 #### Parameters | Parameter | Type | | --------- | ------------------------------------------------- | | `plugin` | [`AnyElysia`](/reference/type-aliases/anyelysia/) | #### Returns `boolean` *** ### head() #### Call Signature ```ts head( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:986 Register handler for path with method \[HEAD] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"head"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .head('/', () => 'hi') .head('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts head(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:989 Register handler for path with method \[HEAD] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"head"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .head('/', () => 'hi') .head('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### headers() ```ts headers(headers): this; ``` Defined in: elysia/dist/base.d.ts:229 #### Parameters | Parameter | Type | | --------- | ---------------------------- | | `headers` | `Record`<`string`, `string`> | #### Returns `this` *** ### listen() ```ts listen(options, callback?): this; ``` Defined in: elysia/dist/base.d.ts:1050 #### Parameters | Parameter | Type | | ----------- | ----------------------------------------------------------------- | | `options` | `string` \| `number` \| `Partial`<`Options`<`unknown`, `string`>> | | `callback?` | `ListenCallback` | #### Returns `this` *** ### macro() ```ts macro(macro): Elysia any ? MacroOptionLambda<(...)> extends never ? Params : unknown : boolean : boolean }[K] }>; macroFn: Metadata["macroFn"] & NewMacro; parser: Metadata["parser"]; response: Metadata["response"]; schema: Metadata["schema"]; schemas: Metadata["schemas"]; }, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:677 Declare a custom route property: applying it on a route or guard folds the definition’s schema, lifecycle hooks, and `derive` result into that route ```ts new Elysia() .macro({ auth: { headers: t.Object({ authorization: t.String() }), derive: ({ headers }) => ({ user: headers.authorization }) }, role: (role: 'admin' | 'user') => ({ beforeHandle() { ... } }) }) .get('/', { auth: true, role: 'admin' }, ({ user }) => user) ``` #### Type Parameters | Type Parameter | Default type | | -------------- | ------------ | | `NewMacro` | - | | `Refs` | `object` | #### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `macro` | `ObjectMacroDefs`<`NewMacro`, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>, `Singleton` & `object`, `Definitions`, `Metadata`\[`"macro"`], `Metadata`\[`"macroFn"`], `Refs`> | #### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, { `macro`: `Metadata`\[`"macro"`] & `Partial`<{ \[K in string | number | symbol]: { \[K in string | number | symbol]: NewMacro\[K] extends Function ? any\[any] extends (a: Params) => any ? MacroOptionLambda<(…)> extends never ? Params : unknown : boolean : boolean }\[K] }>; `macroFn`: `Metadata`\[`"macroFn"`] & `NewMacro`; `parser`: `Metadata`\[`"parser"`]; `response`: `Metadata`\[`"response"`]; `schema`: `Metadata`\[`"schema"`]; `schemas`: `Metadata`\[`"schemas"`]; }, `Routes`, `Ephemeral`, `Volatile`> *** ### mapDerive() #### Call Signature ```ts mapDerive(transform): Elysia; error: Volatile["error"]; response: UnionResponseStatus>; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:287 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `ExcludeElysiaResponse`<`Derivative`>; `error`: `Volatile`\[`"error"`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `DeriveResponse`<`Derivative`>>; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> #### Call Signature ```ts mapDerive(scope, transform): Elysia; error: Volatile["error"]; response: UnionResponseStatus>; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:294 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"local"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `ExcludeElysiaResponse`<`Derivative`>; `error`: `Volatile`\[`"error"`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `DeriveResponse`<`Derivative`>>; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> #### Call Signature ```ts mapDerive(scope, transform): Elysia; error: Ephemeral["error"]; response: UnionResponseStatus>; schema: Ephemeral["schema"]; schemas: Ephemeral["schemas"]; }, Volatile>; ``` Defined in: elysia/dist/base.d.ts:301 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"plugin"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `ExcludeElysiaResponse`<`Derivative`>; `error`: `Ephemeral`\[`"error"`]; `response`: `UnionResponseStatus`<`Ephemeral`\[`"response"`], `DeriveResponse`<`Derivative`>>; `schema`: `Ephemeral`\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`]; }, `Volatile`> #### Call Signature ```ts mapDerive(scope, transform): Elysia; store: Singleton["store"]; }, Definitions, { macro: Metadata["macro"]; macroFn: Metadata["macroFn"]; parser: Metadata["parser"]; response: UnionResponseStatus>; schema: Metadata["schema"]; schemas: Metadata["schemas"]; }, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:308 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `"global"` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `ExcludeElysiaResponse`<`Derivative`>; `store`: `Singleton`\[`"store"`]; }, `Definitions`, { `macro`: `Metadata`\[`"macro"`]; `macroFn`: `Metadata`\[`"macroFn"`]; `parser`: `Metadata`\[`"parser"`]; `response`: `UnionResponseStatus`<`Metadata`\[`"response"`], `DeriveResponse`<`Derivative`>>; `schema`: `Metadata`\[`"schema"`]; `schemas`: `Metadata`\[`"schemas"`]; }, `Routes`, `Ephemeral`, `Volatile`> #### Call Signature ```ts mapDerive(options, transform): ScopedMapDeriveReturn, BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, DeriveResponse, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:321 Deprecated 1.x syntax, use `.mapDerive('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ------------ | ------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `ScopedMapDeriveReturn`<`LegacyScope`<`As`>, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> #### Call Signature ```ts mapDerive(scope, transform): ScopedMapDeriveReturn, ExcludeElysiaResponse>; ``` Defined in: elysia/dist/base.d.ts:324 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HookScope` *extends* `EventScope` | | `Derivative` *extends* \| `void` \| `Record`<`string`, `unknown`> \| `Error` \| [`ElysiaStatus`](/reference/classes/elysiastatus/)<`any`, `any`, `any`> | ##### Parameters | Parameter | Type | | ----------- | ------------------------------------------- | | `scope` | `HookScope` | | `transform` | (`context`) => `MaybePromise`<`Derivative`> | ##### Returns `ScopedMapDeriveReturn`<`HookScope`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `DeriveResponse`<`Derivative`>, `ExcludeElysiaResponse`<`Derivative`>> *** ### mapResponse() #### Call Signature ```ts mapResponse(fn): this; ``` Defined in: elysia/dist/base.d.ts:334 ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fn` | `MaybeArray`<`MapResponse`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Returns `this` #### Call Signature ```ts mapResponse(options, fn): this; ``` Defined in: elysia/dist/base.d.ts:336 Deprecated 1.x syntax, use `.mapResponse('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | ##### Parameters | Parameter | Type | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `MaybeArray`<`MapResponse`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>>> | ##### Returns `this` #### Call Signature ```ts mapResponse(scope, fn): this; ``` Defined in: elysia/dist/base.d.ts:339 ##### Type Parameters | Type Parameter | | ---------------------------------- | | `HookScope` *extends* `EventScope` | ##### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | `HookScope` | | `fn` | `MaybeArray`<`MapResponse`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`>> | ##### Returns `this` *** ### method() #### Call Signature ```ts method( method, path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:1005 Register handler for path with custom method *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Method` *extends* `HTTPMethod` | - | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `method` | `Method` | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Method`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .method('Elysia', '/', 'hi') ``` #### Call Signature ```ts method( method, path, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:1008 Register handler for path with custom method *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Method` *extends* `HTTPMethod` | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `method` | `Method` | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Method`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .method('Elysia', '/', 'hi') ``` *** ### model() #### Call Signature ```ts model(name, model): Elysia; ``` Defined in: elysia/dist/base.d.ts:815 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------- | | `Name` *extends* `string` | | `Model` *extends* [`AnySchema`](/reference/type-aliases/anyschema/) | ##### Parameters | Parameter | Type | | --------- | ------- | | `name` | `Name` | | `model` | `Model` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: `Definitions`\[`"error"`]; `typebox`: `Definitions`\[`"typebox"`] & `{ [name in string]: Model }`; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> #### Call Signature ```ts model(record): Elysia; ``` Defined in: elysia/dist/base.d.ts:819 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------ | | `Recorder` *extends* `Record`<`string`, [`AnySchema`](/reference/type-aliases/anyschema/)> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `record` | `Recorder` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: `Definitions`\[`"error"`]; `typebox`: `Definitions`\[`"typebox"`] & `Recorder`; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> #### Call Signature ```ts model(mapper): Elysia ? Definitions["typebox"][Name] : NewType[Name] }; }, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:823 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------------------------- | | `NewType` *extends* `Record`<`string`, [`AnySchema`](/reference/type-aliases/anyschema/)> | ##### Parameters | Parameter | Type | | --------- | --------------------------- | | `mapper` | (`decorators`) => `NewType` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: `Definitions`\[`"error"`]; `typebox`: { \[Name in string | number | symbol]: NewType\[Name] extends TRef\ ? Definitions\[“typebox”]\[Name] : NewType\[Name] }; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> *** ### mount() #### Call Signature ```ts mount(handle): this; ``` Defined in: elysia/dist/base.d.ts:1037 ##### Parameters | Parameter | Type | | --------- | ----------------------------------------- | | `handle` | (`request`) => `MaybePromise`<`Response`> | ##### Returns `this` #### Call Signature ```ts mount(path, handle): this; ``` Defined in: elysia/dist/base.d.ts:1038 ##### Parameters | Parameter | Type | | --------- | ----------------------------------------- | | `path` | `string` | | `handle` | (`request`) => `MaybePromise`<`Response`> | ##### Returns `this` *** ### onAfterHandle() #### Call Signature ```ts onAfterHandle(fn): LocalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:325 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `fn` | `Handler` | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onAfterHandle(scope, fn): LocalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:326 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `scope` | `"local"` | | `fn` | `Handler` | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onAfterHandle(scope, fn): PluginHookReturn>; ``` Defined in: elysia/dist/base.d.ts:327 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `"plugin"`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"plugin"` | | `fn` | `Handler` | ##### Returns `PluginHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onAfterHandle(scope, fn): GlobalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:328 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `"global"`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"global"` | | `fn` | `Handler` | ##### Returns `GlobalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onAfterHandle(options, fn): ScopedHookReturn, BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, ElysiaHandlerToResponseSchemaAmbiguous>; ``` Defined in: elysia/dist/base.d.ts:330 Deprecated 1.x syntax, use `.onAfterHandle('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>>> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `Handler` | ##### Returns `ScopedHookReturn`<`LegacyScope`<`As`>, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onAfterHandle(scope, fn): ScopedHookReturn>; ``` Defined in: elysia/dist/base.d.ts:333 ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HookScope` *extends* `EventScope` | | `Handler` *extends* `MaybeArray`<`AfterHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`>> | ##### Parameters | Parameter | Type | | --------- | ----------- | | `scope` | `HookScope` | | `fn` | `Handler` | ##### Returns `ScopedHookReturn`<`HookScope`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> *** ### onAfterResponse() #### Call Signature ```ts onAfterResponse(fn): this; ``` Defined in: elysia/dist/base.d.ts:340 ##### Parameters | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fn` | `AfterResponseHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>> | ##### Returns `this` #### Call Signature ```ts onAfterResponse(options, fn): this; ``` Defined in: elysia/dist/base.d.ts:342 Deprecated 1.x syntax, use `.onAfterResponse('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | ##### Parameters | Parameter | Type | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `AfterResponseHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>> | ##### Returns `this` #### Call Signature ```ts onAfterResponse(scope, fn): this; ``` Defined in: elysia/dist/base.d.ts:345 ##### Type Parameters | Type Parameter | | ---------------------------------- | | `HookScope` *extends* `EventScope` | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `scope` | `HookScope` | | `fn` | `AfterResponseHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`> | ##### Returns `this` *** ### onBeforeHandle() #### Call Signature ```ts onBeforeHandle(fn): LocalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:269 ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `fn` | `Handler` | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onBeforeHandle(scope, fn): LocalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:270 ##### Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `scope` | `"local"` | | `fn` | `Handler` | ##### Returns `LocalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onBeforeHandle(scope, fn): PluginHookReturn>; ``` Defined in: elysia/dist/base.d.ts:271 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `"plugin"`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"plugin"` | | `fn` | `Handler` | ##### Returns `PluginHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onBeforeHandle(scope, fn): GlobalHookReturn>; ``` Defined in: elysia/dist/base.d.ts:272 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `"global"`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"global"` | | `fn` | `Handler` | ##### Returns `GlobalHookReturn`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onBeforeHandle(options, fn): ScopedHookReturn, BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, ElysiaHandlerToResponseSchemaAmbiguous>; ``` Defined in: elysia/dist/base.d.ts:274 Deprecated 1.x syntax, use `.onBeforeHandle('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>>> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `Handler` | ##### Returns `ScopedHookReturn`<`LegacyScope`<`As`>, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> #### Call Signature ```ts onBeforeHandle(scope, fn): ScopedHookReturn>; ``` Defined in: elysia/dist/base.d.ts:277 ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HookScope` *extends* `EventScope` | | `Handler` *extends* `MaybeArray`<`OptionalHandler`<`HookContextSchema`<`Metadata`, `Ephemeral`, `Volatile`, `BasePath`>, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`>> | ##### Parameters | Parameter | Type | | --------- | ----------- | | `scope` | `HookScope` | | `fn` | `Handler` | ##### Returns `ScopedHookReturn`<`HookScope`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `ElysiaHandlerToResponseSchemaAmbiguous`<`Handler`>> *** ### onError() #### Call Signature ```ts onError(fn): CatchAllHookReturn<"local", BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, Handler>; ``` Defined in: elysia/dist/base.d.ts:346 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton`>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`"local"`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError(error, fn): Scope extends "local" ? Elysia>]; response: Volatile["response"]; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }> : Scope extends "plugin" ? Elysia>]; response: Ephemeral["response"]; schema: Ephemeral["schema"]; schemas: Ephemeral["schemas"]; }, Volatile> : Elysia>]; typebox: Definitions["typebox"]; }, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:347 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------- | | `E` *extends* `AnyErrorConstructor` & (…`args`) => `Error` | | `Fn` *extends* (`context`) => `unknown` | ##### Parameters | Parameter | Type | | --------- | ---- | | `error` | `E` | | `fn` | `Fn` | ##### Returns `Scope` *extends* `"local"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `response`: `Volatile`\[`"response"`]; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> : `Scope` *extends* `"plugin"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `response`: `Ephemeral`\[`"response"`]; `schema`: `Ephemeral`\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`]; }, `Volatile`> : `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: \[`...Definitions["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `typebox`: `Definitions`\[`"typebox"`]; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> #### Call Signature ```ts onError(error, value): Elysia]; response: Volatile["response"]; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:367 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------- | | `E` *extends* `AnyErrorConstructor` & (…`args`) => `Error` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------- | | `error` | `E` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `ErrorDefinitionEntry`<`E`, `Value`>]; `response`: `Volatile`\[`"response"`]; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> #### Call Signature ```ts onError(scope, fn): CatchAllHookReturn<"local", BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, Handler>; ``` Defined in: elysia/dist/base.d.ts:374 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton` & `object`>> | ##### Parameters | Parameter | Type | | --------- | --------- | | `scope` | `"local"` | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`"local"`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError(scope, fn): CatchAllHookReturn<"plugin", BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, Handler>; ``` Defined in: elysia/dist/base.d.ts:377 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton` & `object`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"plugin"` | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`"plugin"`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError(scope, fn): CatchAllHookReturn<"global", BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, Handler>; ``` Defined in: elysia/dist/base.d.ts:380 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton` & `object`>> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `scope` | `"global"` | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`"global"`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError(options, fn): CatchAllHookReturn, BasePath, Scope, Singleton, Definitions, Metadata, Routes, Ephemeral, Volatile, Handler>; ``` Defined in: elysia/dist/base.d.ts:384 Deprecated 1.x syntax, use `.onError('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton` & `object`>> | ##### Parameters | Parameter | Type | | ------------ | --------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`LegacyScope`<`As`>, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError(scope, fn): CatchAllHookReturn; ``` Defined in: elysia/dist/base.d.ts:389 ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `HookScope` *extends* `EventScope` | | `Handler` *extends* `MaybeArray`<`ErrorHandler`<\[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], { }, `Singleton` & `object`>> | ##### Parameters | Parameter | Type | | --------- | ----------- | | `scope` | `HookScope` | | `fn` | `Handler` | ##### Returns `CatchAllHookReturn`<`HookScope`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Handler`> #### Call Signature ```ts onError( scope, error, fn ): S extends "global" ? Elysia>]; typebox: Definitions["typebox"]; }, Metadata, Routes, Ephemeral, Volatile> : S extends "plugin" ? Elysia>]; response: Ephemeral["response"]; schema: Ephemeral["schema"]; schemas: Ephemeral["schemas"]; }, Volatile> : Elysia>]; response: Volatile["response"]; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:392 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------- | | `S` *extends* `EventScope` | | `E` *extends* `AnyErrorConstructor` & (…`args`) => `Error` | | `Fn` *extends* (`context`) => `unknown` | ##### Parameters | Parameter | Type | | --------- | ---- | | `scope` | `S` | | `error` | `E` | | `fn` | `Fn` | ##### Returns `S` *extends* `"global"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: \[`...Definitions["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `typebox`: `Definitions`\[`"typebox"`]; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> : `S` *extends* `"plugin"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `response`: `Ephemeral`\[`"response"`]; `schema`: `Ephemeral`\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`]; }, `Volatile`> : `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `ErrorDefinitionEntry`<`E`, `ReturnType`<`Fn`>>]; `response`: `Volatile`\[`"response"`]; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> #### Call Signature ```ts onError( scope, error, value ): S extends "global" ? Elysia]; typebox: Definitions["typebox"]; }, Metadata, Routes, Ephemeral, Volatile> : S extends "plugin" ? Elysia]; response: Ephemeral["response"]; schema: Ephemeral["schema"]; schemas: Ephemeral["schemas"]; }, Volatile> : Elysia]; response: Volatile["response"]; schema: Volatile["schema"]; schemas: Volatile["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:414 ##### Type Parameters | Type Parameter | | ---------------------------------------------------------- | | `S` *extends* `EventScope` | | `E` *extends* `AnyErrorConstructor` & (…`args`) => `Error` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------- | | `scope` | `S` | | `error` | `E` | | `value` | `Value` | ##### Returns `S` *extends* `"global"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, { `error`: \[`...Definitions["error"][]`, `ErrorDefinitionEntry`<`E`, `Value`>]; `typebox`: `Definitions`\[`"typebox"`]; }, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> : `S` *extends* `"plugin"` ? `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, { `derive`: `Ephemeral`\[`"derive"`]; `error`: \[`...Ephemeral["error"][]`, `ErrorDefinitionEntry`<`E`, `Value`>]; `response`: `Ephemeral`\[`"response"`]; `schema`: `Ephemeral`\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`]; }, `Volatile`> : `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `ErrorDefinitionEntry`<`E`, `Value`>]; `response`: `Volatile`\[`"response"`]; `schema`: `Volatile`\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`]; }> *** ### onParse() #### Call Signature ```ts onParse(fn): this; ``` Defined in: elysia/dist/base.d.ts:231 ##### Parameters | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fn` | `MaybeArray`<`BodyHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Returns `this` #### Call Signature ```ts onParse(name): this; ``` Defined in: elysia/dist/base.d.ts:232 ##### Parameters | Parameter | Type | | --------- | -------- | | `name` | `string` | ##### Returns `this` #### Call Signature ```ts onParse(options, fn): this; ``` Defined in: elysia/dist/base.d.ts:234 Deprecated 1.x syntax, use `.onParse('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | ##### Parameters | Parameter | Type | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `MaybeArray`<`BodyHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>>> | ##### Returns `this` #### Call Signature ```ts onParse(scope, fn): this; ``` Defined in: elysia/dist/base.d.ts:237 ##### Type Parameters | Type Parameter | | ---------------------------------- | | `HookScope` *extends* `EventScope` | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `scope` | `HookScope` | | `fn` | `MaybeArray`<`BodyHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`>> | ##### Returns `this` *** ### onRequest() ```ts onRequest(fn): this; ``` Defined in: elysia/dist/base.d.ts:230 #### Parameters | Parameter | Type | | --------- | -------------------------------------------- | | `fn` | `MaybeArray`<`PreHandler`<{ }, `Singleton`>> | #### Returns `this` *** ### onStart() ```ts onStart(handler): this; ``` Defined in: elysia/dist/base.d.ts:262 Called after server is ready for serving *** #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `handler` | `MaybeArray`<`GracefulHandler`<`Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`>>> | #### Returns `this` #### Example ```typescript new Elysia() .onStart(({ server }) => { console.log(`Running at ${server?.url}`) }) .listen(3000) ``` *** ### onStop() ```ts onStop(handler): this; ``` Defined in: elysia/dist/base.d.ts:1080 Called after server stop serving request *** #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `handler` | `MaybeArray`<`GracefulHandler`<`Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`>>> | #### Returns `this` #### Example ```typescript new Elysia() .onStop((app) => { closeDatabase() }) ``` *** ### onTransform() #### Call Signature ```ts onTransform(fn): this; ``` Defined in: elysia/dist/base.d.ts:263 ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fn` | `MaybeArray`<`TransformHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>>> | ##### Returns `this` #### Call Signature ```ts onTransform(options, fn): this; ``` Defined in: elysia/dist/base.d.ts:265 Deprecated 1.x syntax, use `.onTransform('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | ##### Parameters | Parameter | Type | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `MaybeArray`<`TransformHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `LegacyScope`<`As`>>> | ##### Returns `this` #### Call Signature ```ts onTransform(scope, fn): this; ``` Defined in: elysia/dist/base.d.ts:268 ##### Type Parameters | Type Parameter | | ---------------------------------- | | `HookScope` *extends* `EventScope` | ##### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | `HookScope` | | `fn` | `MaybeArray`<`TransformHandler`<`BasePath` *extends* `` `${string}/:${string}` `` \| `` `${string}/*${string}` `` ? `Omit`<{ }, `"params"`> & `object` : `object`, `HookContextSingleton`<`Singleton`, `Ephemeral`, `Volatile`>, `undefined`, `HookScope`>> | ##### Returns `this` *** ### options() #### Call Signature ```ts options( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:946 Register handler for path with method \[OPTIONS] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"options"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .options('/', () => 'hi') .options('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts options(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:949 Register handler for path with method \[OPTIONS] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"options"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .options('/', () => 'hi') .options('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### parser() ```ts parser(name, fn): Elysia }; response: Metadata["response"]; schema: Metadata["schema"]; schemas: Metadata["schemas"]; }, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:238 #### Type Parameters | Type Parameter | | ------------------------- | | `Name` *extends* `string` | #### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `Name` | | `fn` | `BodyHandler`<`MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>, `BasePath`> & `Metadata`\[`"schemas"`] & `Ephemeral`\[`"schemas"`] & `Volatile`\[`"schemas"`], `Singleton` & `object`> | #### Returns `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, { `macro`: `Metadata`\[`"macro"`]; `macroFn`: `Metadata`\[`"macroFn"`]; `parser`: `Metadata`\[`"parser"`] & `{ [name in string]: BodyHandler }`; `response`: `Metadata`\[`"response"`]; `schema`: `Metadata`\[`"schema"`]; `schemas`: `Metadata`\[`"schemas"`]; }, `Routes`, `Ephemeral`, `Volatile`> *** ### patch() #### Call Signature ```ts patch( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:906 Register handler for path with method \[PATCH] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"patch"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .patch('/', () => 'hi') .patch('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts patch(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:909 Register handler for path with method \[PATCH] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"patch"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .patch('/', () => 'hi') .patch('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### post() #### Call Signature ```ts post( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:866 Register handler for path with method \[POST] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"post"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/', () => 'hi') .post('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts post(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:869 Register handler for path with method \[POST] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"post"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/', () => 'hi') .post('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### put() #### Call Signature ```ts put( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:886 Register handler for path with method \[PUT] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"put"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .put('/', () => 'hi') .put('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts put(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:889 Register handler for path with method \[PUT] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"put"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .put('/', () => 'hi') .put('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### query() #### Call Signature ```ts query( path, hook, fn ): AddRoute; ``` Defined in: elysia/dist/base.d.ts:966 Register handler for path with method \[QUERY] *** ##### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | `Path` *extends* `string` | - | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `RouteHookCapture` | - | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | - | | `Decorator` *extends* `SingletonBase` & `object` | - | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | - | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends (NoInfer\ & MacroContext)\[“response”] ? unknown : Error \| ((NoInfer\ & MacroContext)\[“response”] extends { …; } ? (NoInfer\ & MacroContext)\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| (NoInfer\ & MacroContext)\[“re…) | - | | `UnknownKey` *extends* `PropertyKey` | `Exclude`\ | ##### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `Path` | | `hook` | `object` & `Input` *extends* `any` ? `Input` : { \[K in string \| number \| symbol]: Input\[K] } & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `Decorator`> & `UnknownHookKey`<`UnknownKey`> | | `fn` | `Handle` | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"query"`, `Path`, `Schema`, `MacroContext`, `Handle`, `Input`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .query('/', () => 'hi') .query('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` #### Call Signature ```ts query(path, fn): AddRoute; ``` Defined in: elysia/dist/base.d.ts:969 Register handler for path with method \[QUERY] *** ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<{ }, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `true`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `Decorator` *extends* `SingletonBase` & `object` | | `Handle` *extends* \| `MaybePromise`<`InlineResponse`> \| ((`context`) => MaybePromise\ \| MaybePromise<{} extends NoInfer\\[“response”] ? unknown : Error \| (NoInfer\\[“response”] extends { …; } ? NoInfer\\[“response”]\[200] \| … 2 more … \| AsyncGenerator<…> : unknown) \| NoInfer\\[“response”]\[keyof NoInfer\\[“response”]] \| InlineHandlerResponse<…) | ##### Parameters | Parameter | Type | | --------- | --------------------------------- | | `path` | `Path` | | `fn` | `Handle` & `Metadata`\[`"macro"`] | ##### Returns `AddRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `"query"`, `Path`, `Schema`, { }, `Handle`> ##### Example ```typescript import { Elysia, t } from 'elysia' new Elysia() .query('/', () => 'hi') .query('/hook', { query: t.Object({ name: t.String() }) }, () => 'hi') ``` *** ### Ref() ```ts Ref(key): TRef; ``` Defined in: elysia/dist/base.d.ts:831 #### Type Parameters | Type Parameter | | ------------------------ | | `Key` *extends* `string` | #### Parameters | Parameter | Type | | --------- | ----- | | `key` | `Key` | #### Returns `TRef`<`Key`> *** ### state() #### Call Signature ```ts state(name, value): Elysia; ``` Defined in: elysia/dist/base.d.ts:194 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------- | | `Name` *extends* `string` \| `number` \| `symbol` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------- | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & { \[k in string | number | symbol]: Value })\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state(store): Elysia; ``` Defined in: elysia/dist/base.d.ts:199 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | -------------------------------------------------- | | `NewStore` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `store` | `NewStore` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewStore)\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state(mapper): Elysia; ``` Defined in: elysia/dist/base.d.ts:204 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | -------------------------------------------------- | | `NewStore` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | --------- | ----------------------- | | `mapper` | (`store`) => `NewStore` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: `NewStore`; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state( type, name, value ): Elysia; ``` Defined in: elysia/dist/base.d.ts:209 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------- | | `Name` *extends* `string` \| `number` \| `symbol` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ---------- | | `type` | `"append"` | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & { \[k in string | number | symbol]: Value })\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state( type, name, value ): Elysia & { [k in string | number | symbol]: Value })[K] }; }, Definitions, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:214 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | ------------------------------------------------- | | `Name` *extends* `string` \| `number` \| `symbol` | | `Value` | ##### Parameters | Parameter | Type | | --------- | ------------ | | `type` | `"override"` | | `name` | `Name` | | `value` | `Value` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Omit\ & { \[k in string | number | symbol]: Value })\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state(type, store): Elysia; ``` Defined in: elysia/dist/base.d.ts:219 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | -------------------------------------------------- | | `NewStore` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | --------- | ---------- | | `type` | `"append"` | | `store` | `NewStore` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewStore)\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` #### Call Signature ```ts state(type, store): Elysia & NewStore)[K] }; }, Definitions, Metadata, Routes, Ephemeral, Volatile>; ``` Defined in: elysia/dist/base.d.ts:224 Assign global mutable state accessible for all handler. *** ##### Type Parameters | Type Parameter | | -------------------------------------------------- | | `NewStore` *extends* `Record`<`string`, `unknown`> | ##### Parameters | Parameter | Type | | --------- | ------------ | | `type` | `"override"` | | `store` | `NewStore` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Omit\ & NewStore)\[K] }; }, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> ##### Example ```typescript new Elysia() .state('counter', 0) .get('/', ({ store }) => ++store.counter) ``` *** ### stop() ```ts stop(closeActiveConnections?): Promise; ``` Defined in: elysia/dist/base.d.ts:1090 Stop the underlying server (if any), then run every `onStop` handler Awaiting `stop()` from inside an `onStart`, `onStop` or WebSocket lifecycle callback is only supported while that callback is still synchronous #### Parameters | Parameter | Type | Description | | ------------------------- | --------- | ----------------------------------------------------------------------------------- | | `closeActiveConnections?` | `boolean` | Pass `true` to terminate active transports. Omit (or pass `false`) to drain safely. | #### Returns `Promise`<`void`> *** ### trace() #### Call Signature ```ts trace(fn): this; ``` Defined in: elysia/dist/base.d.ts:430 ##### Parameters | Parameter | Type | | --------- | ---------------------------- | | `fn` | `TraceHandler`<`any`, `any`> | ##### Returns `this` #### Call Signature ```ts trace(options, fn): this; ``` Defined in: elysia/dist/base.d.ts:432 Deprecated 1.x syntax, use `.trace('plugin', fn)`, `'scoped'` is now `'plugin'` ##### Type Parameters | Type Parameter | | ---------------------------------------------------- | | `As` *extends* `"global"` \| `"local"` \| `"scoped"` | ##### Parameters | Parameter | Type | | ------------ | ---------------------------- | | `options` | { `as`: `As`; } | | `options.as` | `As` | | `fn` | `TraceHandler`<`any`, `any`> | ##### Returns `this` #### Call Signature ```ts trace(scope, fn): this; ``` Defined in: elysia/dist/base.d.ts:435 ##### Type Parameters | Type Parameter | | ---------------------------------- | | `HookScope` *extends* `EventScope` | ##### Parameters | Parameter | Type | | --------- | ---------------------------- | | `scope` | `HookScope` | | `fn` | `TraceHandler`<`any`, `any`> | ##### Returns `this` *** ### use() #### Call Signature ```ts use(instance): Elysia, BasePath extends "" ? Routes & ResolveUsedRouteErrors>> : Routes & CreateEden>>>, Ephemeral, { derive: Volatile["derive"] & NewElysia["~Ephemeral"]["derive"]; error: [...Volatile["error"][], ...NewElysia["~Ephemeral"]["error"][]]; response: UnionResponseStatus; schema: Volatile["schema"] & NewElysia["~Ephemeral"]["schema"]; schemas: Volatile["schemas"] & NewElysia["~Ephemeral"]["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:691 Merge a plugin instance ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------- | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | ---------- | ----------- | | `instance` | `NewElysia` | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`] & `NewElysia`\[`"~Singleton"`]\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `NewElysia`\[`"~Singleton"`]\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewElysia\[“\~Singleton”]\[“store”])\[K] }; }, { `error`: \[`...Definitions["error"][]`, `...NewElysia["~Definitions"]["error"][]`]; `typebox`: `Definitions`\[`"typebox"`] & `NewElysia`\[`"~Definitions"`]\[`"typebox"`]; }, `MergeMetadata`<`Metadata`, `NewElysia`\[`"~Metadata"`]>, `BasePath` *extends* `""` ? `Routes` & `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>> : `Routes` & `CreateEden`<`BasePath`, `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>>>, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `NewElysia`\[`"~Ephemeral"`]\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...NewElysia["~Ephemeral"]["error"][]`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `NewElysia`\[`"~Ephemeral"`]\[`"response"`]>; `schema`: `Volatile`\[`"schema"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schemas"`]; }> #### Call Signature ```ts use(instances): MergeElysiaInstances; ``` Defined in: elysia/dist/base.d.ts:712 Merge multiple plugin instances, or a factory returning them A factory taking no `app` has its array inferred by the instance overload above, as an array rather than a tuple, so its plugins are lost: return it `as const` ##### Type Parameters | Type Parameter | Default type | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `Instances` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/)\[] | - | | `Param` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> | ##### Parameters | Parameter | Type | | ----------- | --------------------------------------------------- | | `instances` | `Instances` \| ((`app`) => readonly \[`Instances`]) | ##### Returns `MergeElysiaInstances`<`Instances`, `BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Ephemeral`, `Volatile`, `Routes`> #### Call Signature ```ts use(plugin): IgnoresApp extends true ? Elysia, BasePath extends "" ? Routes & ResolveUsedRouteErrors>> : Routes & CreateEden>>>, Ephemeral, { derive: Volatile["derive"] & NewElysia["~Ephemeral"]["derive"]; error: [...Volatile["error"][], ...NewElysia["~Ephemeral"]["error"][]]; response: UnionResponseStatus; schema: Volatile["schema"] & NewElysia["~Ephemeral"]["schema"]; schemas: Volatile["schemas"] & NewElysia["~Ephemeral"]["schemas"]; }> : Elysia, BasePath extends "" ? Routes & NewElysia["~Routes"] : Routes & CreateEden, { derive: Ephemeral["derive"] & NewElysia["~Ephemeral"]["derive"]; error: NewElysia["~Ephemeral"]["error"]; response: UnionResponseStatus; schema: Ephemeral["schema"] & NewElysia["~Ephemeral"]["schema"]; schemas: Ephemeral["schemas"] & NewElysia["~Ephemeral"]["schemas"]; }, { derive: Volatile["derive"] & NewElysia["~Volatile"]["derive"]; error: NewElysia["~Volatile"]["error"]; response: UnionResponseStatus; schema: Volatile["schema"] & NewElysia["~Volatile"]["schema"]; schemas: Volatile["schemas"] & NewElysia["~Volatile"]["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:721 Inline functional plugin A factory that ignores `app` is assumed to return a new instance, used as a plugin the same way as `.use(instance)`. One returning the parent it captured is mistyped, types can’t see instance identity. A factory declaring a parameter, even an optional one, is typed as returning `app` ##### Type Parameters | Type Parameter | Default type | | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | - | | `Param` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> | | `Factory` | `unknown` | ##### Parameters | Parameter | Type | | --------- | ---------------------------------- | | `plugin` | `Factory` & (`app`) => `NewElysia` | ##### Returns `IgnoresApp`<`Factory`> *extends* `true` ? `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`] & `NewElysia`\[`"~Singleton"`]\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `NewElysia`\[`"~Singleton"`]\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewElysia\[“\~Singleton”]\[“store”])\[K] }; }, { `error`: \[`...Definitions["error"][]`, `...NewElysia["~Definitions"]["error"][]`]; `typebox`: `Definitions`\[`"typebox"`] & `NewElysia`\[`"~Definitions"`]\[`"typebox"`]; }, `MergeMetadata`<`Metadata`, `NewElysia`\[`"~Metadata"`]>, `BasePath` *extends* `""` ? `Routes` & `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>> : `Routes` & `CreateEden`<`BasePath`, `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>>>, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `NewElysia`\[`"~Ephemeral"`]\[`"derive"`]; `error`: \[`...Volatile["error"][]`, `...NewElysia["~Ephemeral"]["error"][]`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `NewElysia`\[`"~Ephemeral"`]\[`"response"`]>; `schema`: `Volatile`\[`"schema"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schemas"`]; }> : `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`] & `NewElysia`\[`"~Singleton"`]\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `NewElysia`\[`"~Singleton"`]\[`"derive"`]; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewElysia\[“\~Singleton”]\[“store”])\[K] }; }, { `error`: \[`...Definitions["error"][]`, `...NewElysia["~Definitions"]["error"][]`]; `typebox`: `Definitions`\[`"typebox"`] & `NewElysia`\[`"~Definitions"`]\[`"typebox"`]; }, `MergeMetadata`<`Metadata`, `NewElysia`\[`"~Metadata"`]>, `BasePath` *extends* `""` ? `Routes` & `NewElysia`\[`"~Routes"`] : `Routes` & `CreateEden`<`BasePath`, `NewElysia`\[`"~Routes"`]>, { `derive`: `Ephemeral`\[`"derive"`] & `NewElysia`\[`"~Ephemeral"`]\[`"derive"`]; `error`: `NewElysia`\[`"~Ephemeral"`]\[`"error"`]; `response`: `UnionResponseStatus`<`Ephemeral`\[`"response"`], `NewElysia`\[`"~Ephemeral"`]\[`"response"`]>; `schema`: `Ephemeral`\[`"schema"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schemas"`]; }, { `derive`: `Volatile`\[`"derive"`] & `NewElysia`\[`"~Volatile"`]\[`"derive"`]; `error`: `NewElysia`\[`"~Volatile"`]\[`"error"`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `NewElysia`\[`"~Volatile"`]\[`"response"`]>; `schema`: `Volatile`\[`"schema"`] & `NewElysia`\[`"~Volatile"`]\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & `NewElysia`\[`"~Volatile"`]\[`"schemas"`]; }> #### Call Signature ```ts use(instance): Elysia; store: { [K in string | number | symbol]: (Singleton["store"] & NewElysia["~Singleton"]["store"])[K] }; }, { error: [...Definitions["error"][], ...NewElysia["~Definitions"]["error"][]]; typebox: Definitions["typebox"] & NewElysia["~Definitions"]["typebox"]; }, MergeMetadata, BasePath extends "" ? Routes & ResolveUsedRouteErrors>> : Routes & CreateEden>>>, Ephemeral, { derive: Volatile["derive"] & Partial; error: [...Volatile["error"][], ...NewElysia["~Ephemeral"]["error"][]]; response: UnionResponseStatus; schema: Volatile["schema"] & NewElysia["~Ephemeral"]["schema"]; schemas: Volatile["schemas"] & NewElysia["~Ephemeral"]["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:763 async plugin instance A factory that ignores `app` is assumed to resolve to a new instance, used as a plugin the same way. One resolving to the parent it captured is mistyped, types can’t see instance identity. A factory declaring a parameter, even an optional one, is typed as returning `app`, see the async functional plugin below ##### Type Parameters | Type Parameter | | ----------------------------------------------------------------------- | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | ##### Parameters | Parameter | Type | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `instance` | \| `Promise`< \| `NewElysia` \| { `default`: `NewElysia`; }> \| ((`app?`) => `MaybePromise`< \| `NewElysia` \| { `default`: `NewElysia`; }>) | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`] & `NewElysia`\[`"~Singleton"`]\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `Partial`<`NewElysia`\[`"~Singleton"`]\[`"derive"`]>; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewElysia\[“\~Singleton”]\[“store”])\[K] }; }, { `error`: \[`...Definitions["error"][]`, `...NewElysia["~Definitions"]["error"][]`]; `typebox`: `Definitions`\[`"typebox"`] & `NewElysia`\[`"~Definitions"`]\[`"typebox"`]; }, `MergeMetadata`<`Metadata`, `NewElysia`\[`"~Metadata"`]>, `BasePath` *extends* `""` ? `Routes` & `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>> : `Routes` & `CreateEden`<`BasePath`, `ResolveUsedRouteErrors`<`NewElysia`\[`"~Routes"`], \[`...Definitions["error"][]`, `...Ephemeral["error"][]`, `...Volatile["error"][]`], `UnionResponseStatus`<`Metadata`\[`"response"`], `UnionResponseStatus`<`Ephemeral`\[`"response"`], `Volatile`\[`"response"`]>>>>, `Ephemeral`, { `derive`: `Volatile`\[`"derive"`] & `Partial`<`NewElysia`\[`"~Ephemeral"`]\[`"derive"`]>; `error`: \[`...Volatile["error"][]`, `...NewElysia["~Ephemeral"]["error"][]`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `NewElysia`\[`"~Ephemeral"`]\[`"response"`]>; `schema`: `Volatile`\[`"schema"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schemas"`]; }> #### Call Signature ```ts use(plugin): Elysia; store: { [K in string | number | symbol]: (Singleton["store"] & NewElysia["~Singleton"]["store"])[K] }; }, { error: [...Definitions["error"][], ...NewElysia["~Definitions"]["error"][]]; typebox: Definitions["typebox"] & NewElysia["~Definitions"]["typebox"]; }, MergeMetadata, BasePath extends "" ? Routes & NewElysia["~Routes"] : Routes & CreateEden, { derive: Ephemeral["derive"] & Partial; error: NewElysia["~Ephemeral"]["error"]; response: UnionResponseStatus; schema: Ephemeral["schema"] & NewElysia["~Ephemeral"]["schema"]; schemas: Ephemeral["schemas"] & NewElysia["~Ephemeral"]["schemas"]; }, { derive: Volatile["derive"] & Partial; error: NewElysia["~Volatile"]["error"]; response: UnionResponseStatus; schema: Volatile["schema"] & NewElysia["~Volatile"]["schema"]; schemas: Volatile["schemas"] & NewElysia["~Volatile"]["schemas"]; }>; ``` Defined in: elysia/dist/base.d.ts:784 Async functional plugin ##### Type Parameters | Type Parameter | Default type | | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `NewElysia` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | - | | `Param` *extends* [`AnyElysia`](/reference/type-aliases/anyelysia/) | `Elysia`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`> | ##### Parameters | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plugin` | \| ((`app`) => `Promise`<`NewElysia`>) \| `Promise`<(`app`) => `MaybePromise`<`NewElysia`>> \| `Promise`<{ `default`: (`app`) => `MaybePromise`<`NewElysia`>; }> | ##### Returns `Elysia`<`BasePath`, `Scope`, { `decorator`: `Singleton`\[`"decorator"`] & `NewElysia`\[`"~Singleton"`]\[`"decorator"`]; `derive`: `Singleton`\[`"derive"`] & `Partial`<`NewElysia`\[`"~Singleton"`]\[`"derive"`]>; `store`: { \[K in string | number | symbol]: (Singleton\[“store”] & NewElysia\[“\~Singleton”]\[“store”])\[K] }; }, { `error`: \[`...Definitions["error"][]`, `...NewElysia["~Definitions"]["error"][]`]; `typebox`: `Definitions`\[`"typebox"`] & `NewElysia`\[`"~Definitions"`]\[`"typebox"`]; }, `MergeMetadata`<`Metadata`, `NewElysia`\[`"~Metadata"`]>, `BasePath` *extends* `""` ? `Routes` & `NewElysia`\[`"~Routes"`] : `Routes` & `CreateEden`<`BasePath`, `NewElysia`\[`"~Routes"`]>, { `derive`: `Ephemeral`\[`"derive"`] & `Partial`<`NewElysia`\[`"~Ephemeral"`]\[`"derive"`]>; `error`: `NewElysia`\[`"~Ephemeral"`]\[`"error"`]; `response`: `UnionResponseStatus`<`Ephemeral`\[`"response"`], `NewElysia`\[`"~Ephemeral"`]\[`"response"`]>; `schema`: `Ephemeral`\[`"schema"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schema"`]; `schemas`: `Ephemeral`\[`"schemas"`] & `NewElysia`\[`"~Ephemeral"`]\[`"schemas"`]; }, { `derive`: `Volatile`\[`"derive"`] & `Partial`<`NewElysia`\[`"~Volatile"`]\[`"derive"`]>; `error`: `NewElysia`\[`"~Volatile"`]\[`"error"`]; `response`: `UnionResponseStatus`<`Volatile`\[`"response"`], `NewElysia`\[`"~Volatile"`]\[`"response"`]>; `schema`: `Volatile`\[`"schema"`] & `NewElysia`\[`"~Volatile"`]\[`"schema"`]; `schemas`: `Volatile`\[`"schemas"`] & `NewElysia`\[`"~Volatile"`]\[`"schemas"`]; }> #### Call Signature ```ts use(app): this; ``` Defined in: elysia/dist/base.d.ts:809 Fallback for values the typed overloads cannot model ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `app` | `MaybePromise`< \| [`AnyElysia`](/reference/type-aliases/anyelysia/) \| [`AnyElysia`](/reference/type-aliases/anyelysia/)\[] \| { `default`: `unknown`; } \| ((`app`) => `unknown`)> | ##### Returns `this` *** ### wrap() ```ts wrap(callback): this; ``` Defined in: elysia/dist/base.d.ts:1066 Add a higher order function over Elysia.fetch #### Type Parameters | Type Parameter | Default type | | ------------------------------------------------------- | -------------------------------------------------- | | `T` *extends* (…`params`) => `MaybePromise`<`Response`> | (`request`, …`rest`) => `MaybePromise`<`Response`> | #### Parameters | Parameter | Type | | ---------- | ------------- | | `callback` | `WrapFn`<`T`> | #### Returns `this` #### Example ```ts const ctx = new AsyncLocalStorage<{ counter: number }>() new Elysia() .wrap( (fetch) => (request, ...rest) => ctx.run({ counter: 0 }, () => fetch(request, ...rest)) ) .get('/', () => ctx.getStore()) ``` *** ### ws() #### Call Signature ```ts ws(path, options): AddWSRoute; ``` Defined in: elysia/dist/base.d.ts:1022 Register a WebSocket route as `(path, options)`, `(path, handler)`, or `(path, options, handler)`. Requires `.use(websocket())` from `elysia/websocket`. ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `WSHookCapture` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `path` | `Path` | | `options` | { \[K in string \| number \| symbol]: Input\[K] } & `object` & `TypedWebSocketHandler`<`Omit`<{ \[K in string \| number \| symbol]: ({ body: PrettifyIfObject<(…)\[(…)] & (…)\[(…)]>; cookie: undefined extends ((…) & (…))\[“cookie”] ? Record\> : Record<(…), (…)> & { \[K in (…)]: (…) }; headers: undefined extends ((…) & (…))\[“headers”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)>; params: undefined extends ((…) & (…))\[“params”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)>; query: undefined extends ((…) & (…))\[“query”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)> } & ContextBase\ & (Singleton & { derive: (…) & (…) & (…) })\[“decorator”] & Omit<(Singleton & { derive: … })\[“derive”], keyof InputSchema\>)\[K] }, `"body"`> & `Omit`<`ElysiaWSLike`<{ \[K in string \| number \| symbol]: ({ body: PrettifyIfObject<(…) & (…)>; cookie: undefined extends (…)\[(…)] ? Record<(…), (…)> : (…) & (…); headers: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)>; params: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)>; query: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)> } & ContextBase\ & (Singleton & { derive: … })\[“decorator”] & Omit<((…) & (…))\[“derive”], keyof InputSchema<(…)>>)\[K] }, `Schema` & `MacroContext`>, `"body"`> & `object`, `Schema` & `MacroContext`> & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | ##### Returns `AddWSRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Path`, `Schema`, `MacroContext`, `void`, `Input`\[`"error"`], `Input`\[`"beforeHandle"`]> #### Call Signature ```ts ws(path, handler): AddWSRoute>; ``` Defined in: elysia/dist/base.d.ts:1027 Register a WebSocket route as `(path, options)`, `(path, handler)`, or `(path, options, handler)`. Requires `.use(websocket())` from `elysia/websocket`. ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `Handler` *extends* `WSMessageHandler`<`Schema` & `MacroContext`, `Singleton` & `object`> | ##### Parameters | Parameter | Type | | --------- | --------- | | `path` | `Path` | | `handler` | `Handler` | ##### Returns `AddWSRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Path`, `Schema`, `MacroContext`, `WSHandlerResponse`<`Handler`>> #### Call Signature ```ts ws( path, options, handler ): AddWSRoute, Input["error"], Input["beforeHandle"]>; ``` Defined in: elysia/dist/base.d.ts:1030 Register a WebSocket route as `(path, options)`, `(path, handler)`, or `(path, options, handler)`. Requires `.use(websocket())` from `elysia/websocket`. ##### Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Path` *extends* `string` | | `Input` *extends* `BaseMacro` & [`InputSchema`](/reference/interfaces/inputschema/)\ & `WSHookCapture` | | `Schema` *extends* `IntersectIfObjectSchema`<`MergeSchema`<[`UnwrapRoute`](/reference/interfaces/unwraproute/)<`Input`, `Definitions`\[`"typebox"`], `JoinPath`<`BasePath`, `Path`>>, `MergeSchema`<`Volatile`\[`"schema"`], `MergeSchema`<`Ephemeral`\[`"schema"`], `Metadata`\[`"schema"`]>>, `""`, `undefined` *extends* `Input`\[`"params"`] ? `true` : `false`>, `MergeScopedSchemas`<`Metadata`\[`"schemas"`], `Ephemeral`\[`"schemas"`], `Volatile`\[`"schemas"`]>> | | `MacroContext` *extends* \| [`Macro`](/reference/interfaces/macro/)<{ }, { }, { }, [`DefaultSingleton`](/reference/interfaces/defaultsingleton/), \[]> \| { \[K in string \| number \| symbol]: (InnerMacroToContext\>, Definitions\[“typebox”], \[]> extends A ? { \[K in string \| number \| symbol]: K extends “derive” ? UnionToIntersect\ : K extends “meta” ? ((…)\[(…)] & { value: … })\[“value”] : A\[K] } & { return: FlattenMacroResponse\ } : {})\[K] } | | `Handler` *extends* `WSMessageHandler`<`Schema` & `MacroContext`, `Singleton` & `object`> | ##### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `path` | `Path` | | `options` | { \[K in string \| number \| symbol]: Input\[K] } & `object` & `TypedWebSocketHandler`<`Omit`<{ \[K in string \| number \| symbol]: ({ body: PrettifyIfObject<(…)\[(…)] & (…)\[(…)]>; cookie: undefined extends ((…) & (…))\[“cookie”] ? Record\> : Record<(…), (…)> & { \[K in (…)]: (…) }; headers: undefined extends ((…) & (…))\[“headers”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)>; params: undefined extends ((…) & (…))\[“params”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)>; query: undefined extends ((…) & (…))\[“query”] ? {} extends NonNullable<(…)> ? Record<(…), (…)> : (…)\[(…)] : PrettifyIfObject<(…) & (…)> } & ContextBase\ & (Singleton & { derive: (…) & (…) & (…) })\[“decorator”] & Omit<(Singleton & { derive: … })\[“derive”], keyof InputSchema\>)\[K] }, `"body"`> & `Omit`<`ElysiaWSLike`<{ \[K in string \| number \| symbol]: ({ body: PrettifyIfObject<(…) & (…)>; cookie: undefined extends (…)\[(…)] ? Record<(…), (…)> : (…) & (…); headers: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)>; params: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)>; query: undefined extends (…)\[(…)] ? (…) extends (…) ? (…) : (…) : PrettifyIfObject<(…)> } & ContextBase\ & (Singleton & { derive: … })\[“decorator”] & Omit<((…) & (…))\[“derive”], keyof InputSchema<(…)>>)\[K] }, `Schema` & `MacroContext`>, `"body"`> & `object`, `Schema` & `MacroContext`> & `MacroOptionContext`<`Metadata`\[`"macroFn"`], `Schema`, `GuardHookSingleton`<`Singleton`, `Ephemeral`, `Volatile`, { `derive`: { }; }>> | | `handler` | `Handler` | ##### Returns `AddWSRoute`<`BasePath`, `Scope`, `Singleton`, `Definitions`, `Metadata`, `Routes`, `Ephemeral`, `Volatile`, `Path`, `Schema`, `MacroContext`, `WSHandlerResponse`<`Handler`>, `Input`\[`"error"`], `Input`\[`"beforeHandle"`]> --- Source: [reference/classes/elysiaerror/index.md](reference/classes/elysiaerror/index.md) # ElysiaError ## Members ### Properties | Property | Description | | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/elysiaerror/#cause) | The cause of the error. | | [code](/reference/classes/elysiaerror/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [message](/reference/classes/elysiaerror/#message) | - | | [name](/reference/classes/elysiaerror/#name) | - | | [response](/reference/classes/elysiaerror/#response) | - | | [stack](/reference/classes/elysiaerror/#stack) | - | | [status](/reference/classes/elysiaerror/#status) | - | | [type](/reference/classes/elysiaerror/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [stackTraceLimit](/reference/classes/elysiaerror/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Methods | Method | Description | | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [toResponse](/reference/classes/elysiaerror/#toresponse) | - | | [captureStackTrace](/reference/classes/elysiaerror/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/elysiaerror/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/elysiaerror/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:6 ## Extends - `Error` ## Extended by - [`InternalServerError`](/reference/classes/internalservererror/) - [`InvalidCookie`](/reference/classes/invalidcookie/) - [`NotFound`](/reference/classes/notfound/) - [`ParseError`](/reference/classes/parseerror/) - [`ValidationError`](/reference/classes/validationerror/) ## Type Parameters | Type Parameter | Default type | | ----------------------------- | ------------ | | `Status` *extends* `number` | `number` | | `Response` *extends* `string` | `string` | ## Constructors ### Constructor ```ts new ElysiaError(message, cause?): ElysiaError; ``` Defined in: elysia/dist/error.d.ts:20 #### Parameters | Parameter | Type | | --------- | -------- | | `message` | `string` | | `cause?` | `Error` | #### Returns `ElysiaError`<`Status`, `Response`> #### Overrides ```ts Error.constructor ``` ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from ```ts Error.cause ``` *** ### code? ```ts readonly optional code?: string; ``` Defined in: elysia/dist/error.d.ts:13 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from ```ts Error.message ``` *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from ```ts Error.name ``` *** ### response? ```ts optional response?: Response; ``` Defined in: elysia/dist/error.d.ts:8 *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from ```ts Error.stack ``` *** ### status? ```ts optional status?: Status; ``` Defined in: elysia/dist/error.d.ts:7 *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/error.d.ts:19 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from ```ts Error.stackTraceLimit ``` ## Methods ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:21 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from ```ts Error.captureStackTrace ``` #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from ```ts Error.captureStackTrace ``` *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from ```ts Error.isError ``` #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from ```ts Error.isError ``` *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from ```ts Error.prepareStackTrace ``` --- Source: [reference/classes/elysiafile/index.md](reference/classes/elysiafile/index.md) # ElysiaFile ## Members ### Properties | Property | Description | | ------------------------------------------- | ----------- | | [path](/reference/classes/elysiafile/#path) | - | ### Accessors | Accessor | Description | | --------------------------------------------- | ----------- | | [stats](/reference/classes/elysiafile/#stats) | - | | [type](/reference/classes/elysiafile/#type) | - | | [value](/reference/classes/elysiafile/#value) | - | ### Methods | Method | Description | | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | [slice](/reference/classes/elysiafile/#slice) | - | | [isElysiaFile](/reference/classes/elysiafile/#iselysiafile) | Brand check on the private field: true for subclasses, and unlike `instanceof` it never walks the prototype chain, so no user trap runs | Defined in: elysia/dist/universal/file.d.ts:44 ## Constructors ### Constructor ```ts new ElysiaFile(path): ElysiaFile; ``` Defined in: elysia/dist/universal/file.d.ts:47 #### Parameters | Parameter | Type | | --------- | -------- | | `path` | `string` | #### Returns `ElysiaFile` ## Properties ### path ```ts path: string; ``` Defined in: elysia/dist/universal/file.d.ts:46 ## Accessors ### stats #### Get Signature ```ts get stats(): Promise; ``` Defined in: elysia/dist/universal/file.d.ts:53 ##### Returns `Promise`<`Stats`> *** ### type #### Get Signature ```ts get type(): any; ``` Defined in: elysia/dist/universal/file.d.ts:56 ##### Returns `any` *** ### value #### Get Signature ```ts get value(): BunFile | ReadStream; ``` Defined in: elysia/dist/universal/file.d.ts:54 ##### Returns `BunFile` | `ReadStream` ## Methods ### slice() ```ts slice(start, end): BunFile | ReadStream; ``` Defined in: elysia/dist/universal/file.d.ts:55 #### Parameters | Parameter | Type | | --------- | -------- | | `start` | `number` | | `end` | `number` | #### Returns `BunFile` | `ReadStream` *** ### isElysiaFile() ```ts static isElysiaFile(value): value is ElysiaFile; ``` Defined in: elysia/dist/universal/file.d.ts:52 Brand check on the private field: true for subclasses, and unlike `instanceof` it never walks the prototype chain, so no user trap runs #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `value is ElysiaFile` --- Source: [reference/classes/elysiastatus/index.md](reference/classes/elysiastatus/index.md) # ElysiaStatus ## Members ### Properties | Property | Description | | ----------------------------------------------------- | ----------- | | [headers](/reference/classes/elysiastatus/#headers) | - | | [response](/reference/classes/elysiastatus/#response) | - | | [status](/reference/classes/elysiastatus/#status) | - | Defined in: elysia/dist/error.d.ts:124 ## Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | `Code` *extends* `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | - | | `T` | [`StatusResponse`](/reference/type-aliases/statusresponse/)<`Code`> | | `Status` *extends* [`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`> | [`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`> | ## Constructors ### Constructor ```ts new ElysiaStatus( code, res, headers? ): ElysiaStatus; ``` Defined in: elysia/dist/error.d.ts:136 #### Parameters | Parameter | Type | | ---------- | ---------------------------- | | `code` | `Code` | | `res` | `T` | | `headers?` | `Record`<`string`, `string`> | #### Returns `ElysiaStatus`<`Code`, `T`, `Status`> ## Properties ### headers? ```ts optional headers?: Record; ``` Defined in: elysia/dist/error.d.ts:135 *** ### response ```ts response: T; ``` Defined in: elysia/dist/error.d.ts:134 *** ### status ```ts status: Status; ``` Defined in: elysia/dist/error.d.ts:133 --- Source: [reference/classes/httperror/index.md](reference/classes/httperror/index.md) # HTTPError ## Members ### Properties | Property | Description | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/httperror/#cause) | The cause of the error. | | [code](/reference/classes/httperror/#code) | Stable machine-readable token served in the `code` member, set by [HTTPError.id](/reference/classes/httperror/#id) to its first argument | | [detail](/reference/classes/httperror/#detail) | `detail` member of the RFC 9457 problem document, served in place of the error message | | [headers](/reference/classes/httperror/#headers) | Headers merged into `set.headers` when the error is served | | [message](/reference/classes/httperror/#message) | - | | [name](/reference/classes/httperror/#name) | - | | [stack](/reference/classes/httperror/#stack) | - | | [status](/reference/classes/httperror/#status) | HTTP status served when the error reaches the error pipeline, as a number or a status name (`'Payment Required'`) | | [type](/reference/classes/httperror/#type) | RFC 9457 problem type served in the `type` member, a URI or a slug. Doubles as the tag that discriminates one error class from another | | [stackTraceLimit](/reference/classes/httperror/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | | [typeBase](/reference/classes/httperror/#typebase) | Absolute base for the RFC 9457 `type` URI, applied to every class made by [HTTPError.id](/reference/classes/httperror/#id) and to the built-in `ElysiaError`s (`NotFound`, `ParseError`, `InvalidCookie`, `InternalServerError`) both derive `type` from the same slug. Request validation is the exception, it keeps `type: 'validation'` unprefixed | ### Methods | Method | Description | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [extension](/reference/classes/httperror/#extension) | Return RFC 9457 extension members added to the problem document, synchronously | | [value](/reference/classes/httperror/#value) | Return a value to serve as the whole response, may be `async` | | [captureStackTrace](/reference/classes/httperror/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [id](/reference/classes/httperror/#id) | Create a concrete `HTTPError` subclass carrying `code`, mirrored into `type` unless [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set | | [isError](/reference/classes/httperror/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/httperror/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:271 Self-describing HTTP error Set `status`, `headers`, `detail`, `extension` and `value` to map to response Send RFC 9457 problem document by default that can be overridden by `value()` - `detail` - add `detail` field to the problem document - `extension()` - add extension members to the problem document - `value()` - override the whole response, return `problem()` to keep the shape `value` wins over `detail`, `detail` over an `extension()` string `detail`, that over message. A `value` returning `undefined` falls through to the next one. A response that depends on the request belongs in `.onError(Class, handler)` ## Example ```ts class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT') { readonly status = 402 readonly detail = 'Top up to continue' } class Overdrawn extends Error implements HTTPError { readonly type = 'OVERDRAWN' readonly status = 402 value() { return problem(402, { type: 'OVERDRAWN', detail: await describeBalance() }) } } ``` ## See ## Since 2.0.0 ## Extends - `Error` ## Type Parameters | Type Parameter | Default type | | ---------------------- | ------------ | | `T` *extends* `string` | `string` | ## Constructors ### Constructor ```ts new HTTPError(message?): HTTPError; ``` Defined in: typescript/lib/lib.es5.d.ts:1080 #### Parameters | Parameter | Type | | ---------- | -------- | | `message?` | `string` | #### Returns `HTTPError` #### Inherited from ```ts Error.constructor ``` ### Constructor ```ts new HTTPError(message?, options?): HTTPError; ``` Defined in: typescript/lib/lib.es5.d.ts:1080 #### Parameters | Parameter | Type | | ---------- | -------------- | | `message?` | `string` | | `options?` | `ErrorOptions` | #### Returns `HTTPError` #### Inherited from ```ts Error.constructor ``` ### Constructor ```ts new HTTPError(message?, options?): HTTPError; ``` Defined in: typescript/lib/lib.es5.d.ts:1080 #### Parameters | Parameter | Type | | ---------- | -------------- | | `message?` | `string` | | `options?` | `ErrorOptions` | #### Returns `HTTPError` #### Inherited from ```ts Error.constructor ``` ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from ```ts Error.cause ``` *** ### code? ```ts readonly optional code?: string; ``` Defined in: elysia/dist/error.d.ts:287 Stable machine-readable token served in the `code` member, set by [HTTPError.id](/reference/classes/httperror/#id) to its first argument Unlike `type` it is never widened to a URI, so it stays the value to dispatch on *** ### detail? ```ts readonly optional detail?: string; ``` Defined in: elysia/dist/error.d.ts:324 `detail` member of the RFC 9457 problem document, served in place of the error message Use `value()` for anything computed asynchronously *** ### headers? ```ts readonly optional headers?: Record; ``` Defined in: elysia/dist/error.d.ts:311 Headers merged into `set.headers` when the error is served *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from ```ts Error.message ``` *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from ```ts Error.name ``` *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from ```ts Error.stack ``` *** ### status? ```ts readonly optional status?: | number | "Continue" | "Switching Protocols" | "Processing" | "Early Hints" | "OK" | "Created" | "Accepted" | "Non-Authoritative Information" | "No Content" | "Reset Content" | "Partial Content" | "Multi-Status" | "Already Reported" | "Multiple Choices" | "Moved Permanently" | "Found" | "See Other" | "Not Modified" | "Temporary Redirect" | "Permanent Redirect" | "Bad Request" | "Unauthorized" | "Payment Required" | "Forbidden" | "Not Found" | "Method Not Allowed" | "Not Acceptable" | "Proxy Authentication Required" | "Request Timeout" | "Conflict" | "Gone" | "Length Required" | "Precondition Failed" | "Payload Too Large" | "URI Too Long" | "Unsupported Media Type" | "Range Not Satisfiable" | "Expectation Failed" | "I'm a teapot" | "Enhance Your Calm" | "Misdirected Request" | "Unprocessable Content" | "Locked" | "Failed Dependency" | "Too Early" | "Upgrade Required" | "Precondition Required" | "Too Many Requests" | "Request Header Fields Too Large" | "Unavailable For Legal Reasons" | "Internal Server Error" | "Not Implemented" | "Bad Gateway" | "Service Unavailable" | "Gateway Timeout" | "HTTP Version Not Supported" | "Variant Also Negotiates" | "Insufficient Storage" | "Loop Detected" | "Not Extended" | "Network Authentication Required"; ``` Defined in: elysia/dist/error.d.ts:309 HTTP status served when the error reaches the error pipeline, as a number or a status name (`'Payment Required'`) *** ### type ```ts abstract type: T; ``` Defined in: elysia/dist/error.d.ts:279 RFC 9457 problem type served in the `type` member, a URI or a slug. Doubles as the tag that discriminates one error class from another On a [HTTPError.id](/reference/classes/httperror/#id) class this mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from ```ts Error.stackTraceLimit ``` *** ### typeBase? ```ts static optional typeBase?: string; ``` Defined in: elysia/dist/error.d.ts:304 Absolute base for the RFC 9457 `type` URI, applied to every class made by [HTTPError.id](/reference/classes/httperror/#id) and to the built-in `ElysiaError`s (`NotFound`, `ParseError`, `InvalidCookie`, `InternalServerError`) both derive `type` from the same slug. Request validation is the exception, it keeps `type: 'validation'` unprefixed #### Example ```ts HTTPError.typeBase = 'https://example.com/errors' class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {} // → { type: 'https://example.com/errors/OUT_OF_CREDIT', // code: 'OUT_OF_CREDIT', … } ``` ## Methods ### extension()? ```ts optional extension(): Record; ``` Defined in: elysia/dist/error.d.ts:350 Return RFC 9457 extension members added to the problem document, synchronously `type`, `code` and `status` stay the error’s own. A string `detail` is served when the error has no `detail` of its own, in place of the message. `title` and `instance` may be overridden. Not served when `value()` answers Only a plain object’s own data members are served, which types can’t check: return an object literal, not a class instance #### Returns `Record`<`string`, `unknown`> #### Example ```ts class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) { constructor(readonly balance: number) { super() } extension() { return { balance: this.balance, accounts: ['/account/12345'] } } } ``` *** ### value()? ```ts optional value(): unknown; ``` Defined in: elysia/dist/error.d.ts:317 Return a value to serve as the whole response, may be `async` Supports `status()`/`problem()` for status-based Eden response #### Returns `unknown` *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from ```ts Error.captureStackTrace ``` #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from ```ts Error.captureStackTrace ``` *** ### id() #### Call Signature ```ts static id(code): TaggedHTTPError; ``` Defined in: elysia/dist/error.d.ts:368 Create a concrete `HTTPError` subclass carrying `code`, mirrored into `type` unless [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set `code` carries no status annotation, annotate `status` in the subclass body as a number or a status name ##### Type Parameters | Type Parameter | | ------------------------- | | `Type` *extends* `string` | ##### Parameters | Parameter | Type | | --------- | ------ | | `code` | `Type` | ##### Returns [`TaggedHTTPError`](/reference/type-aliases/taggedhttperror/)<`Type`> ##### Example ```ts class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT') { override readonly status = 402 headers = { 'x-powered-by': 'Elysia' } } ``` #### Call Signature ```ts static id(code, status): TaggedHTTPError; ``` Defined in: elysia/dist/error.d.ts:384 Create a concrete `HTTPError` subclass carrying `code`, annotated with `status` as a number or a status name Annotate what is served with `detail` (problem `detail` member) or `value()` (whole-response override, may be `async`) ##### Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Type` *extends* `string` | | `S` *extends* \| `number` \| `"Continue"` \| `"Switching Protocols"` \| `"Processing"` \| `"Early Hints"` \| `"OK"` \| `"Created"` \| `"Accepted"` \| `"Non-Authoritative Information"` \| `"No Content"` \| `"Reset Content"` \| `"Partial Content"` \| `"Multi-Status"` \| `"Already Reported"` \| `"Multiple Choices"` \| `"Moved Permanently"` \| `"Found"` \| `"See Other"` \| `"Not Modified"` \| `"Temporary Redirect"` \| `"Permanent Redirect"` \| `"Bad Request"` \| `"Unauthorized"` \| `"Payment Required"` \| `"Forbidden"` \| `"Not Found"` \| `"Method Not Allowed"` \| `"Not Acceptable"` \| `"Proxy Authentication Required"` \| `"Request Timeout"` \| `"Conflict"` \| `"Gone"` \| `"Length Required"` \| `"Precondition Failed"` \| `"Payload Too Large"` \| `"URI Too Long"` \| `"Unsupported Media Type"` \| `"Range Not Satisfiable"` \| `"Expectation Failed"` \| `"I'm a teapot"` \| `"Enhance Your Calm"` \| `"Misdirected Request"` \| `"Unprocessable Content"` \| `"Locked"` \| `"Failed Dependency"` \| `"Too Early"` \| `"Upgrade Required"` \| `"Precondition Required"` \| `"Too Many Requests"` \| `"Request Header Fields Too Large"` \| `"Unavailable For Legal Reasons"` \| `"Internal Server Error"` \| `"Not Implemented"` \| `"Bad Gateway"` \| `"Service Unavailable"` \| `"Gateway Timeout"` \| `"HTTP Version Not Supported"` \| `"Variant Also Negotiates"` \| `"Insufficient Storage"` \| `"Loop Detected"` \| `"Not Extended"` \| `"Network Authentication Required"` | ##### Parameters | Parameter | Type | | --------- | ------ | | `code` | `Type` | | `status` | `S` | ##### Returns [`TaggedHTTPError`](/reference/type-aliases/taggedhttperror/)<`Type`, { `status`: `S` *extends* | `"Continue"` | `"Switching Protocols"` | `"Processing"` | `"Early Hints"` | `"OK"` | `"Created"` | `"Accepted"` | `"Non-Authoritative Information"` | `"No Content"` | `"Reset Content"` | `"Partial Content"` | `"Multi-Status"` | `"Already Reported"` | `"Multiple Choices"` | `"Moved Permanently"` | `"Found"` | `"See Other"` | `"Not Modified"` | `"Temporary Redirect"` | `"Permanent Redirect"` | `"Bad Request"` | `"Unauthorized"` | `"Payment Required"` | `"Forbidden"` | `"Not Found"` | `"Method Not Allowed"` | `"Not Acceptable"` | `"Proxy Authentication Required"` | `"Request Timeout"` | `"Conflict"` | `"Gone"` | `"Length Required"` | `"Precondition Failed"` | `"Payload Too Large"` | `"URI Too Long"` | `"Unsupported Media Type"` | `"Range Not Satisfiable"` | `"Expectation Failed"` | `"I'm a teapot"` | `"Enhance Your Calm"` | `"Misdirected Request"` | `"Unprocessable Content"` | `"Locked"` | `"Failed Dependency"` | `"Too Early"` | `"Upgrade Required"` | `"Precondition Required"` | `"Too Many Requests"` | `"Request Header Fields Too Large"` | `"Unavailable For Legal Reasons"` | `"Internal Server Error"` | `"Not Implemented"` | `"Bad Gateway"` | `"Service Unavailable"` | `"Gateway Timeout"` | `"HTTP Version Not Supported"` | `"Variant Also Negotiates"` | `"Insufficient Storage"` | `"Loop Detected"` | `"Not Extended"` | `"Network Authentication Required"` ? `object`\[`S`] : `S`; }> ##### Example ```ts class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {} class Denied extends HTTPError.id('DENIED', 'Payment Required') { override readonly detail = 'Top up to continue' } ``` *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from ```ts Error.isError ``` #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from ```ts Error.isError ``` *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from ```ts Error.prepareStackTrace ``` --- Source: [reference/classes/internalservererror/index.md](reference/classes/internalservererror/index.md) # InternalServerError ## Members ### Properties | Property | Description | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/internalservererror/#cause) | The cause of the error. | | [code](/reference/classes/internalservererror/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [message](/reference/classes/internalservererror/#message) | - | | [name](/reference/classes/internalservererror/#name) | - | | [response](/reference/classes/internalservererror/#response) | - | | [stack](/reference/classes/internalservererror/#stack) | - | | [status](/reference/classes/internalservererror/#status) | - | | [type](/reference/classes/internalservererror/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [stackTraceLimit](/reference/classes/internalservererror/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Methods | Method | Description | | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [toResponse](/reference/classes/internalservererror/#toresponse) | - | | [captureStackTrace](/reference/classes/internalservererror/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/internalservererror/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/internalservererror/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:40 ## Extends - [`ElysiaError`](/reference/classes/elysiaerror/) ## Constructors ### Constructor ```ts new InternalServerError(response?, cause?): InternalServerError; ``` Defined in: elysia/dist/error.d.ts:44 #### Parameters | Parameter | Type | | ----------- | -------- | | `response?` | `string` | | `cause?` | `Error` | #### Returns `InternalServerError` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`constructor`](/reference/classes/elysiaerror/#constructor) ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`cause`](/reference/classes/elysiaerror/#cause) *** ### code ```ts readonly code: "internal-server-error" = "internal-server-error"; ``` Defined in: elysia/dist/error.d.ts:43 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`code`](/reference/classes/elysiaerror/#code) *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`message`](/reference/classes/elysiaerror/#message) *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`name`](/reference/classes/elysiaerror/#name) *** ### response ```ts response: string; ``` Defined in: elysia/dist/error.d.ts:41 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`response`](/reference/classes/elysiaerror/#response) *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stack`](/reference/classes/elysiaerror/#stack) *** ### status ```ts status: 500; ``` Defined in: elysia/dist/error.d.ts:42 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`status`](/reference/classes/elysiaerror/#status) *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/error.d.ts:19 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`type`](/reference/classes/elysiaerror/#type) *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stackTraceLimit`](/reference/classes/elysiaerror/#stacktracelimit) ## Methods ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:21 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`toResponse`](/reference/classes/elysiaerror/#toresponse) *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`prepareStackTrace`](/reference/classes/elysiaerror/#preparestacktrace) --- Source: [reference/classes/invalidcookie/index.md](reference/classes/invalidcookie/index.md) # InvalidCookie ## Members ### Properties | Property | Description | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/invalidcookie/#cause) | The cause of the error. | | [code](/reference/classes/invalidcookie/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [key](/reference/classes/invalidcookie/#key) | - | | [message](/reference/classes/invalidcookie/#message) | - | | [name](/reference/classes/invalidcookie/#name) | - | | [response](/reference/classes/invalidcookie/#response) | - | | [stack](/reference/classes/invalidcookie/#stack) | - | | [status](/reference/classes/invalidcookie/#status) | - | | [type](/reference/classes/invalidcookie/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [stackTraceLimit](/reference/classes/invalidcookie/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Methods | Method | Description | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [toResponse](/reference/classes/invalidcookie/#toresponse) | - | | [captureStackTrace](/reference/classes/invalidcookie/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/invalidcookie/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/invalidcookie/#preparestacktrace) | - | | [secret](/reference/classes/invalidcookie/#secret) | - | | [signature](/reference/classes/invalidcookie/#signature) | - | Defined in: elysia/dist/cookie/error.d.ts:4 ## Extends - [`ElysiaError`](/reference/classes/elysiaerror/) ## Constructors ### Constructor ```ts new InvalidCookie( key?, response?, status? ): InvalidCookie; ``` Defined in: elysia/dist/cookie/error.d.ts:9 #### Parameters | Parameter | Type | | ----------- | -------- | | `key?` | `string` | | `response?` | `string` | | `status?` | `number` | #### Returns `InvalidCookie` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`constructor`](/reference/classes/elysiaerror/#constructor) ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`cause`](/reference/classes/elysiaerror/#cause) *** ### code ```ts readonly code: "invalid-cookie" = "invalid-cookie"; ``` Defined in: elysia/dist/cookie/error.d.ts:8 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`code`](/reference/classes/elysiaerror/#code) *** ### key? ```ts optional key?: string; ``` Defined in: elysia/dist/cookie/error.d.ts:5 *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`message`](/reference/classes/elysiaerror/#message) *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`name`](/reference/classes/elysiaerror/#name) *** ### response ```ts response: string; ``` Defined in: elysia/dist/cookie/error.d.ts:6 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`response`](/reference/classes/elysiaerror/#response) *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stack`](/reference/classes/elysiaerror/#stack) *** ### status ```ts status: 400; ``` Defined in: elysia/dist/cookie/error.d.ts:7 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`status`](/reference/classes/elysiaerror/#status) *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/error.d.ts:19 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`type`](/reference/classes/elysiaerror/#type) *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stackTraceLimit`](/reference/classes/elysiaerror/#stacktracelimit) ## Methods ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:21 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`toResponse`](/reference/classes/elysiaerror/#toresponse) *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`prepareStackTrace`](/reference/classes/elysiaerror/#preparestacktrace) *** ### secret() ```ts static secret(key?): Omit & object; ``` Defined in: elysia/dist/cookie/error.d.ts:11 #### Parameters | Parameter | Type | | --------- | -------- | | `key?` | `string` | #### Returns `Omit`<`InvalidCookie`, `"status"`> & `object` *** ### signature() ```ts static signature(key): InvalidCookie; ``` Defined in: elysia/dist/cookie/error.d.ts:10 #### Parameters | Parameter | Type | | --------- | -------- | | `key` | `string` | #### Returns `InvalidCookie` --- Source: [reference/classes/multivalidator/index.md](reference/classes/multivalidator/index.md) # MultiValidator ## Members ### Properties | Property | Description | | ----------------------------------------------------------------------- | ----------- | | [Clean](/reference/classes/multivalidator/#clean) | - | | [isAsync](/reference/classes/multivalidator/#isasync) | - | | [mayReturnPromise](/reference/classes/multivalidator/#mayreturnpromise) | - | ### Methods | Method | Description | | ----------------------------------------------------------- | ----------- | | [Check](/reference/classes/multivalidator/#check) | - | | [EncodeFrom](/reference/classes/multivalidator/#encodefrom) | - | | [Errors](/reference/classes/multivalidator/#errors) | - | | [From](/reference/classes/multivalidator/#from) | - | | [clear](/reference/classes/multivalidator/#clear) | - | | [create](/reference/classes/multivalidator/#create) | - | | [reference](/reference/classes/multivalidator/#reference) | - | | [response](/reference/classes/multivalidator/#response) | - | Defined in: elysia/dist/validator/index.d.ts:53 ## Extends - [`Validator`](/reference/classes/validator/) ## Constructors ### Constructor ```ts new MultiValidator(schema, options): MultiValidator; ``` Defined in: elysia/dist/validator/index.d.ts:56 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------- | | `schema` | \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | | `options` | `ValidatorOptions` | #### Returns `MultiValidator` #### Overrides [`Validator`](/reference/classes/validator/).[`constructor`](/reference/classes/validator/#constructor) ## Properties ### Clean ```ts Clean: (value) => unknown; ``` Defined in: elysia/dist/validator/index.d.ts:37 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` #### Inherited from [`Validator`](/reference/classes/validator/).[`Clean`](/reference/classes/validator/#clean) *** ### isAsync ```ts isAsync: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:55 #### Overrides [`Validator`](/reference/classes/validator/).[`isAsync`](/reference/classes/validator/#isasync) *** ### mayReturnPromise ```ts mayReturnPromise: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:32 #### Inherited from [`Validator`](/reference/classes/validator/).[`mayReturnPromise`](/reference/classes/validator/#mayreturnpromise) ## Methods ### Check() ```ts Check(value): boolean; ``` Defined in: elysia/dist/validator/index.d.ts:57 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `boolean` #### Overrides [`Validator`](/reference/classes/validator/).[`Check`](/reference/classes/validator/#check) *** ### EncodeFrom() ```ts EncodeFrom(value): unknown; ``` Defined in: elysia/dist/validator/index.d.ts:59 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` *** ### Errors() ```ts Errors(value): TLocalizedValidationError[]; ``` Defined in: elysia/dist/validator/index.d.ts:58 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `TLocalizedValidationError`\[] #### Overrides [`Validator`](/reference/classes/validator/).[`Errors`](/reference/classes/validator/#errors) *** ### From() ```ts From( value, type?, allowAsync? ): unknown; ``` Defined in: elysia/dist/validator/index.d.ts:60 #### Parameters | Parameter | Type | | ------------- | --------- | | `value` | `unknown` | | `type?` | `string` | | `allowAsync?` | `boolean` | #### Returns `unknown` #### Overrides [`Validator`](/reference/classes/validator/).[`From`](/reference/classes/validator/#from) *** ### clear() ```ts static clear(): void; ``` Defined in: elysia/dist/validator/index.d.ts:42 #### Returns `void` #### Inherited from [`Validator`](/reference/classes/validator/).[`clear`](/reference/classes/validator/#clear) *** ### create() #### Call Signature ```ts static create(schema, options?): TypeBoxValidator; ``` Defined in: elysia/dist/validator/index.d.ts:38 ##### Type Parameters | Type Parameter | | ---------------------------- | | `Schema` *extends* `TSchema` | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns `TypeBoxValidator`<`Schema`> ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): StandardValidator; ``` Defined in: elysia/dist/validator/index.d.ts:39 ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------- | | `Schema` *extends* [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns [`StandardValidator`](/reference/classes/standardvalidator/) ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): Validator; ``` Defined in: elysia/dist/validator/index.d.ts:40 ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `null` | | `options?` | `ValidatorOptions` | ##### Returns [`Validator`](/reference/classes/validator/) ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) *** ### reference() ```ts static reference(schema, models): AnySchema; ``` Defined in: elysia/dist/validator/index.d.ts:35 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `string` \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | | `models` | `Record`<`string` \| `number` \| `symbol`, [`AnySchema`](/reference/type-aliases/anyschema/)> | #### Returns [`AnySchema`](/reference/type-aliases/anyschema/) #### Inherited from [`Validator`](/reference/classes/validator/).[`reference`](/reference/classes/validator/#reference) *** ### response() ```ts static response(schema, options?): Map; ``` Defined in: elysia/dist/validator/index.d.ts:41 #### Parameters | Parameter | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> \| `Record`<`number`, \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`>> | | `options?` | `ResponseValidatorOptions` | #### Returns `Map`<`number`, [`Validator`](/reference/classes/validator/)> #### Inherited from [`Validator`](/reference/classes/validator/).[`response`](/reference/classes/validator/#response) --- Source: [reference/classes/notfound/index.md](reference/classes/notfound/index.md) # NotFound ## Members ### Properties | Property | Description | | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/notfound/#cause) | The cause of the error. | | [code](/reference/classes/notfound/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [message](/reference/classes/notfound/#message) | - | | [name](/reference/classes/notfound/#name) | - | | [response](/reference/classes/notfound/#response) | - | | [stack](/reference/classes/notfound/#stack) | - | | [status](/reference/classes/notfound/#status) | - | | [type](/reference/classes/notfound/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [stackTraceLimit](/reference/classes/notfound/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Methods | Method | Description | | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [toResponse](/reference/classes/notfound/#toresponse) | - | | [captureStackTrace](/reference/classes/notfound/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/notfound/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/notfound/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:46 ## Extends - [`ElysiaError`](/reference/classes/elysiaerror/) ## Constructors ### Constructor ```ts new NotFound(response?): NotFound; ``` Defined in: elysia/dist/error.d.ts:50 #### Parameters | Parameter | Type | | ----------- | -------- | | `response?` | `string` | #### Returns `NotFound` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`constructor`](/reference/classes/elysiaerror/#constructor) ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`cause`](/reference/classes/elysiaerror/#cause) *** ### code ```ts readonly code: "not-found" = "not-found"; ``` Defined in: elysia/dist/error.d.ts:49 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`code`](/reference/classes/elysiaerror/#code) *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`message`](/reference/classes/elysiaerror/#message) *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`name`](/reference/classes/elysiaerror/#name) *** ### response ```ts response: string; ``` Defined in: elysia/dist/error.d.ts:47 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`response`](/reference/classes/elysiaerror/#response) *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stack`](/reference/classes/elysiaerror/#stack) *** ### status ```ts status: 404; ``` Defined in: elysia/dist/error.d.ts:48 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`status`](/reference/classes/elysiaerror/#status) *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/error.d.ts:19 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`type`](/reference/classes/elysiaerror/#type) *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stackTraceLimit`](/reference/classes/elysiaerror/#stacktracelimit) ## Methods ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:21 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`toResponse`](/reference/classes/elysiaerror/#toresponse) *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`prepareStackTrace`](/reference/classes/elysiaerror/#preparestacktrace) --- Source: [reference/classes/parseerror/index.md](reference/classes/parseerror/index.md) # ParseError ## Members ### Properties | Property | Description | | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [cause](/reference/classes/parseerror/#cause) | The cause of the error. | | [code](/reference/classes/parseerror/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [message](/reference/classes/parseerror/#message) | - | | [name](/reference/classes/parseerror/#name) | - | | [response](/reference/classes/parseerror/#response) | - | | [stack](/reference/classes/parseerror/#stack) | - | | [status](/reference/classes/parseerror/#status) | - | | [type](/reference/classes/parseerror/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [stackTraceLimit](/reference/classes/parseerror/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Methods | Method | Description | | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [toResponse](/reference/classes/parseerror/#toresponse) | - | | [captureStackTrace](/reference/classes/parseerror/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/parseerror/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/parseerror/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:52 ## Extends - [`ElysiaError`](/reference/classes/elysiaerror/) ## Constructors ### Constructor ```ts new ParseError(cause?): ParseError; ``` Defined in: elysia/dist/error.d.ts:56 #### Parameters | Parameter | Type | | --------- | ------- | | `cause?` | `Error` | #### Returns `ParseError` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`constructor`](/reference/classes/elysiaerror/#constructor) ## Properties ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`cause`](/reference/classes/elysiaerror/#cause) *** ### code ```ts readonly code: "parse" = "parse"; ``` Defined in: elysia/dist/error.d.ts:55 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`code`](/reference/classes/elysiaerror/#code) *** ### message ```ts message: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1075 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`message`](/reference/classes/elysiaerror/#message) *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`name`](/reference/classes/elysiaerror/#name) *** ### response ```ts response: string; ``` Defined in: elysia/dist/error.d.ts:54 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`response`](/reference/classes/elysiaerror/#response) *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stack`](/reference/classes/elysiaerror/#stack) *** ### status ```ts status: 400; ``` Defined in: elysia/dist/error.d.ts:53 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`status`](/reference/classes/elysiaerror/#status) *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/error.d.ts:19 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`type`](/reference/classes/elysiaerror/#type) *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stackTraceLimit`](/reference/classes/elysiaerror/#stacktracelimit) ## Methods ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:57 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`toResponse`](/reference/classes/elysiaerror/#toresponse) *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`prepareStackTrace`](/reference/classes/elysiaerror/#preparestacktrace) --- Source: [reference/classes/standardvalidator/index.md](reference/classes/standardvalidator/index.md) # StandardValidator ## Members ### Properties | Property | Description | | -------------------------------------------------------------------------- | ----------- | | [Clean](/reference/classes/standardvalidator/#clean) | - | | [isAsync](/reference/classes/standardvalidator/#isasync) | - | | [mayReturnPromise](/reference/classes/standardvalidator/#mayreturnpromise) | - | ### Methods | Method | Description | | -------------------------------------------------------------- | ----------- | | [Check](/reference/classes/standardvalidator/#check) | - | | [EncodeFrom](/reference/classes/standardvalidator/#encodefrom) | - | | [Errors](/reference/classes/standardvalidator/#errors) | - | | [From](/reference/classes/standardvalidator/#from) | - | | [clear](/reference/classes/standardvalidator/#clear) | - | | [create](/reference/classes/standardvalidator/#create) | - | | [reference](/reference/classes/standardvalidator/#reference) | - | | [response](/reference/classes/standardvalidator/#response) | - | Defined in: elysia/dist/validator/index.d.ts:44 ## Extends - [`Validator`](/reference/classes/validator/) ## Constructors ### Constructor ```ts new StandardValidator(schema): StandardValidator; ``` Defined in: elysia/dist/validator/index.d.ts:47 #### Parameters | Parameter | Type | | --------- | --------------------------------------------------------------------- | | `schema` | [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/) | #### Returns `StandardValidator` #### Overrides [`Validator`](/reference/classes/validator/).[`constructor`](/reference/classes/validator/#constructor) ## Properties ### Clean ```ts Clean: (value) => unknown; ``` Defined in: elysia/dist/validator/index.d.ts:37 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` #### Inherited from [`Validator`](/reference/classes/validator/).[`Clean`](/reference/classes/validator/#clean) *** ### isAsync ```ts isAsync: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:31 #### Inherited from [`Validator`](/reference/classes/validator/).[`isAsync`](/reference/classes/validator/#isasync) *** ### mayReturnPromise ```ts mayReturnPromise: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:46 #### Overrides [`Validator`](/reference/classes/validator/).[`mayReturnPromise`](/reference/classes/validator/#mayreturnpromise) ## Methods ### Check() ```ts Check(value): boolean; ``` Defined in: elysia/dist/validator/index.d.ts:48 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `boolean` #### Overrides [`Validator`](/reference/classes/validator/).[`Check`](/reference/classes/validator/#check) *** ### EncodeFrom() ```ts EncodeFrom(value): unknown; ``` Defined in: elysia/dist/validator/index.d.ts:50 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` *** ### Errors() ```ts Errors(value): TLocalizedValidationError[]; ``` Defined in: elysia/dist/validator/index.d.ts:49 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `TLocalizedValidationError`\[] #### Overrides [`Validator`](/reference/classes/validator/).[`Errors`](/reference/classes/validator/#errors) *** ### From() ```ts From( value, type?, allowAsync? ): unknown; ``` Defined in: elysia/dist/validator/index.d.ts:51 #### Parameters | Parameter | Type | | ------------- | --------- | | `value` | `unknown` | | `type?` | `string` | | `allowAsync?` | `boolean` | #### Returns `unknown` #### Overrides [`Validator`](/reference/classes/validator/).[`From`](/reference/classes/validator/#from) *** ### clear() ```ts static clear(): void; ``` Defined in: elysia/dist/validator/index.d.ts:42 #### Returns `void` #### Inherited from [`Validator`](/reference/classes/validator/).[`clear`](/reference/classes/validator/#clear) *** ### create() #### Call Signature ```ts static create(schema, options?): TypeBoxValidator; ``` Defined in: elysia/dist/validator/index.d.ts:38 ##### Type Parameters | Type Parameter | | ---------------------------- | | `Schema` *extends* `TSchema` | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns `TypeBoxValidator`<`Schema`> ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): StandardValidator; ``` Defined in: elysia/dist/validator/index.d.ts:39 ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------- | | `Schema` *extends* [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns `StandardValidator` ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): Validator; ``` Defined in: elysia/dist/validator/index.d.ts:40 ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `null` | | `options?` | `ValidatorOptions` | ##### Returns [`Validator`](/reference/classes/validator/) ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) *** ### reference() ```ts static reference(schema, models): AnySchema; ``` Defined in: elysia/dist/validator/index.d.ts:35 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `string` \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | | `models` | `Record`<`string` \| `number` \| `symbol`, [`AnySchema`](/reference/type-aliases/anyschema/)> | #### Returns [`AnySchema`](/reference/type-aliases/anyschema/) #### Inherited from [`Validator`](/reference/classes/validator/).[`reference`](/reference/classes/validator/#reference) *** ### response() ```ts static response(schema, options?): Map; ``` Defined in: elysia/dist/validator/index.d.ts:41 #### Parameters | Parameter | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> \| `Record`<`number`, \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`>> | | `options?` | `ResponseValidatorOptions` | #### Returns `Map`<`number`, [`Validator`](/reference/classes/validator/)> #### Inherited from [`Validator`](/reference/classes/validator/).[`response`](/reference/classes/validator/#response) --- Source: [reference/classes/typeboxvalidator/index.md](reference/classes/typeboxvalidator/index.md) # TypeBoxValidator ## Members ### Properties | Property | Description | | ----------------------------------------------------------------------------- | ----------- | | [Clean](/reference/classes/typeboxvalidator/#clean) | - | | [hasCodec](/reference/classes/typeboxvalidator/#hascodec) | - | | [hasDefault](/reference/classes/typeboxvalidator/#hasdefault) | - | | [isAsync](/reference/classes/typeboxvalidator/#isasync) | - | | [mayReturnPromise](/reference/classes/typeboxvalidator/#mayreturnpromise) | - | | [precomputeSafe](/reference/classes/typeboxvalidator/#precomputesafe) | - | | [reconstructedCheck](/reference/classes/typeboxvalidator/#reconstructedcheck) | - | | [schema](/reference/classes/typeboxvalidator/#schema) | - | | [tb](/reference/classes/typeboxvalidator/#tb) | - | ### Methods | Method | Description | | ------------------------------------------------------------------- | ----------- | | [Check](/reference/classes/typeboxvalidator/#check) | - | | [Decode](/reference/classes/typeboxvalidator/#decode) | - | | [Encode](/reference/classes/typeboxvalidator/#encode) | - | | [EncodeFrom](/reference/classes/typeboxvalidator/#encodefrom) | - | | [Errors](/reference/classes/typeboxvalidator/#errors) | - | | [From](/reference/classes/typeboxvalidator/#from) | - | | [FromAsync](/reference/classes/typeboxvalidator/#fromasync) | - | | [FromSync](/reference/classes/typeboxvalidator/#fromsync) | - | | [clear](/reference/classes/typeboxvalidator/#clear) | - | | [create](/reference/classes/typeboxvalidator/#create) | - | | [isAsyncMember](/reference/classes/typeboxvalidator/#isasyncmember) | - | | [reference](/reference/classes/typeboxvalidator/#reference) | - | | [response](/reference/classes/typeboxvalidator/#response) | - | Defined in: elysia/dist/type/validator/index.d.ts:11 ## Extends - [`Validator`](/reference/classes/validator/) ## Type Parameters | Type Parameter | Default type | | ----------------------- | ------------ | | `T` *extends* `TSchema` | `TAny` | ## Constructors ### Constructor ```ts new TypeBoxValidator( schema, options?, name?, isIntersectable? ): TypeBoxValidator; ``` Defined in: elysia/dist/type/validator/index.d.ts:21 #### Parameters | Parameter | Type | | ------------------ | ------------------ | | `schema` | `T` | | `options?` | `ValidatorOptions` | | `name?` | `string` | | `isIntersectable?` | `boolean` | #### Returns `TypeBoxValidator`<`T`> #### Overrides [`Validator`](/reference/classes/validator/).[`constructor`](/reference/classes/validator/#constructor) ## Properties ### Clean ```ts Clean: (value) => unknown; ``` Defined in: elysia/dist/validator/index.d.ts:37 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` #### Inherited from [`Validator`](/reference/classes/validator/).[`Clean`](/reference/classes/validator/#clean) *** ### hasCodec ```ts hasCodec: boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:17 *** ### hasDefault ```ts hasDefault: boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:19 *** ### isAsync ```ts isAsync: boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:18 #### Overrides [`Validator`](/reference/classes/validator/).[`isAsync`](/reference/classes/validator/#isasync) *** ### mayReturnPromise ```ts mayReturnPromise: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:32 #### Inherited from [`Validator`](/reference/classes/validator/).[`mayReturnPromise`](/reference/classes/validator/#mayreturnpromise) *** ### precomputeSafe ```ts precomputeSafe: boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:20 *** ### reconstructedCheck? ```ts optional reconstructedCheck?: (value) => boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:15 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `boolean` *** ### schema ```ts schema: T; ``` Defined in: elysia/dist/type/validator/index.d.ts:16 *** ### tb? ```ts optional tb?: Validator; ``` Defined in: elysia/dist/type/validator/index.d.ts:14 ## Methods ### Check() ```ts Check(value): boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:22 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | #### Returns `boolean` #### Overrides [`Validator`](/reference/classes/validator/).[`Check`](/reference/classes/validator/#check) *** ### Decode() ```ts Decode(value): StaticType<[]>; ``` Defined in: elysia/dist/type/validator/index.d.ts:24 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | #### Returns `StaticType`<\[]> *** ### Encode() ```ts Encode(value): StaticType<[]>; ``` Defined in: elysia/dist/type/validator/index.d.ts:25 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | #### Returns `StaticType`<\[]> *** ### EncodeFrom() ```ts EncodeFrom(value, type?): StaticType<[]>; ``` Defined in: elysia/dist/type/validator/index.d.ts:26 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | | `type?` | `string` | #### Returns `StaticType`<\[]> *** ### Errors() ```ts Errors(value): TLocalizedValidationError[]; ``` Defined in: elysia/dist/type/validator/index.d.ts:23 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `TLocalizedValidationError`\[] #### Overrides [`Validator`](/reference/classes/validator/).[`Errors`](/reference/classes/validator/#errors) *** ### From() ```ts From(value, type?): MaybePromise>; ``` Defined in: elysia/dist/type/validator/index.d.ts:27 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | | `type?` | `string` | #### Returns `MaybePromise`<`StaticType`<\[], `"Encode"`, { }, { }, `T`>> #### Overrides [`Validator`](/reference/classes/validator/).[`From`](/reference/classes/validator/#from) *** ### FromAsync() ```ts FromAsync(value, type?): Promise>; ``` Defined in: elysia/dist/type/validator/index.d.ts:29 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | | `type?` | `string` | #### Returns `Promise`<`StaticType`<\[], `"Encode"`, { }, { }, `T`>> *** ### FromSync() ```ts FromSync(value, type?): StaticType<[]>; ``` Defined in: elysia/dist/type/validator/index.d.ts:30 #### Parameters | Parameter | Type | | --------- | ----------------- | | `value` | `StaticType`<\[]> | | `type?` | `string` | #### Returns `StaticType`<\[]> *** ### clear() ```ts static clear(): void; ``` Defined in: elysia/dist/validator/index.d.ts:42 #### Returns `void` #### Inherited from [`Validator`](/reference/classes/validator/).[`clear`](/reference/classes/validator/#clear) *** ### create() #### Call Signature ```ts static create(schema, options?): TypeBoxValidator; ``` Defined in: elysia/dist/validator/index.d.ts:38 ##### Type Parameters | Type Parameter | | ---------------------------- | | `Schema` *extends* `TSchema` | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns `TypeBoxValidator`<`Schema`> ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): StandardValidator; ``` Defined in: elysia/dist/validator/index.d.ts:39 ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------- | | `Schema` *extends* [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns [`StandardValidator`](/reference/classes/standardvalidator/) ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) #### Call Signature ```ts static create(schema, options?): Validator; ``` Defined in: elysia/dist/validator/index.d.ts:40 ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `null` | | `options?` | `ValidatorOptions` | ##### Returns [`Validator`](/reference/classes/validator/) ##### Inherited from [`Validator`](/reference/classes/validator/).[`create`](/reference/classes/validator/#create) *** ### isAsyncMember() ```ts static isAsyncMember(schema, compiled): boolean; ``` Defined in: elysia/dist/type/validator/index.d.ts:13 #### Parameters | Parameter | Type | | ---------- | --------- | | `schema` | `TSchema` | | `compiled` | `any` | #### Returns `boolean` *** ### reference() ```ts static reference(schema, models): AnySchema; ``` Defined in: elysia/dist/validator/index.d.ts:35 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `string` \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | | `models` | `Record`<`string` \| `number` \| `symbol`, [`AnySchema`](/reference/type-aliases/anyschema/)> | #### Returns [`AnySchema`](/reference/type-aliases/anyschema/) #### Inherited from [`Validator`](/reference/classes/validator/).[`reference`](/reference/classes/validator/#reference) *** ### response() ```ts static response(schema, options?): Map; ``` Defined in: elysia/dist/validator/index.d.ts:41 #### Parameters | Parameter | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> \| `Record`<`number`, \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`>> | | `options?` | `ResponseValidatorOptions` | #### Returns `Map`<`number`, [`Validator`](/reference/classes/validator/)> #### Inherited from [`Validator`](/reference/classes/validator/).[`response`](/reference/classes/validator/#response) --- Source: [reference/classes/validationerror/index.md](reference/classes/validationerror/index.md) # ValidationError ## Members ### Properties | Property | Description | | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [allowUnsafeValidationDetails](/reference/classes/validationerror/#allowunsafevalidationdetails) | - | | [cause](/reference/classes/validationerror/#cause) | The cause of the error. | | [code](/reference/classes/validationerror/#code) | Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) | | [errors](/reference/classes/validationerror/#errors) | - | | [message](/reference/classes/validationerror/#message) | - | | [name](/reference/classes/validationerror/#name) | - | | [response](/reference/classes/validationerror/#response) | - | | [schema](/reference/classes/validationerror/#schema) | - | | [stack](/reference/classes/validationerror/#stack) | - | | [status](/reference/classes/validationerror/#status) | Response validation is a server error; other validation is a client error. | | [type](/reference/classes/validationerror/#type) | RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below | | [value](/reference/classes/validationerror/#value) | - | | [stackTraceLimit](/reference/classes/validationerror/#stacktracelimit) | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). | ### Accessors | Accessor | Description | | -------------------------------------------------------------- | ----------- | | [all](/reference/classes/validationerror/#all) | - | | [customError](/reference/classes/validationerror/#customerror) | - | | [payload](/reference/classes/validationerror/#payload) | - | ### Methods | Method | Description | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [detail](/reference/classes/validationerror/#detail) | - | | [toResponse](/reference/classes/validationerror/#toresponse) | - | | [captureStackTrace](/reference/classes/validationerror/#capturestacktrace) | Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. | | [isError](/reference/classes/validationerror/#iserror) | Indicates whether the argument provided is a built-in Error instance or not. | | [prepareStackTrace](/reference/classes/validationerror/#preparestacktrace) | - | Defined in: elysia/dist/error.d.ts:61 ## Extends - [`ElysiaError`](/reference/classes/elysiaerror/) ## Constructors ### Constructor ```ts new ValidationError( type, value, errors, schema?, findCustomError? ): ValidationError; ``` Defined in: elysia/dist/error.d.ts:71 #### Parameters | Parameter | Type | | ------------------ | ---------------------------- | | `type` | `string` | | `value` | `unknown` | | `errors` | `any`\[] \| (() => `any`\[]) | | `schema?` | `unknown` | | `findCustomError?` | (`value`) => `object` | #### Returns `ValidationError` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`constructor`](/reference/classes/elysiaerror/#constructor) ## Properties ### allowUnsafeValidationDetails ```ts allowUnsafeValidationDetails: boolean; ``` Defined in: elysia/dist/error.d.ts:70 *** ### cause? ```ts optional cause?: unknown; ``` Defined in: typescript/lib/lib.es2022.error.d.ts:24 The cause of the error. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`cause`](/reference/classes/elysiaerror/#cause) *** ### code ```ts readonly code: "validation" = "validation"; ``` Defined in: elysia/dist/error.d.ts:67 Stable machine-readable token served in the `code` member, the same contract a [HTTPError.id](/reference/classes/httperror/#id) class speaks (set per subclass) #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`code`](/reference/classes/elysiaerror/#code) *** ### errors ```ts errors: any[]; ``` Defined in: elysia/dist/error.d.ts:81 *** ### message ```ts message: string; ``` Defined in: elysia/dist/error.d.ts:69 #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`message`](/reference/classes/elysiaerror/#message) *** ### name ```ts name: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1074 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`name`](/reference/classes/elysiaerror/#name) *** ### response? ```ts optional response?: string; ``` Defined in: elysia/dist/error.d.ts:8 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`response`](/reference/classes/elysiaerror/#response) *** ### schema? ```ts optional schema?: unknown; ``` Defined in: elysia/dist/error.d.ts:68 *** ### stack? ```ts optional stack?: string; ``` Defined in: typescript/lib/lib.es5.d.ts:1076 #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stack`](/reference/classes/elysiaerror/#stack) *** ### status ```ts status: 422 | 500; ``` Defined in: elysia/dist/error.d.ts:66 Response validation is a server error; other validation is a client error. #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`status`](/reference/classes/elysiaerror/#status) *** ### type ```ts type: string; ``` Defined in: elysia/dist/error.d.ts:63 RFC 9457 problem type served in the `type` member. Mirrors `code`, or resolves to `/` once [HTTPError.typeBase](/reference/classes/httperror/#typebase) is set. Defined on the prototype below #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`type`](/reference/classes/elysiaerror/#type) *** ### value ```ts value: unknown; ``` Defined in: elysia/dist/error.d.ts:64 *** ### stackTraceLimit ```ts static stackTraceLimit: number; ``` Defined in: @types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`stackTraceLimit`](/reference/classes/elysiaerror/#stacktracelimit) ## Accessors ### all #### Get Signature ```ts get all(): any[]; ``` Defined in: elysia/dist/error.d.ts:84 ##### Returns `any`\[] *** ### customError #### Get Signature ```ts get customError(): unknown; ``` Defined in: elysia/dist/error.d.ts:82 ##### Returns `unknown` #### Set Signature ```ts set customError(v): void; ``` Defined in: elysia/dist/error.d.ts:83 ##### Parameters | Parameter | Type | | --------- | --------- | | `v` | `unknown` | ##### Returns `void` *** ### payload #### Get Signature ```ts get payload(): | { code: string; status: number; title: string; type: string; } | { code: string; on: string; property: string; status: number; title: string; type: string; } | { code: string; detail: any; errors: any[]; expected: unknown; found: unknown; on: string; property: string; status: number; title: string; type: string; }; ``` Defined in: elysia/dist/error.d.ts:96 ##### Returns \| { `code`: `string`; `status`: `number`; `title`: `string`; `type`: `string`; } | { `code`: `string`; `on`: `string`; `property`: `string`; `status`: `number`; `title`: `string`; `type`: `string`; } | { `code`: `string`; `detail`: `any`; `errors`: `any`\[]; `expected`: `unknown`; `found`: `unknown`; `on`: `string`; `property`: `string`; `status`: `number`; `title`: `string`; `type`: `string`; } ## Methods ### detail() ```ts detail(message): | { errors: any[]; message: unknown; on: string; type: string; } | { errors?: undefined; message: unknown; on: string; type: string; }; ``` Defined in: elysia/dist/error.d.ts:85 #### Parameters | Parameter | Type | | --------- | --------- | | `message` | `unknown` | #### Returns \| { `errors`: `any`\[]; `message`: `unknown`; `on`: `string`; `type`: `string`; } | { `errors?`: `undefined`; `message`: `unknown`; `on`: `string`; `type`: `string`; } *** ### toResponse() ```ts toResponse(headers?): Response; ``` Defined in: elysia/dist/error.d.ts:120 #### Parameters | Parameter | Type | | ---------- | ------------------------- | | `headers?` | `Record`<`string`, `any`> | #### Returns `Response` #### Overrides [`ElysiaError`](/reference/classes/elysiaerror/).[`toResponse`](/reference/classes/elysiaerror/#toresponse) *** ### captureStackTrace() #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: @types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) #### Call Signature ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: bun-types/globals.d.ts:1081 Create .stack property on a target object ##### Parameters | Parameter | Type | | ----------------- | ---------- | | `targetObject` | `object` | | `constructorOpt?` | `Function` | ##### Returns `void` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`captureStackTrace`](/reference/classes/elysiaerror/#capturestacktrace) *** ### isError() #### Call Signature ```ts static isError(error): error is Error; ``` Defined in: typescript/lib/lib.esnext.error.d.ts:21 Indicates whether the argument provided is a built-in Error instance or not. ##### Parameters | Parameter | Type | | --------- | --------- | | `error` | `unknown` | ##### Returns `error is Error` ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) #### Call Signature ```ts static isError(value): value is Error; ``` Defined in: bun-types/globals.d.ts:1076 Check if a value is an instance of Error ##### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------ | | `value` | `unknown` | The value to check | ##### Returns `value is Error` True if the value is an instance of Error, false otherwise ##### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`isError`](/reference/classes/elysiaerror/#iserror) *** ### prepareStackTrace() ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: @types/node/globals.d.ts:56 #### Parameters | Parameter | Type | | ------------- | ------------- | | `err` | `Error` | | `stackTraces` | `CallSite`\[] | #### Returns `any` #### See #### Inherited from [`ElysiaError`](/reference/classes/elysiaerror/).[`prepareStackTrace`](/reference/classes/elysiaerror/#preparestacktrace) --- Source: [reference/classes/validator/index.md](reference/classes/validator/index.md) # Validator ## Members ### Properties | Property | Description | | ------------------------------------------------------------------ | ----------- | | [Clean](/reference/classes/validator/#clean) | - | | [isAsync](/reference/classes/validator/#isasync) | - | | [mayReturnPromise](/reference/classes/validator/#mayreturnpromise) | - | ### Methods | Method | Description | | ---------------------------------------------------- | ----------- | | [Check](/reference/classes/validator/#check) | - | | [Errors](/reference/classes/validator/#errors) | - | | [From](/reference/classes/validator/#from) | - | | [clear](/reference/classes/validator/#clear) | - | | [create](/reference/classes/validator/#create) | - | | [reference](/reference/classes/validator/#reference) | - | | [response](/reference/classes/validator/#response) | - | Defined in: elysia/dist/validator/index.d.ts:30 ## Extended by - [`MultiValidator`](/reference/classes/multivalidator/) - [`StandardValidator`](/reference/classes/standardvalidator/) - [`TypeBoxValidator`](/reference/classes/typeboxvalidator/) ## Constructors ### Constructor ```ts new Validator(): Validator; ``` #### Returns `Validator` ## Properties ### Clean ```ts Clean: (value) => unknown; ``` Defined in: elysia/dist/validator/index.d.ts:37 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `unknown` *** ### isAsync ```ts isAsync: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:31 *** ### mayReturnPromise ```ts mayReturnPromise: boolean; ``` Defined in: elysia/dist/validator/index.d.ts:32 ## Methods ### Check() ```ts abstract Check(value): boolean; ``` Defined in: elysia/dist/validator/index.d.ts:33 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `boolean` *** ### Errors() ```ts abstract Errors(value): TLocalizedValidationError[]; ``` Defined in: elysia/dist/validator/index.d.ts:34 #### Parameters | Parameter | Type | | --------- | --------- | | `value` | `unknown` | #### Returns `TLocalizedValidationError`\[] *** ### From()? ```ts optional From( value, type?, allowAsync? ): unknown; ``` Defined in: elysia/dist/validator/index.d.ts:36 #### Parameters | Parameter | Type | | ------------- | --------- | | `value` | `unknown` | | `type?` | `string` | | `allowAsync?` | `boolean` | #### Returns `unknown` *** ### clear() ```ts static clear(): void; ``` Defined in: elysia/dist/validator/index.d.ts:42 #### Returns `void` *** ### create() #### Call Signature ```ts static create(schema, options?): TypeBoxValidator; ``` Defined in: elysia/dist/validator/index.d.ts:38 ##### Type Parameters | Type Parameter | | ---------------------------- | | `Schema` *extends* `TSchema` | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns `TypeBoxValidator`<`Schema`> #### Call Signature ```ts static create(schema, options?): StandardValidator; ``` Defined in: elysia/dist/validator/index.d.ts:39 ##### Type Parameters | Type Parameter | | -------------------------------------------------------------------------------------------------------------- | | `Schema` *extends* [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `Schema` | | `options?` | `ValidatorOptions` | ##### Returns [`StandardValidator`](/reference/classes/standardvalidator/) #### Call Signature ```ts static create(schema, options?): Validator; ``` Defined in: elysia/dist/validator/index.d.ts:40 ##### Parameters | Parameter | Type | | ---------- | ------------------ | | `schema` | `null` | | `options?` | `ValidatorOptions` | ##### Returns `Validator` *** ### reference() ```ts static reference(schema, models): AnySchema; ``` Defined in: elysia/dist/validator/index.d.ts:35 #### Parameters | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `string` \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> | | `models` | `Record`<`string` \| `number` \| `symbol`, [`AnySchema`](/reference/type-aliases/anyschema/)> | #### Returns [`AnySchema`](/reference/type-aliases/anyschema/) *** ### response() ```ts static response(schema, options?): Map; ``` Defined in: elysia/dist/validator/index.d.ts:41 #### Parameters | Parameter | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema` | \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`> \| `Record`<`number`, \| `TSchema` \| [`StandardSchemaV1Like`](/reference/interfaces/standardschemav1like/)<`unknown`, `unknown`>> | | `options?` | `ResponseValidatorOptions` | #### Returns `Map`<`number`, `Validator`> --- Source: [reference/elysia/namespaces/prefix/index.md](reference/elysia/namespaces/prefix/index.md) # prefix ## Variables | Variable | Description | | ----------------------------------------------------------------------- | ----------- | | [capitalize](/reference/elysia/namespaces/prefix/variables/capitalize/) | - | --- Source: [reference/elysia/namespaces/prefix/variables/capitalize/index.md](reference/elysia/namespaces/prefix/variables/capitalize/index.md) # capitalize ```ts capitalize: (prefix, models) => { [k in keyof Models as `${T}.${Capitalize}`]: Models[k] }; ``` Defined in: elysia/dist/utils.d.ts:209 ## Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------- | | `T` *extends* `string` | | `Models` *extends* `Record`<`string`, [`AnySchema`](/reference/type-aliases/anyschema/)> | ## Parameters | Parameter | Type | | --------- | -------- | | `prefix` | `T` | | `models` | `Models` | ## Returns ``{ [k in keyof Models as `${T}.${Capitalize}`]: Models[k] }`` --- Source: [reference/functions/borrow/index.md](reference/functions/borrow/index.md) # borrow ```ts function borrow(response): T; ``` Defined in: elysia/dist/adapter/response-ownership.d.ts:2 ## Type Parameters | Type Parameter | | ------------------------ | | `T` *extends* `Response` | ## Parameters | Parameter | Type | | ---------- | ---- | | `response` | `T` | ## Returns `T` --- Source: [reference/functions/bytes/index.md](reference/functions/bytes/index.md) # bytes ```ts function bytes(stream): T; ``` Defined in: elysia/dist/utils.d.ts:155 Certify a byte stream for direct Response body pass-through. The returned Response owns this exact stream. Cancel it through the response body reader; preserving identity means Elysia does not install a second request-signal reader after the body is locked. ## Type Parameters | Type Parameter | | --------------------------------------------------------------- | | `T` *extends* `ReadableStream`<`Uint8Array`<`ArrayBufferLike`>> | ## Parameters | Parameter | Type | | --------- | ---- | | `stream` | `T` | ## Returns `T` --- Source: [reference/functions/createbasecontext/index.md](reference/functions/createbasecontext/index.md) # createBaseContext ```ts function createBaseContext(app): any; ``` Defined in: elysia/dist/context.d.ts:11 ## Parameters | Parameter | Type | | --------- | ------------------------------------------------- | | `app` | [`AnyElysia`](/reference/type-aliases/anyelysia/) | ## Returns `any` --- Source: [reference/functions/createcontext/index.md](reference/functions/createcontext/index.md) # createContext ```ts function createContext(app): (request) => object; ``` Defined in: elysia/dist/context.d.ts:13 ## Parameters | Parameter | Type | | --------- | ------------------------------------------------- | | `app` | [`AnyElysia`](/reference/type-aliases/anyelysia/) | ## Returns (`request`) => `object` --- Source: [reference/functions/filetype/index.md](reference/functions/filetype/index.md) # fileType ```ts function fileType(file, type): Promise; ``` Defined in: elysia/dist/type/elysia/file-type.d.ts:9 ## Parameters | Parameter | Type | | --------- | ------------------------ | | `file` | `MaybeArray`<`File`> | | `type` | `MaybeArray`<`FileType`> | ## Returns `Promise`<`boolean`> --- Source: [reference/functions/inaotbuild/index.md](reference/functions/inaotbuild/index.md) # inAotBuild ```ts function inAotBuild(): boolean; ``` Defined in: elysia/dist/compile/aot.d.ts:230 ## Returns `boolean` --- Source: [reference/functions/prefix/index.md](reference/functions/prefix/index.md) # prefix ```ts function prefix(prefix, models): { [k in string | number | symbol as `${T}.${k & string}`]: Models[k] }; ``` Defined in: elysia/dist/utils.d.ts:207 ## Type Parameters | Type Parameter | | ---------------------------------------------------------------------------------------- | | `T` *extends* `string` | | `Models` *extends* `Record`<`string`, [`AnySchema`](/reference/type-aliases/anyschema/)> | ## Parameters | Parameter | Type | | --------- | -------- | | `prefix` | `T` | | `models` | `Models` | ## Returns { \[k in string | number | symbol as \`${T}.${k & string}\`]: Models\[k] } --- Source: [reference/functions/problem/index.md](reference/functions/problem/index.md) # problem ## Call Signature ```ts function problem(status, detail?): ElysiaStatus, ProblemResponseBody, P>>; ``` Defined in: elysia/dist/error.d.ts:203 RFC 9457 Problem Details function ### Type Parameters | Type Parameter | Default type | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | `Code` *extends* \| `number` \| `"Continue"` \| `"Switching Protocols"` \| `"Processing"` \| `"Early Hints"` \| `"OK"` \| `"Created"` \| `"Accepted"` \| `"Non-Authoritative Information"` \| `"No Content"` \| `"Reset Content"` \| `"Partial Content"` \| `"Multi-Status"` \| `"Already Reported"` \| `"Multiple Choices"` \| `"Moved Permanently"` \| `"Found"` \| `"See Other"` \| `"Not Modified"` \| `"Temporary Redirect"` \| `"Permanent Redirect"` \| `"Bad Request"` \| `"Unauthorized"` \| `"Payment Required"` \| `"Forbidden"` \| `"Not Found"` \| `"Method Not Allowed"` \| `"Not Acceptable"` \| `"Proxy Authentication Required"` \| `"Request Timeout"` \| `"Conflict"` \| `"Gone"` \| `"Length Required"` \| `"Precondition Failed"` \| `"Payload Too Large"` \| `"URI Too Long"` \| `"Unsupported Media Type"` \| `"Range Not Satisfiable"` \| `"Expectation Failed"` \| `"I'm a teapot"` \| `"Enhance Your Calm"` \| `"Misdirected Request"` \| `"Unprocessable Content"` \| `"Locked"` \| `"Failed Dependency"` \| `"Too Early"` \| `"Upgrade Required"` \| `"Precondition Required"` \| `"Too Many Requests"` \| `"Request Header Fields Too Large"` \| `"Unavailable For Legal Reasons"` \| `"Internal Server Error"` \| `"Not Implemented"` \| `"Bad Gateway"` \| `"Service Unavailable"` \| `"Gateway Timeout"` \| `"HTTP Version Not Supported"` \| `"Variant Also Negotiates"` \| `"Insufficient Storage"` \| `"Loop Detected"` \| `"Not Extended"` \| `"Network Authentication Required"` | - | | `P` *extends* `Record`<`string`, `unknown`> & `object` | `object` | ### Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `status` | `Code` | | `detail?` | `P` & `Omit`<{ `code?`: `string`; `detail?`: `string`; `instance?`: `string`; `status?`: `Code`; `title?`: `string`; `type?`: `string`; }, `"status"`> | ### Returns [`ElysiaStatus`](/reference/classes/elysiastatus/)<[`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`>, `ProblemResponseBody`<[`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`>, `P`>> ### Example ```ts problem(400, { detail: 'Something went wrong' }) ``` ### See ## Call Signature ```ts function problem

(detail): ElysiaStatus, ProblemResponseBody, P>>; ``` Defined in: elysia/dist/error.d.ts:216 RFC 9457 Problem Details function ### Type Parameters | Type Parameter | | ---------------------- | | `P` *extends* `object` | ### Parameters | Parameter | Type | | --------- | ---- | | `detail` | `P` | ### Returns [`ElysiaStatus`](/reference/classes/elysiastatus/)<`ProblemStatus`<`P`>, `ProblemResponseBody`<`ProblemStatus`<`P`>, `P`>> ### Example ```ts problem({ status: 400, detail: 'Something went wrong' }) ``` ### See --- Source: [reference/functions/setfiletypedetector/index.md](reference/functions/setfiletypedetector/index.md) # setFileTypeDetector ```ts function setFileTypeDetector(detector): void; ``` Defined in: elysia/dist/type/elysia/file-type.d.ts:8 ## Parameters | Parameter | Type | | ---------- | ----------------------------------------------------------------------------- | | `detector` | `MaybeArray`<[`FileTypeDetector`](/reference/type-aliases/filetypedetector/)> | ## Returns `void` --- Source: [reference/functions/setuptypebox/index.md](reference/functions/setuptypebox/index.md) # setupTypebox ```ts function setupTypebox(options?): void; ``` Defined in: elysia/dist/type/compat.d.ts:6 ## Parameters | Parameter | Type | | ---------------------- | --------------------------------------------------------------------------------------------------------- | | `options?` | { `exactMirror?`: `CreateMirror`; `typebox?`: `Partial`<`TypeboxNamespaces` & `TypeboxTypeNamespaces`>; } | | `options.exactMirror?` | `CreateMirror` | | `options.typebox?` | `Partial`<`TypeboxNamespaces` & `TypeboxTypeNamespaces`> | ## Returns `void` --- Source: [reference/index.md](reference/index.md) # API reference ## Namespaces | Namespace | Description | | ---------------------------------------------- | ----------- | | [prefix](/reference/elysia/namespaces/prefix/) | - | ## Classes | Class | Description | | -------------------------------------------------------------- | -------------------------- | | [Compiled](/reference/classes/compiled/) | - | | [Elysia](/reference/classes/elysia/) | - | | [ElysiaError](/reference/classes/elysiaerror/) | - | | [ElysiaFile](/reference/classes/elysiafile/) | - | | [ElysiaStatus](/reference/classes/elysiastatus/) | - | | [HTTPError](/reference/classes/httperror/) | Self-describing HTTP error | | [InternalServerError](/reference/classes/internalservererror/) | - | | [InvalidCookie](/reference/classes/invalidcookie/) | - | | [MultiValidator](/reference/classes/multivalidator/) | - | | [NotFound](/reference/classes/notfound/) | - | | [ParseError](/reference/classes/parseerror/) | - | | [StandardValidator](/reference/classes/standardvalidator/) | - | | [TypeBoxValidator](/reference/classes/typeboxvalidator/) | - | | [ValidationError](/reference/classes/validationerror/) | - | | [Validator](/reference/classes/validator/) | - | ## Interfaces | Interface | Description | | --------------------------------------------------------------------------- | --------------------------------------------------------------- | | [AotFingerprint](/reference/interfaces/aotfingerprint/) | - | | [BaseCookie](/reference/interfaces/basecookie/) | - | | [BaseSchema](/reference/interfaces/baseschema/) | - | | [CompilerSession](/reference/interfaces/compilersession/) | - | | [Cookie](/reference/interfaces/cookie/) | - | | [CookieOptions](/reference/interfaces/cookieoptions/) | - | | [DefaultEphemeral](/reference/interfaces/defaultephemeral/) | - | | [DefaultMetadata](/reference/interfaces/defaultmetadata/) | - | | [DefaultSingleton](/reference/interfaces/defaultsingleton/) | - | | [InputSchema](/reference/interfaces/inputschema/) | - | | [Macro](/reference/interfaces/macro/) | - | | [MacroTypeLambda](/reference/interfaces/macrotypelambda/) | Type-level lambda applied to a macro’s per call site hook value | | [ProgramId](/reference/interfaces/programid/) | - | | [RouteSchema](/reference/interfaces/routeschema/) | - | | [StandardJSONSchemaV1Like](/reference/interfaces/standardjsonschemav1like/) | - | | [StandardSchemaV1Like](/reference/interfaces/standardschemav1like/) | - | | [TCookieField](/reference/interfaces/tcookiefield/) | - | | [TCookieObject](/reference/interfaces/tcookieobject/) | - | | [UnwrapRoute](/reference/interfaces/unwraproute/) | - | ## Type Aliases | Type Alias | Description | | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | [AnyElysia](/reference/type-aliases/anyelysia/) | - | | [AnySchema](/reference/type-aliases/anyschema/) | - | | [Context](/reference/type-aliases/context/) | - | | [ErrorContext](/reference/type-aliases/errorcontext/) | - | | [FileTypeDetector](/reference/type-aliases/filetypedetector/) | - | | [HTTPHeaders](/reference/type-aliases/httpheaders/) | - | | [MacroToContext](/reference/type-aliases/macrotocontext/) | - | | [MacroToProperty](/reference/type-aliases/macrotoproperty/) | - | | [NumericStatus](/reference/type-aliases/numericstatus/) | Numeric form of a status name or code. | | [Problem](/reference/type-aliases/problem/) | RFC 9457 Problem Details shape. | | [redirect](/reference/type-aliases/redirect/) | - | | [SelectiveProblem](/reference/type-aliases/selectiveproblem/) | - | | [SelectiveStatus](/reference/type-aliases/selectivestatus/) | - | | [Server](/reference/type-aliases/server/) | - | | [SSEPayload](/reference/type-aliases/ssepayload/) | - | | [StatusMap](/reference/type-aliases/statusmap/) | - | | [StatusMapBack](/reference/type-aliases/statusmapback/) | - | | [StatusResponse](/reference/type-aliases/statusresponse/) | Default body for a status with no response. Named to keep emitted types small. | | [TaggedHTTPError](/reference/type-aliases/taggedhttperror/) | Class returned by [HTTPError.id](/reference/classes/httperror/#id) | | [TFiles](/reference/type-aliases/tfiles/) | - | | [UnwrapModels](/reference/type-aliases/unwrapmodels/) | - | | [UnwrapSchema](/reference/type-aliases/unwrapschema/) | - | | [ValidationErrorResponse](/reference/type-aliases/validationerrorresponse/) | Request validation problem. Diagnostic fields are development-only. | ## Variables | Variable | Description | | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [env](/reference/variables/env/) | - | | [file](/reference/variables/file/) | Serve a file from a path as the response. | | [form](/reference/variables/form/) | Return a `multipart/form-data` response. | | [macroType](/reference/variables/macrotype/) | - | | [Manifest](/reference/variables/manifest/) | - | | [redirect](/reference/variables/redirect/) | - | | [sse](/reference/variables/sse/) | - | | [status](/reference/variables/status/) | - | | [StatusMap](/reference/variables/statusmap/) | - | | [StatusMapBack](/reference/variables/statusmapback/) | - | | [t](/reference/variables/t/) | - | | [TypeSystem](/reference/variables/typesystem/) | `typebox/system` is a subgraph of `typebox/type`, so a static re-export here would pin \~231 KB of the deferral back into the eager graph. The proxy also guarantees Elysia’s own `Settings` default lands BEFORE the namespace is handed out, keeping a user’s explicit `TypeSystem.Settings.Set(...)` last — the ordering the eager `setupTypebox()` used to provide | | [validationDetail](/reference/variables/validationdetail/) | Wrap a string into a TypeBox `error` callback that overrides the default validation message. Use as `t.Number({ error: validationDetail('x must be a number') })`. | ## Functions | Function | Description | | ---------------------------------------------------------------- | ------------------------------------------------------------ | | [borrow](/reference/functions/borrow/) | - | | [bytes](/reference/functions/bytes/) | Certify a byte stream for direct Response body pass-through. | | [createBaseContext](/reference/functions/createbasecontext/) | - | | [createContext](/reference/functions/createcontext/) | - | | [fileType](/reference/functions/filetype/) | - | | [inAotBuild](/reference/functions/inaotbuild/) | - | | [prefix](/reference/functions/prefix/) | - | | [problem](/reference/functions/problem/) | RFC 9457 Problem Details function | | [setFileTypeDetector](/reference/functions/setfiletypedetector/) | - | | [setupTypebox](/reference/functions/setuptypebox/) | - | ## References ### default Renames and re-exports [Elysia](/reference/classes/elysia/) --- Source: [reference/interfaces/aotfingerprint/index.md](reference/interfaces/aotfingerprint/index.md) # AotFingerprint ## Members ### Properties | Property | Description | | ------------------------------------------------ | ----------- | | [abi](/reference/interfaces/aotfingerprint/#abi) | - | Defined in: elysia/dist/compile/aot.d.ts:4 ## Properties ### abi ```ts abi: string; ``` Defined in: elysia/dist/compile/aot.d.ts:5 --- Source: [reference/interfaces/basecookie/index.md](reference/interfaces/basecookie/index.md) # BaseCookie ## Members ### Properties | Property | Description | | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [domain](/reference/interfaces/basecookie/#domain) | Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. | | [expires](/reference/interfaces/basecookie/#expires) | Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. | | [httpOnly](/reference/interfaces/basecookie/#httponly) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. | | [legacySignature](/reference/interfaces/basecookie/#legacysignature) | Only use secret for signing cookie instead of secret + separator + name | | [maxAge](/reference/interfaces/basecookie/#maxage) | Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. | | [partitioned](/reference/interfaces/basecookie/#partitioned) | Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. | | [path](/reference/interfaces/basecookie/#path) | Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. | | [priority](/reference/interfaces/basecookie/#priority) | Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. | | [sameSite](/reference/interfaces/basecookie/#samesite) | Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). | | [secrets](/reference/interfaces/basecookie/#secrets) | Secret key for signing cookie | | [secure](/reference/interfaces/basecookie/#secure) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. | | [value](/reference/interfaces/basecookie/#value) | - | Defined in: elysia/dist/cookie/types.d.ts:2 ## Extends - [`CookieOptions`](/reference/interfaces/cookieoptions/) ## Properties ### domain? ```ts optional domain?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:11 Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`domain`](/reference/interfaces/cookieoptions/#domain) *** ### expires? ```ts optional expires?: Date; ``` Defined in: elysia/dist/cookie/types.d.ts:22 Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`expires`](/reference/interfaces/cookieoptions/#expires) *** ### httpOnly? ```ts optional httpOnly?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:31 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. *Note* be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`httpOnly`](/reference/interfaces/cookieoptions/#httponly) *** ### legacySignature? ```ts optional legacySignature?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:120 Only use secret for signing cookie instead of secret + separator + name Set to `false` once every signed cookie in circulation has been re-issued Applies to the app and route level #### Default ```ts true ``` #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`legacySignature`](/reference/interfaces/cookieoptions/#legacysignature) *** ### maxAge? ```ts optional maxAge?: number; ``` Defined in: elysia/dist/cookie/types.d.ts:42 Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`maxAge`](/reference/interfaces/cookieoptions/#maxage) *** ### partitioned? ```ts optional partitioned?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:72 Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS) #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`partitioned`](/reference/interfaces/cookieoptions/#partitioned) *** ### path? ```ts optional path?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:47 Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`path`](/reference/interfaces/cookieoptions/#path) *** ### priority? ```ts optional priority?: "low" | "medium" | "high"; ``` Defined in: elysia/dist/cookie/types.d.ts:61 Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. - `'low'` will set the `Priority` attribute to `Low`. - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. - `'high'` will set the `Priority` attribute to `High`. More information about the different priority levels can be found in \[the specification]\[rfc-west-cookie-priority-00-4.1]. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`priority`](/reference/interfaces/cookieoptions/#priority) *** ### sameSite? ```ts optional sameSite?: boolean | "lax" | "strict" | "none"; ``` Defined in: elysia/dist/cookie/types.d.ts:90 Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. - `false` will not set the `SameSite` attribute. - `'lax'` will set the `SameSite` attribute to Lax for lax same site enforcement. - `'strict'` will set the `SameSite` attribute to Strict for strict same site enforcement. - `'none'` will set the SameSite attribute to None for an explicit cross-site cookie. More information about the different enforcement levels can be found in [specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7Cthe). *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`sameSite`](/reference/interfaces/cookieoptions/#samesite) *** ### secrets? ```ts optional secrets?: string | string[]; ``` Defined in: elysia/dist/cookie/types.d.ts:110 Secret key for signing cookie If array is passed, will use Key Rotation. Key rotation is when an encryption key is retired and replaced by generating a new cryptographic key. `null` in the array makes Elysia accept an unsigned cookie (intended only for a one-way unsigned→signed migration) #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`secrets`](/reference/interfaces/cookieoptions/#secrets) *** ### secure? ```ts optional secure?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:99 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. *Note* be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. #### Inherited from [`CookieOptions`](/reference/interfaces/cookieoptions/).[`secure`](/reference/interfaces/cookieoptions/#secure) *** ### value? ```ts optional value?: unknown; ``` Defined in: elysia/dist/cookie/types.d.ts:3 --- Source: [reference/interfaces/baseschema/index.md](reference/interfaces/baseschema/index.md) # BaseSchema ## Members ### Properties | Property | Description | | ------------------------------------------------------------------------------ | ----------- | | [$defs](/reference/interfaces/baseschema/#defs) | - | | [$id](/reference/interfaces/baseschema/#id) | - | | [$ref](/reference/interfaces/baseschema/#ref) | - | | [$schema](/reference/interfaces/baseschema/#schema) | - | | [additionalItems](/reference/interfaces/baseschema/#additionalitems) | - | | [additionalProperties](/reference/interfaces/baseschema/#additionalproperties) | - | | [allOf](/reference/interfaces/baseschema/#allof) | - | | [anyOf](/reference/interfaces/baseschema/#anyof) | - | | [const](/reference/interfaces/baseschema/#const) | - | | [definitions](/reference/interfaces/baseschema/#definitions) | - | | [dependencies](/reference/interfaces/baseschema/#dependencies) | - | | [enum](/reference/interfaces/baseschema/#enum) | - | | [id](/reference/interfaces/baseschema/#id-1) | - | | [items](/reference/interfaces/baseschema/#items) | - | | [not](/reference/interfaces/baseschema/#not) | - | | [oneOf](/reference/interfaces/baseschema/#oneof) | - | | [patternProperties](/reference/interfaces/baseschema/#patternproperties) | - | | [properties](/reference/interfaces/baseschema/#properties) | - | | [required](/reference/interfaces/baseschema/#required) | - | | [type](/reference/interfaces/baseschema/#type) | - | Defined in: elysia/dist/type/types.d.ts:147 ## Properties ### $defs? ```ts optional $defs?: BaseSchemaRecord; ``` Defined in: elysia/dist/type/types.d.ts:169 *** ### $id? ```ts optional $id?: string; ``` Defined in: elysia/dist/type/types.d.ts:150 *** ### $ref? ```ts optional $ref?: string; ``` Defined in: elysia/dist/type/types.d.ts:168 *** ### $schema? ```ts optional $schema?: string; ``` Defined in: elysia/dist/type/types.d.ts:153 *** ### additionalItems? ```ts optional additionalItems?: boolean | BaseSchema; ``` Defined in: elysia/dist/type/types.d.ts:155 *** ### additionalProperties? ```ts optional additionalProperties?: boolean | BaseSchema; ``` Defined in: elysia/dist/type/types.d.ts:158 *** ### allOf? ```ts optional allOf?: BaseSchema[]; ``` Defined in: elysia/dist/type/types.d.ts:164 *** ### anyOf? ```ts optional anyOf?: BaseSchema[]; ``` Defined in: elysia/dist/type/types.d.ts:165 *** ### const? ```ts optional const?: unknown[]; ``` Defined in: elysia/dist/type/types.d.ts:154 *** ### definitions? ```ts optional definitions?: BaseSchemaRecord; ``` Defined in: elysia/dist/type/types.d.ts:159 *** ### dependencies? ```ts optional dependencies?: BaseSchemaRecord; ``` Defined in: elysia/dist/type/types.d.ts:162 *** ### enum? ```ts optional enum?: unknown[]; ``` Defined in: elysia/dist/type/types.d.ts:163 *** ### id? ```ts optional id?: string; ``` Defined in: elysia/dist/type/types.d.ts:151 *** ### items? ```ts optional items?: BaseSchema | BaseSchema[]; ``` Defined in: elysia/dist/type/types.d.ts:156 *** ### not? ```ts optional not?: BaseSchema; ``` Defined in: elysia/dist/type/types.d.ts:167 *** ### oneOf? ```ts optional oneOf?: BaseSchema[]; ``` Defined in: elysia/dist/type/types.d.ts:166 *** ### patternProperties? ```ts optional patternProperties?: BaseSchemaRecord; ``` Defined in: elysia/dist/type/types.d.ts:161 *** ### properties? ```ts optional properties?: BaseSchemaRecord; ``` Defined in: elysia/dist/type/types.d.ts:160 *** ### required? ```ts optional required?: string[]; ``` Defined in: elysia/dist/type/types.d.ts:157 *** ### type? ```ts optional type?: string; ``` Defined in: elysia/dist/type/types.d.ts:152 --- Source: [reference/interfaces/compilersession/index.md](reference/interfaces/compilersession/index.md) # CompilerSession ## Members ### Properties | Property | Description | | ------------------------------------------------------------------------- | ----------- | | [app](/reference/interfaces/compilersession/#app) | - | | [capture](/reference/interfaces/compilersession/#capture) | - | | [explicitCapture](/reference/interfaces/compilersession/#explicitcapture) | - | | [external](/reference/interfaces/compilersession/#external) | - | | [handlerCapture](/reference/interfaces/compilersession/#handlercapture) | - | | [sucroseCache](/reference/interfaces/compilersession/#sucrosecache) | - | Defined in: elysia/dist/compile/aot.d.ts:11 ## Properties ### app? ```ts optional app?: object; ``` Defined in: elysia/dist/compile/aot.d.ts:18 *** ### capture? ```ts optional capture?: Map; ``` Defined in: elysia/dist/compile/aot.d.ts:16 *** ### explicitCapture? ```ts optional explicitCapture?: true; ``` Defined in: elysia/dist/compile/aot.d.ts:20 *** ### external? ```ts optional external?: true; ``` Defined in: elysia/dist/compile/aot.d.ts:19 *** ### handlerCapture? ```ts optional handlerCapture?: Map; ``` Defined in: elysia/dist/compile/aot.d.ts:17 *** ### sucroseCache ```ts readonly sucroseCache: Map; ``` Defined in: elysia/dist/compile/aot.d.ts:12 --- Source: [reference/interfaces/cookie/index.md](reference/interfaces/cookie/index.md) # Cookie ## Members ### Properties | Property | Description | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [domain](/reference/interfaces/cookie/#domain) | Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. | | [expires](/reference/interfaces/cookie/#expires) | Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. | | [httpOnly](/reference/interfaces/cookie/#httponly) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. | | [maxAge](/reference/interfaces/cookie/#maxage) | Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. | | [partitioned](/reference/interfaces/cookie/#partitioned) | Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. | | [path](/reference/interfaces/cookie/#path) | Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. | | [priority](/reference/interfaces/cookie/#priority) | Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. | | [sameSite](/reference/interfaces/cookie/#samesite) | Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). | | [secure](/reference/interfaces/cookie/#secure) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. | ### Accessors | Accessor | Description | | ---------------------------------------------- | ----------- | | [cookie](/reference/interfaces/cookie/#cookie) | - | | [value](/reference/interfaces/cookie/#value) | - | ### Methods | Method | Description | | -------------------------------------------------- | --------------------------------------------- | | [remove](/reference/interfaces/cookie/#remove) | - | | [set](/reference/interfaces/cookie/#set) | - | | [toJSON](/reference/interfaces/cookie/#tojson) | - | | [toString](/reference/interfaces/cookie/#tostring) | Returns a string representation of an object. | | [update](/reference/interfaces/cookie/#update) | - | Defined in: elysia/dist/cookie/cookie.d.ts:10 ## Extends - `Pick`<[`BaseCookie`](/reference/interfaces/basecookie/), `FORWARDED_KEYS`\[`number`]> ## Type Parameters | Type Parameter | Default type | | -------------- | ------------ | | `T` | `any` | ## Implements - [`BaseCookie`](/reference/interfaces/basecookie/) ## Properties ### domain? ```ts optional domain?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:11 Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`domain`](/reference/interfaces/basecookie/#domain) *** ### expires? ```ts optional expires?: Date; ``` Defined in: elysia/dist/cookie/types.d.ts:22 Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`expires`](/reference/interfaces/basecookie/#expires) *** ### httpOnly? ```ts optional httpOnly?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:31 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. *Note* be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`httpOnly`](/reference/interfaces/basecookie/#httponly) *** ### maxAge? ```ts optional maxAge?: number; ``` Defined in: elysia/dist/cookie/types.d.ts:42 Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`maxAge`](/reference/interfaces/basecookie/#maxage) *** ### partitioned? ```ts optional partitioned?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:72 Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS) #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`partitioned`](/reference/interfaces/basecookie/#partitioned) *** ### path? ```ts optional path?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:47 Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`path`](/reference/interfaces/basecookie/#path) *** ### priority? ```ts optional priority?: "low" | "medium" | "high"; ``` Defined in: elysia/dist/cookie/types.d.ts:61 Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. - `'low'` will set the `Priority` attribute to `Low`. - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. - `'high'` will set the `Priority` attribute to `High`. More information about the different priority levels can be found in \[the specification]\[rfc-west-cookie-priority-00-4.1]. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`priority`](/reference/interfaces/basecookie/#priority) *** ### sameSite? ```ts optional sameSite?: boolean | "lax" | "strict" | "none"; ``` Defined in: elysia/dist/cookie/types.d.ts:90 Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. - `false` will not set the `SameSite` attribute. - `'lax'` will set the `SameSite` attribute to Lax for lax same site enforcement. - `'strict'` will set the `SameSite` attribute to Strict for strict same site enforcement. - `'none'` will set the SameSite attribute to None for an explicit cross-site cookie. More information about the different enforcement levels can be found in [specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7Cthe). *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`sameSite`](/reference/interfaces/basecookie/#samesite) *** ### secure? ```ts optional secure?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:99 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. *Note* be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. #### Inherited from [`BaseCookie`](/reference/interfaces/basecookie/).[`secure`](/reference/interfaces/basecookie/#secure) ## Accessors ### cookie #### Get Signature ```ts get cookie(): BaseCookie; ``` Defined in: elysia/dist/cookie/cookie.d.ts:17 ##### Returns [`BaseCookie`](/reference/interfaces/basecookie/) #### Set Signature ```ts set cookie(jar): void; ``` Defined in: elysia/dist/cookie/cookie.d.ts:18 ##### Parameters | Parameter | Type | | --------- | ---------------- | | `jar` | `CookieMutation` | ##### Returns `void` *** ### value #### Get Signature ```ts get value(): T; ``` Defined in: elysia/dist/cookie/cookie.d.ts:20 ##### Returns `T` #### Set Signature ```ts set value(value): void; ``` Defined in: elysia/dist/cookie/cookie.d.ts:21 ##### Parameters | Parameter | Type | | --------- | ---- | | `value` | `T` | ##### Returns `void` ## Methods ### remove() ```ts remove(): Cookie; ``` Defined in: elysia/dist/cookie/cookie.d.ts:24 #### Returns `Cookie`<`T`> *** ### set() ```ts set(config): this; ``` Defined in: elysia/dist/cookie/cookie.d.ts:23 #### Parameters | Parameter | Type | | --------- | --------------------------- | | `config` | `Updater`<`CookieMutation`> | #### Returns `this` *** ### toJSON() ```ts toJSON(): T; ``` Defined in: elysia/dist/cookie/cookie.d.ts:25 #### Returns `T` *** ### toString() ```ts toString(): string; ``` Defined in: elysia/dist/cookie/cookie.d.ts:26 Returns a string representation of an object. #### Returns `string` *** ### update() ```ts update(config): this; ``` Defined in: elysia/dist/cookie/cookie.d.ts:22 #### Parameters | Parameter | Type | | --------- | --------------------------- | | `config` | `Updater`<`CookieMutation`> | #### Returns `this` --- Source: [reference/interfaces/cookieoptions/index.md](reference/interfaces/cookieoptions/index.md) # CookieOptions ## Members ### Properties | Property | Description | | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [domain](/reference/interfaces/cookieoptions/#domain) | Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. | | [expires](/reference/interfaces/cookieoptions/#expires) | Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. | | [httpOnly](/reference/interfaces/cookieoptions/#httponly) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. | | [legacySignature](/reference/interfaces/cookieoptions/#legacysignature) | Only use secret for signing cookie instead of secret + separator + name | | [maxAge](/reference/interfaces/cookieoptions/#maxage) | Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. | | [partitioned](/reference/interfaces/cookieoptions/#partitioned) | Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. | | [path](/reference/interfaces/cookieoptions/#path) | Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. | | [priority](/reference/interfaces/cookieoptions/#priority) | Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. | | [sameSite](/reference/interfaces/cookieoptions/#samesite) | Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). | | [secrets](/reference/interfaces/cookieoptions/#secrets) | Secret key for signing cookie | | [secure](/reference/interfaces/cookieoptions/#secure) | Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. | Defined in: elysia/dist/cookie/types.d.ts:5 ## Extended by - [`BaseCookie`](/reference/interfaces/basecookie/) ## Properties ### domain? ```ts optional domain?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:11 Specifies the value for the [Set-Cookie attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3%7CDomain). By default, no domain is set, and most clients will consider the cookie to apply to only the current domain. *** ### expires? ```ts optional expires?: Date; ``` Defined in: elysia/dist/cookie/types.d.ts:22 Specifies the `Date` object to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1%7C%60Expires%60). By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. *** ### httpOnly? ```ts optional httpOnly?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:31 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6%7C%60HttpOnly%60). When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set. *Note* be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`. *** ### legacySignature? ```ts optional legacySignature?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:120 Only use secret for signing cookie instead of secret + separator + name Set to `false` once every signed cookie in circulation has been re-issued Applies to the app and route level #### Default ```ts true ``` *** ### maxAge? ```ts optional maxAge?: number; ``` Defined in: elysia/dist/cookie/types.d.ts:42 Specifies the number (in seconds) to be the value for the `Max-Age` `Set-Cookie` attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set. *Note* the [storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3%7Ccookie) states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time. *** ### partitioned? ```ts optional partitioned?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:72 Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. More information about can be found in [the proposal](https://github.com/privacycg/CHIPS) *** ### path? ```ts optional path?: string; ``` Defined in: elysia/dist/cookie/types.d.ts:47 Specifies the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4%7C%60Path%60). By default, the path is considered the “default path”. *** ### priority? ```ts optional priority?: "low" | "medium" | "high"; ``` Defined in: elysia/dist/cookie/types.d.ts:61 Specifies the `string` to be the value for the \[`Priority` `Set-Cookie` attribute]\[rfc-west-cookie-priority-00-4.1]. - `'low'` will set the `Priority` attribute to `Low`. - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. - `'high'` will set the `Priority` attribute to `High`. More information about the different priority levels can be found in \[the specification]\[rfc-west-cookie-priority-00-4.1]. **note** This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. *** ### sameSite? ```ts optional sameSite?: boolean | "lax" | "strict" | "none"; ``` Defined in: elysia/dist/cookie/types.d.ts:90 Specifies the boolean or string to be the value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7C%60SameSite%60). - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. - `false` will not set the `SameSite` attribute. - `'lax'` will set the `SameSite` attribute to Lax for lax same site enforcement. - `'strict'` will set the `SameSite` attribute to Strict for strict same site enforcement. - `'none'` will set the SameSite attribute to None for an explicit cross-site cookie. More information about the different enforcement levels can be found in [specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7%7Cthe). *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. *** ### secrets? ```ts optional secrets?: string | string[]; ``` Defined in: elysia/dist/cookie/types.d.ts:110 Secret key for signing cookie If array is passed, will use Key Rotation. Key rotation is when an encryption key is retired and replaced by generating a new cryptographic key. `null` in the array makes Elysia accept an unsigned cookie (intended only for a one-way unsigned→signed migration) *** ### secure? ```ts optional secure?: boolean; ``` Defined in: elysia/dist/cookie/types.d.ts:99 Specifies the boolean value for the [\`Set-Cookie\` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5%7C%60Secure%60). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. *Note* be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. --- Source: [reference/interfaces/defaultephemeral/index.md](reference/interfaces/defaultephemeral/index.md) # DefaultEphemeral ## Members ### Properties | Property | Description | | ------------------------------------------------------------ | ----------- | | [derive](/reference/interfaces/defaultephemeral/#derive) | - | | [error](/reference/interfaces/defaultephemeral/#error) | - | | [response](/reference/interfaces/defaultephemeral/#response) | - | | [schema](/reference/interfaces/defaultephemeral/#schema) | - | | [schemas](/reference/interfaces/defaultephemeral/#schemas) | - | Defined in: elysia/dist/types.d.ts:345 ## Properties ### derive ```ts derive: object; ``` Defined in: elysia/dist/types.d.ts:346 *** ### error ```ts error: []; ``` Defined in: elysia/dist/types.d.ts:350 *** ### response ```ts response: object; ``` Defined in: elysia/dist/types.d.ts:349 *** ### schema ```ts schema: object; ``` Defined in: elysia/dist/types.d.ts:347 *** ### schemas ```ts schemas: object; ``` Defined in: elysia/dist/types.d.ts:348 --- Source: [reference/interfaces/defaultmetadata/index.md](reference/interfaces/defaultmetadata/index.md) # DefaultMetadata ## Members ### Properties | Property | Description | | ----------------------------------------------------------- | ----------- | | [macro](/reference/interfaces/defaultmetadata/#macro) | - | | [macroFn](/reference/interfaces/defaultmetadata/#macrofn) | - | | [parser](/reference/interfaces/defaultmetadata/#parser) | - | | [response](/reference/interfaces/defaultmetadata/#response) | - | | [schema](/reference/interfaces/defaultmetadata/#schema) | - | | [schemas](/reference/interfaces/defaultmetadata/#schemas) | - | Defined in: elysia/dist/types.d.ts:357 ## Properties ### macro ```ts macro: object; ``` Defined in: elysia/dist/types.d.ts:360 *** ### macroFn ```ts macroFn: object; ``` Defined in: elysia/dist/types.d.ts:361 *** ### parser ```ts parser: object; ``` Defined in: elysia/dist/types.d.ts:362 *** ### response ```ts response: object; ``` Defined in: elysia/dist/types.d.ts:363 *** ### schema ```ts schema: object; ``` Defined in: elysia/dist/types.d.ts:358 *** ### schemas ```ts schemas: object; ``` Defined in: elysia/dist/types.d.ts:359 --- Source: [reference/interfaces/defaultsingleton/index.md](reference/interfaces/defaultsingleton/index.md) # DefaultSingleton ## Members ### Properties | Property | Description | | -------------------------------------------------------------- | ----------- | | [decorator](/reference/interfaces/defaultsingleton/#decorator) | - | | [derive](/reference/interfaces/defaultsingleton/#derive) | - | | [store](/reference/interfaces/defaultsingleton/#store) | - | Defined in: elysia/dist/types.d.ts:352 ## Properties ### decorator ```ts decorator: object; ``` Defined in: elysia/dist/types.d.ts:353 *** ### derive ```ts derive: object; ``` Defined in: elysia/dist/types.d.ts:355 *** ### store ```ts store: object; ``` Defined in: elysia/dist/types.d.ts:354 --- Source: [reference/interfaces/inputschema/index.md](reference/interfaces/inputschema/index.md) # InputSchema ## Members ### Properties | Property | Description | | ------------------------------------------------------- | ----------- | | [body](/reference/interfaces/inputschema/#body) | - | | [cookie](/reference/interfaces/inputschema/#cookie) | - | | [headers](/reference/interfaces/inputschema/#headers) | - | | [params](/reference/interfaces/inputschema/#params) | - | | [query](/reference/interfaces/inputschema/#query) | - | | [response](/reference/interfaces/inputschema/#response) | - | Defined in: elysia/dist/types.d.ts:199 ## Type Parameters | Type Parameter | Default type | | ------------------------- | ------------ | | `Name` *extends* `string` | `string` | ## Properties ### body? ```ts optional body?: Name | AnySchema; ``` Defined in: elysia/dist/types.d.ts:200 *** ### cookie? ```ts optional cookie?: Name | AnySchema; ``` Defined in: elysia/dist/types.d.ts:204 *** ### headers? ```ts optional headers?: Name | AnySchema; ``` Defined in: elysia/dist/types.d.ts:201 *** ### params? ```ts optional params?: Name | AnySchema; ``` Defined in: elysia/dist/types.d.ts:203 *** ### query? ```ts optional query?: Name | AnySchema; ``` Defined in: elysia/dist/types.d.ts:202 *** ### response? ```ts optional response?: | Name | AnySchema | Record; ``` Defined in: elysia/dist/types.d.ts:205 --- Source: [reference/interfaces/macro/index.md](reference/interfaces/macro/index.md) # Macro Defined in: elysia/dist/types.d.ts:529 ## Type Parameters | Type Parameter | Default type | | -------------------------------------------------------------------------- | ------------------------------------------------------------- | | `Macro` *extends* `BaseMacro` | `object` | | `Input` *extends* `BaseMacro` | `object` | | `TypedRoute` *extends* [`RouteSchema`](/reference/interfaces/routeschema/) | `object` | | `Singleton` *extends* `SingletonBase` | [`DefaultSingleton`](/reference/interfaces/defaultsingleton/) | | `Errors` *extends* `ErrorDefinition`\[] | \[] | ## Indexable ```ts [K: string | number | symbol]: MaybeValueOrVoidFunction ``` --- Source: [reference/interfaces/macrotypelambda/index.md](reference/interfaces/macrotypelambda/index.md) # MacroTypeLambda ## Members ### Properties | Property | Description | | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | [context](/reference/interfaces/macrotypelambda/#context) | The call-site schema and accumulated singleton context, excluding output from macros selected at the same call site | | [input](/reference/interfaces/macrotypelambda/#input) | - | Defined in: elysia/dist/types.d.ts:813 Type-level lambda applied to a macro’s per call site hook value Return type as `$type: macroType()` `output` will be added to route’s context A function-form macro may also compute `option` from `this['context']`. The hook value is then typed as `option` per route, guard, group and ws, replacing the macro’s declared parameter `this` is not allowed in a nested type literal ```ts interface LiveOption { topic?: (ctx: Context) => string } interface Live extends MacroTypeLambda { option: LiveOption } new Elysia().macro({ live: (option: LiveOption) => ({ $type: macroType(), beforeHandle() {} }) }) ``` ## Properties ### context ```ts context: unknown; ``` Defined in: elysia/dist/types.d.ts:819 The call-site schema and accumulated singleton context, excluding output from macros selected at the same call site *** ### input ```ts input: unknown; ``` Defined in: elysia/dist/types.d.ts:814 --- Source: [reference/interfaces/programid/index.md](reference/interfaces/programid/index.md) # ProgramId ## Members ### Properties | Property | Description | | ---------------------------------------- | ----------- | | [\_](/reference/interfaces/programid/#_) | - | Defined in: elysia/dist/compile/aot.d.ts:7 ## Properties ### \_ ```ts readonly _: typeof _; ``` Defined in: elysia/dist/compile/aot.d.ts:8 --- Source: [reference/interfaces/routeschema/index.md](reference/interfaces/routeschema/index.md) # RouteSchema ## Members ### Properties | Property | Description | | ------------------------------------------------------- | ----------- | | [body](/reference/interfaces/routeschema/#body) | - | | [cookie](/reference/interfaces/routeschema/#cookie) | - | | [headers](/reference/interfaces/routeschema/#headers) | - | | [params](/reference/interfaces/routeschema/#params) | - | | [query](/reference/interfaces/routeschema/#query) | - | | [response](/reference/interfaces/routeschema/#response) | - | Defined in: elysia/dist/types.d.ts:378 ## Properties ### body? ```ts optional body?: unknown; ``` Defined in: elysia/dist/types.d.ts:379 *** ### cookie? ```ts optional cookie?: unknown; ``` Defined in: elysia/dist/types.d.ts:383 *** ### headers? ```ts optional headers?: unknown; ``` Defined in: elysia/dist/types.d.ts:380 *** ### params? ```ts optional params?: unknown; ``` Defined in: elysia/dist/types.d.ts:382 *** ### query? ```ts optional query?: unknown; ``` Defined in: elysia/dist/types.d.ts:381 *** ### response? ```ts optional response?: unknown; ``` Defined in: elysia/dist/types.d.ts:384 --- Source: [reference/interfaces/standardjsonschemav1like/index.md](reference/interfaces/standardjsonschemav1like/index.md) # StandardJSONSchemaV1Like Defined in: elysia/dist/type/types.d.ts:171 --- Source: [reference/interfaces/standardschemav1like/index.md](reference/interfaces/standardschemav1like/index.md) # StandardSchemaV1Like Defined in: elysia/dist/type/types.d.ts:179 ## Type Parameters | Type Parameter | Default type | | -------------- | ------------ | | `Input` | `unknown` | | `Output` | `Input` | --- Source: [reference/interfaces/tcookiefield/index.md](reference/interfaces/tcookiefield/index.md) # TCookieField ## Members ### Properties | Property | Description | | ---------------------------------------------------- | ----------- | | [config](/reference/interfaces/tcookiefield/#config) | - | Defined in: elysia/dist/type/elysia/cookie.d.ts:8 ## Properties ### config? ```ts optional config?: CookieValidatorOptions; ``` Defined in: elysia/dist/type/elysia/cookie.d.ts:9 --- Source: [reference/interfaces/tcookieobject/index.md](reference/interfaces/tcookieobject/index.md) # TCookieObject ## Members ### Properties | Property | Description | | ------------------------------------------------------------- | ----------- | | [config](/reference/interfaces/tcookieobject/#config) | - | | [properties](/reference/interfaces/tcookieobject/#properties) | - | | [required](/reference/interfaces/tcookieobject/#required) | - | | [type](/reference/interfaces/tcookieobject/#type) | - | Defined in: elysia/dist/type/elysia/cookie.d.ts:5 ## Extends - `TObject`<`T`> ## Type Parameters | Type Parameter | | --------------------------- | | `T` *extends* `TProperties` | ## Properties ### config? ```ts optional config?: CookieValidatorOptions; ``` Defined in: elysia/dist/type/elysia/cookie.d.ts:6 *** ### properties ```ts properties: T; ``` Defined in: typebox/build/type/types/object.d.mts:9 #### Inherited from ```ts TObject.properties ``` *** ### required ```ts required: TUnionToTuple ? never : Key]: T[Key] }, string>, [], TUnionLast ? never : Key]: T[Key] }, string>>> extends [] ? undefined : TUnionToTuple ? never : Key]: T[Key] }, string>, [], TUnionLast ? never : Key]: T[Key] }, string>>>; ``` Defined in: typebox/build/type/types/object.d.mts:10 #### Inherited from ```ts TObject.required ``` *** ### type ```ts type: "object"; ``` Defined in: typebox/build/type/types/object.d.mts:8 #### Inherited from ```ts TObject.type ``` --- Source: [reference/interfaces/unwraproute/index.md](reference/interfaces/unwraproute/index.md) # UnwrapRoute ## Members ### Properties | Property | Description | | ------------------------------------------------------- | ----------- | | [body](/reference/interfaces/unwraproute/#body) | - | | [cookie](/reference/interfaces/unwraproute/#cookie) | - | | [headers](/reference/interfaces/unwraproute/#headers) | - | | [params](/reference/interfaces/unwraproute/#params) | - | | [query](/reference/interfaces/unwraproute/#query) | - | | [response](/reference/interfaces/unwraproute/#response) | - | Defined in: elysia/dist/types.d.ts:691 ## Type Parameters | Type Parameter | Default type | | ----------------------------------------------------------------------------- | ------------ | | `Schema` *extends* [`InputSchema`](/reference/interfaces/inputschema/)<`any`> | - | | `Definitions` *extends* `DefinitionBase`\[`"typebox"`] | `object` | | `Path` *extends* `string` | `""` | ## Properties ### body ```ts body: UnwrapBodySchema; ``` Defined in: elysia/dist/types.d.ts:692 *** ### cookie ```ts cookie: UnwrapSchema; ``` Defined in: elysia/dist/types.d.ts:696 *** ### headers ```ts headers: UnwrapSchema; ``` Defined in: elysia/dist/types.d.ts:693 *** ### params ```ts params: object extends Schema["params"] ? ResolvePath : object extends Schema ? ResolvePath : UnwrapSchema; ``` Defined in: elysia/dist/types.d.ts:695 *** ### query ```ts query: UnwrapSchema; ``` Defined in: elysia/dist/types.d.ts:694 *** ### response ```ts response: Schema["response"] extends string | AnySchema ? object : Schema["response"] extends object ? { [k in string | number | symbol]: UnwrapResponseSchema extends A ? A extends File ? File | ElysiaFile : A : unknown } : unknown; ``` Defined in: elysia/dist/types.d.ts:702 --- Source: [reference/type-aliases/anyelysia/index.md](reference/type-aliases/anyelysia/index.md) # AnyElysia ```ts type AnyElysia = Elysia; ``` Defined in: elysia/dist/base.d.ts:17 --- Source: [reference/type-aliases/anyschema/index.md](reference/type-aliases/anyschema/index.md) # AnySchema ```ts type AnySchema = | TypeBoxSchema | StandardSchemaV1Like; ``` Defined in: elysia/dist/type/types.d.ts:204 --- Source: [reference/type-aliases/context/index.md](reference/type-aliases/context/index.md) # Context ```ts type Context = Prettify & Singleton["decorator"] & Omit>; ``` Defined in: elysia/dist/context.d.ts:69 ## Type Parameters | Type Parameter | Default type | | --------------------------------------------------------------------- | ------------------------------------------------------------- | | `Route` *extends* [`RouteSchema`](/reference/interfaces/routeschema/) | `object` | | `Singleton` *extends* `SingletonBase` | [`DefaultSingleton`](/reference/interfaces/defaultsingleton/) | | `Path` *extends* `string` \| `undefined` | `undefined` | --- Source: [reference/type-aliases/errorcontext/index.md](reference/type-aliases/errorcontext/index.md) # ErrorContext ```ts type ErrorContext = Prettify & Singleton["decorator"] & Singleton["derive"]>; ``` Defined in: elysia/dist/context.d.ts:61 ## Type Parameters | Type Parameter | Default type | | --------------------------------------------------------------------- | ------------------------------------------------------------- | | `Route` *extends* [`RouteSchema`](/reference/interfaces/routeschema/) | `object` | | `Singleton` *extends* `SingletonBase` | [`DefaultSingleton`](/reference/interfaces/defaultsingleton/) | | `Path` *extends* `string` \| `undefined` | `undefined` | --- Source: [reference/type-aliases/filetypedetector/index.md](reference/type-aliases/filetypedetector/index.md) # FileTypeDetector ```ts type FileTypeDetector = (file) => MaybePromise< | string | { mime?: string; } | null | undefined>; ``` Defined in: elysia/dist/type/elysia/file-type.d.ts:5 ## Parameters | Parameter | Type | | --------- | ------ | | `file` | `File` | ## Returns `MaybePromise`< | `string` | { `mime?`: `string`; } | `null` | `undefined`> --- Source: [reference/type-aliases/httpheaders/index.md](reference/type-aliases/httpheaders/index.md) # HTTPHeaders ```ts type HTTPHeaders = Record & object; ``` Defined in: elysia/dist/types.d.ts:539 ## Type Declaration | Name | Type | Defined in | | -------------------------------------- | --------------------------------------- | -------------------------- | | `accept?` | `string` | elysia/dist/types.d.ts:559 | | `accept-encoding?` | `string` | elysia/dist/types.d.ts:560 | | `accept-language?` | `string` | elysia/dist/types.d.ts:561 | | `accept-ranges?` | `string` | elysia/dist/types.d.ts:588 | | `access-control-allow-credentials?` | `string` | elysia/dist/types.d.ts:567 | | `access-control-allow-headers?` | `string` | elysia/dist/types.d.ts:568 | | `access-control-allow-methods?` | `string` | elysia/dist/types.d.ts:569 | | `access-control-allow-origin?` | `string` | elysia/dist/types.d.ts:566 | | `access-control-expose-headers?` | `string` | elysia/dist/types.d.ts:570 | | `access-control-max-age?` | `string` | elysia/dist/types.d.ts:571 | | `access-control-request-headers?` | `string` | elysia/dist/types.d.ts:572 | | `access-control-request-method?` | `string` | elysia/dist/types.d.ts:573 | | `age?` | `string` | elysia/dist/types.d.ts:544 | | `allow?` | `string` | elysia/dist/types.d.ts:586 | | `alt-svg?` | `string` | elysia/dist/types.d.ts:611 | | `alt-used?` | `string` | elysia/dist/types.d.ts:612 | | `authorization?` | `string` | elysia/dist/types.d.ts:541 | | `cache-control?` | `string` | elysia/dist/types.d.ts:545 | | `clear-site-data?` | `string` | elysia/dist/types.d.ts:546 | | `connection?` | `string` | elysia/dist/types.d.ts:557 | | `content-disposition?` | `string` | elysia/dist/types.d.ts:576 | | `content-encoding?` | `string` | elysia/dist/types.d.ts:579 | | `content-language?` | `string` | elysia/dist/types.d.ts:580 | | `content-length?` | `string` \| `number` | elysia/dist/types.d.ts:577 | | `content-location?` | `string` | elysia/dist/types.d.ts:581 | | `content-range?` | `string` | elysia/dist/types.d.ts:591 | | `content-security-policy?` | `string` | elysia/dist/types.d.ts:592 | | `content-security-policy-report-only?` | `string` | elysia/dist/types.d.ts:593 | | `content-type?` | `SetContentType` \| `string` & `object` | elysia/dist/types.d.ts:578 | | `cookie?` | `string` | elysia/dist/types.d.ts:564 | | `cross-origin-embedder-policy?` | `string` | elysia/dist/types.d.ts:594 | | `cross-origin-opener-policy?` | `string` | elysia/dist/types.d.ts:595 | | `cross-origin-resource-policy?` | `string` | elysia/dist/types.d.ts:596 | | `date?` | `string` | elysia/dist/types.d.ts:613 | | `dnt?` | `string` | elysia/dist/types.d.ts:614 | | `early-data?` | `string` | elysia/dist/types.d.ts:615 | | `etag?` | `string` | elysia/dist/types.d.ts:551 | | `expect?` | `string` | elysia/dist/types.d.ts:562 | | `expect-ct?` | `string` | elysia/dist/types.d.ts:597 | | `expires?` | `string` | elysia/dist/types.d.ts:547 | | `forwarded?` | `string` | elysia/dist/types.d.ts:582 | | `if-match?` | `string` | elysia/dist/types.d.ts:552 | | `if-modified-since?` | `string` | elysia/dist/types.d.ts:554 | | `if-none-match?` | `string` | elysia/dist/types.d.ts:553 | | `if-range?` | `string` | elysia/dist/types.d.ts:590 | | `if-unmodified-since?` | `string` | elysia/dist/types.d.ts:555 | | `keep-alive?` | `string` | elysia/dist/types.d.ts:558 | | `large-allocation?` | `string` | elysia/dist/types.d.ts:616 | | `last-event-id?` | `string` | elysia/dist/types.d.ts:604 | | `last-modified?` | `string` | elysia/dist/types.d.ts:550 | | `link?` | `string` | elysia/dist/types.d.ts:617 | | `location?` | `string` | elysia/dist/types.d.ts:584 | | `max-forwards?` | `string` | elysia/dist/types.d.ts:563 | | `no-vary-search?` | `string` | elysia/dist/types.d.ts:548 | | `origin?` | `string` | elysia/dist/types.d.ts:574 | | `permission-policy?` | `string` | elysia/dist/types.d.ts:598 | | `ping-from?` | `string` | elysia/dist/types.d.ts:605 | | `ping-to?` | `string` | elysia/dist/types.d.ts:606 | | `pragma?` | `string` | elysia/dist/types.d.ts:549 | | `proxy-authenticate?` | `string` | elysia/dist/types.d.ts:542 | | `proxy-authorization?` | `string` | elysia/dist/types.d.ts:543 | | `range?` | `string` | elysia/dist/types.d.ts:589 | | `refresh?` | `string` | elysia/dist/types.d.ts:585 | | `report-to?` | `string` | elysia/dist/types.d.ts:607 | | `retry-after?` | `string` | elysia/dist/types.d.ts:618 | | `server?` | `"Elysia"` \| `string` & `object` | elysia/dist/types.d.ts:587 | | `service-worker-allowed?` | `string` | elysia/dist/types.d.ts:619 | | `set-cookie?` | `string` \| `string`\[] | elysia/dist/types.d.ts:565 | | `source-map?` | `string` | elysia/dist/types.d.ts:620 | | `strict-transport-security?` | `string` | elysia/dist/types.d.ts:599 | | `te?` | `string` | elysia/dist/types.d.ts:608 | | `timing-allow-origin?` | `string` | elysia/dist/types.d.ts:575 | | `trailer?` | `string` | elysia/dist/types.d.ts:609 | | `transfer-encoding?` | `string` | elysia/dist/types.d.ts:610 | | `upgrade?` | `string` | elysia/dist/types.d.ts:621 | | `upgrade-insecure-requests?` | `string` | elysia/dist/types.d.ts:600 | | `vary?` | `string` | elysia/dist/types.d.ts:556 | | `via?` | `string` | elysia/dist/types.d.ts:583 | | `www-authenticate?` | `string` | elysia/dist/types.d.ts:540 | | `x-content-type-options?` | `string` | elysia/dist/types.d.ts:601 | | `x-dns-prefetch-control?` | `string` | elysia/dist/types.d.ts:622 | | `x-forwarded-for?` | `string` | elysia/dist/types.d.ts:623 | | `x-forwarded-host?` | `string` | elysia/dist/types.d.ts:624 | | `x-forwarded-proto?` | `string` | elysia/dist/types.d.ts:625 | | `x-frame-options?` | `string` | elysia/dist/types.d.ts:602 | | `x-powered-by?` | `"Elysia"` \| `string` & `object` | elysia/dist/types.d.ts:626 | | `x-request-id?` | `string` | elysia/dist/types.d.ts:627 | | `x-requested-with?` | `string` | elysia/dist/types.d.ts:628 | | `x-robots-tag?` | `string` | elysia/dist/types.d.ts:629 | | `x-ua-compatible?` | `string` | elysia/dist/types.d.ts:630 | | `x-xss-protection?` | `string` | elysia/dist/types.d.ts:603 | --- Source: [reference/type-aliases/macrotocontext/index.md](reference/type-aliases/macrotocontext/index.md) # MacroToContext ```ts type MacroToContext = Prettify>, Definitions, R> extends infer A ? { [K in Exclude]: K extends "derive" ? UnionToIntersect : K extends "meta" ? (A[K] & { value: unknown })["value"] : A[K] } & Prettify<{ return: FlattenMacroResponse; }> : object>; ``` Defined in: elysia/dist/types.d.ts:840 ## Type Parameters | Type Parameter | Default type | | ----------------------------------------------------------- | ------------ | | `MacroFn` *extends* [`Macro`](/reference/interfaces/macro/) | `object` | | `SelectedMacro` *extends* `BaseMacro` | `object` | | `Definitions` *extends* `DefinitionBase`\[`"typebox"`] | `object` | | `R` *extends* `1`\[] | \[] | --- Source: [reference/type-aliases/macrotoproperty/index.md](reference/type-aliases/macrotoproperty/index.md) # MacroToProperty ```ts type MacroToProperty = Prettify<{ [K in keyof T]: T[K] extends Function ? T[K] extends (a: infer Params) => any ? MacroOptionLambda extends never ? Params : unknown : boolean : boolean }>; ``` Defined in: elysia/dist/types.d.ts:707 ## Type Parameters | Type Parameter | | -------------- | | `T` | --- Source: [reference/type-aliases/numericstatus/index.md](reference/type-aliases/numericstatus/index.md) # NumericStatus ```ts type NumericStatus = Code extends keyof StatusMap ? StatusMap[Code] : Code; ``` Defined in: elysia/dist/error.d.ts:160 Numeric form of a status name or code. ## Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------ | | `Code` *extends* `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | --- Source: [reference/type-aliases/problem/index.md](reference/type-aliases/problem/index.md) # Problem ```ts type Problem = object & Extension; ``` Defined in: elysia/dist/error.d.ts:145 RFC 9457 Problem Details shape. ## Type Declaration | Name | Type | Description | Defined in | | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `code?` | `string` | Extension member carrying a stable machine-readable token. `type` may be widened to a URI by [HTTPError.typeBase](/reference/classes/httperror/#typebase), `code` never is | elysia/dist/error.d.ts:153 | | `detail?` | `string` | - | elysia/dist/error.d.ts:156 | | `instance?` | `string` | - | elysia/dist/error.d.ts:157 | | `status?` | `Code` | - | elysia/dist/error.d.ts:155 | | `title?` | `string` | - | elysia/dist/error.d.ts:154 | | `type?` | `string` | URI (or slug) identifying the problem type. **Default** `'about:blank'` | elysia/dist/error.d.ts:146 | ## Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | `Code` *extends* `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | | `Extension` *extends* `Record`<`string`, `unknown`> | `object` | ## See --- Source: [reference/type-aliases/redirect/index.md](reference/type-aliases/redirect/index.md) # redirect ```ts type redirect = typeof redirect; ``` Defined in: elysia/dist/utils.d.ts:124 ## Param **url** URL to redirect to ## Param **HTTP** status code to send, --- Source: [reference/type-aliases/selectiveproblem/index.md](reference/type-aliases/selectiveproblem/index.md) # SelectiveProblem ```ts type SelectiveProblem = (code, ...detail) => ElysiaStatus, ResponseOf>; ``` Defined in: elysia/dist/error.d.ts:221 ## Type Parameters | Type Parameter | | -------------- | | `Res` | ## Type Parameters | Type Parameter | Default type | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | `Code` *extends* \| keyof `Res` \| [`StatusMapBack`](/reference/type-aliases/statusmapback/)\[`Extract`\] | - | | `T` *extends* `ProblemInput`<`ResponseOf`<`Res`, `Code`>> | `ProblemInput`<`ResponseOf`<`Res`, `Code`>> | ## Parameters | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `code` | `Code` | | …`detail` | `object` *extends* `ProblemInput`<`ResponseOf`<`Res`, `Code`>> ? \[`CheckExcessProps`<`T`, `ProblemInput`<`ResponseOf`<`Res`, `Code`>>>] : \[`CheckExcessProps`<`T`, `ProblemInput`<`ResponseOf`<`Res`, `Code`>>>] | ## Returns [`ElysiaStatus`](/reference/classes/elysiastatus/)<[`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`>, `ResponseOf`<`Res`, `Code`>> --- Source: [reference/type-aliases/selectivestatus/index.md](reference/type-aliases/selectivestatus/index.md) # SelectiveStatus ```ts type SelectiveStatus = (code, response) => ElysiaStatus; ``` Defined in: elysia/dist/error.d.ts:218 ## Type Parameters | Type Parameter | | -------------- | | `Res` | ## Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Code` *extends* \| keyof `Res` \| [`StatusMapBack`](/reference/type-aliases/statusmapback/)\[`Extract`\] | | `T` *extends* `ResponseOf`<`Res`, `Code`> | ## Parameters | Parameter | Type | | ---------- | ---------------------------------------------------- | | `code` | `Code` | | `response` | `CheckExcessProps`<`T`, `ResponseOf`<`Res`, `Code`>> | ## Returns [`ElysiaStatus`](/reference/classes/elysiastatus/)<`Code`, `T`> --- Source: [reference/type-aliases/server/index.md](reference/type-aliases/server/index.md) # Server ```ts type Server = Equal, unknown> extends false ? Server$1 : ServerOptions; ``` Defined in: elysia/dist/universal/server.d.ts:25 --- Source: [reference/type-aliases/ssepayload/index.md](reference/type-aliases/ssepayload/index.md) # SSEPayload ```ts type SSEPayload = object; ``` Defined in: elysia/dist/types.d.ts:159 ## Type Parameters | Type Parameter | Default type | | ----------------------------------------- | ----------------------- | | `Data` | `unknown` | | `Event` *extends* `string` \| `undefined` | `string` \| `undefined` | ## Properties ### data? ```ts optional data?: Data; ``` Defined in: elysia/dist/types.d.ts:163 *** ### event? ```ts optional event?: Event; ``` Defined in: elysia/dist/types.d.ts:161 *** ### id? ```ts optional id?: string | number | null; ``` Defined in: elysia/dist/types.d.ts:160 *** ### retry? ```ts optional retry?: number; ``` Defined in: elysia/dist/types.d.ts:162 --- Source: [reference/type-aliases/statusmap/index.md](reference/type-aliases/statusmap/index.md) # StatusMap ```ts type StatusMap = typeof StatusMap; ``` Defined in: elysia/dist/constants.d.ts:2 --- Source: [reference/type-aliases/statusmapback/index.md](reference/type-aliases/statusmapback/index.md) # StatusMapBack ```ts type StatusMapBack = typeof StatusMapBack; ``` Defined in: elysia/dist/constants.d.ts:69 --- Source: [reference/type-aliases/statusresponse/index.md](reference/type-aliases/statusresponse/index.md) # StatusResponse ```ts type StatusResponse = Code extends keyof StatusMapBack ? StatusMapBack[Code] : Code; ``` Defined in: elysia/dist/error.d.ts:123 Default body for a status with no response. Named to keep emitted types small. ## Type Parameters | Type Parameter | | ------------------------------------------------------------------------------------ | | `Code` *extends* `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | --- Source: [reference/type-aliases/taggedhttperror/index.md](reference/type-aliases/taggedhttperror/index.md) # TaggedHTTPError ```ts type TaggedHTTPError = HTTPError & object & Annotation; ``` Defined in: elysia/dist/error.d.ts:225 Class returned by [HTTPError.id](/reference/classes/httperror/#id) ## Type Parameters | Type Parameter | Default type | | ------------------------- | ------------ | | `Type` *extends* `string` | - | | `Annotation` | `object` | ```ts type new TaggedHTTPError(message?, options?): HTTPError & object & Annotation; ``` Class returned by [HTTPError.id](/reference/classes/httperror/#id) ## Parameters | Parameter | Type | | ---------- | -------------- | | `message?` | `string` | | `options?` | `ErrorOptions` | ## Returns [`HTTPError`](/reference/classes/httperror/)<`Type`> & `object` & `Annotation` ## Properties ### prototype ```ts prototype: HTTPError; ``` Defined in: elysia/dist/error.d.ts:230 --- Source: [reference/type-aliases/tfiles/index.md](reference/type-aliases/tfiles/index.md) # TFiles ```ts type TFiles = Type.TUnsafe; ``` Defined in: elysia/dist/type/elysia/files.d.ts:5 --- Source: [reference/type-aliases/unwrapmodels/index.md](reference/type-aliases/unwrapmodels/index.md) # UnwrapModels ```ts type UnwrapModels = Iteration["length"] extends 8 ? T : { [K in keyof T]: T[K] extends AnySchema ? UnwrapSchema : T[K] extends Record ? UnwrapModels : T[K] }; ``` Defined in: elysia/dist/types.d.ts:706 ## Type Parameters | Type Parameter | Default type | | ------------------------------------------- | ------------ | | `T` *extends* `Record`<`string`, `unknown`> | - | | `Iteration` *extends* `number`\[] | \[] | --- Source: [reference/type-aliases/unwrapschema/index.md](reference/type-aliases/unwrapschema/index.md) # UnwrapSchema ```ts type UnwrapSchema = Schema extends undefined ? unknown : Schema extends TypeBoxSchema ? true extends IsOptionalRoot ? Partial> : StaticCyclic : Schema extends StandardSchemaV1Like ? NonNullable[Side] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TypeBoxSchema ? true extends IsOptionalRoot ? Partial> : StaticCyclic : Definitions[Schema] extends StandardSchemaV1Like ? NonNullable<...[...]["types"]>[Side] : unknown : unknown : unknown; ``` Defined in: elysia/dist/types.d.ts:642 ## Type Parameters | Type Parameter | Default type | | ----------------------------------------------------------------------------------------------- | ------------ | | `Schema` *extends* [`AnySchema`](/reference/type-aliases/anyschema/) \| `string` \| `undefined` | - | | `Definitions` *extends* `DefinitionBase`\[`"typebox"`] | `object` | | `Side` *extends* `SchemaSide` | `"output"` | --- Source: [reference/type-aliases/validationerrorresponse/index.md](reference/type-aliases/validationerrorresponse/index.md) # ValidationErrorResponse ```ts type ValidationErrorResponse = object; ``` Defined in: elysia/dist/error.d.ts:165 Request validation problem. Diagnostic fields are development-only. ## Properties ### code ```ts code: "validation"; ``` Defined in: elysia/dist/error.d.ts:167 *** ### detail? ```ts optional detail?: string; ``` Defined in: elysia/dist/error.d.ts:170 *** ### expected? ```ts optional expected?: string; ``` Defined in: elysia/dist/error.d.ts:174 *** ### found? ```ts optional found?: unknown; ``` Defined in: elysia/dist/error.d.ts:172 *** ### on ```ts on: string; ``` Defined in: elysia/dist/error.d.ts:171 *** ### property? ```ts optional property?: string; ``` Defined in: elysia/dist/error.d.ts:173 *** ### status ```ts status: 422; ``` Defined in: elysia/dist/error.d.ts:169 *** ### title ```ts title: "Validation Error"; ``` Defined in: elysia/dist/error.d.ts:168 *** ### type ```ts type: "validation"; ``` Defined in: elysia/dist/error.d.ts:166 --- Source: [reference/variables/env/index.md](reference/variables/env/index.md) # env ```ts const env: any; ``` Defined in: elysia/dist/universal/env.d.ts:2 --- Source: [reference/variables/file/index.md](reference/variables/file/index.md) # file ```ts const file: (path) => ElysiaFile; ``` Defined in: elysia/dist/universal/file.d.ts:43 Serve a file from a path as the response. ## Parameters | Parameter | Type | | --------- | -------- | | `path` | `string` | ## Returns [`ElysiaFile`](/reference/classes/elysiafile/) --- Source: [reference/variables/form/index.md](reference/variables/form/index.md) # form ```ts const form: (value) => ElysiaFormData; ``` Defined in: elysia/dist/utils.d.ts:144 Return a `multipart/form-data` response. ## Type Parameters | Type Parameter | | ----------------------------------------------- | | `T` *extends* `Record`\ | ## Parameters | Parameter | Type | | --------- | ---- | | `value` | `T` | ## Returns `ElysiaFormData`<`T`> ## Example ```ts import { Elysia, form, file } from 'elysia' new Elysia().get('/', () => form({ name: 'Tea Party', images: [file('1.webp'), file('2.webp')] }) ) ``` --- Source: [reference/variables/macrotype/index.md](reference/variables/macrotype/index.md) # macroType ```ts const macroType: () => L | undefined; ``` Defined in: elysia/dist/utils.d.ts:145 ## Type Parameters | Type Parameter | | ------------------------------------------------------------------------- | | `L` *extends* [`MacroTypeLambda`](/reference/interfaces/macrotypelambda/) | ## Returns `L` | `undefined` --- Source: [reference/variables/manifest/index.md](reference/variables/manifest/index.md) # Manifest ```ts const Manifest: object; ``` Defined in: elysia/dist/compile/aot.d.ts:231 ## Type Declaration | Name | Type | Defined in | | --------------------- | --------------------------------------------- | -------------------------------- | | []()`get()` | (`loc`) => `CapturedValidator` \| `undefined` | elysia/dist/compile/aot.d.ts:233 | | []()`handler` | *typeof* `captureHandler` | elysia/dist/compile/aot.d.ts:238 | | []()`isAotBuildEnv()` | () => `boolean` | elysia/dist/compile/aot.d.ts:239 | | []()`set` | *typeof* `captureSet` | elysia/dist/compile/aot.d.ts:232 | --- Source: [reference/variables/redirect/index.md](reference/variables/redirect/index.md) # redirect ```ts const redirect: (url, status?) => Response; ``` Defined in: elysia/dist/utils.d.ts:124 ## Parameters | Parameter | Type | Description | | --------- | ----------------------------------------- | ------------------ | | `url` | `string` | URL to redirect to | | `status?` | `301` \| `302` \| `303` \| `307` \| `308` | - | ## Returns `Response` --- Source: [reference/variables/sse/index.md](reference/variables/sse/index.md) # sse ```ts const sse: (_payload) => T extends string ? object : T extends SSEPayload ? T : T extends ReadableStream ? ReadableStream> : T extends Generator ? Generator, B, C> : T extends AsyncGenerator ? AsyncGenerator, B_1, C_1> : T; ``` Defined in: elysia/dist/utils.d.ts:161 ## Type Parameters | Type Parameter | | --------------------------------------------------------------------------------------------------------------------------------------- | | `T` *extends* \| `string` \| [`SSEPayload`](/reference/type-aliases/ssepayload/) \| `Generator` \| `AsyncGenerator` \| `ReadableStream` | ## Parameters | Parameter | Type | | ---------- | ---- | | `_payload` | `T` | ## Returns `T` *extends* `string` ? `object` : `T` *extends* [`SSEPayload`](/reference/type-aliases/ssepayload/) ? `T` : `T` *extends* `ReadableStream`\ ? `ReadableStream`<`FormatSSEPayload`<`A`>> : `T` *extends* `Generator`\ ? `Generator`<`FormatSSEPayload`<`A`>, `B`, `C`> : `T` *extends* `AsyncGenerator`\ ? `AsyncGenerator`<`FormatSSEPayload`<`A`>, `B_1`, `C_1`> : `T` --- Source: [reference/variables/status/index.md](reference/variables/status/index.md) # status ```ts const status: (code, response?) => ElysiaStatus>; ``` Defined in: elysia/dist/error.d.ts:138 ## Type Parameters | Type Parameter | Default type | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | `Code` *extends* `number` \| keyof [`StatusMap`](/reference/type-aliases/statusmap/) | - | | `T` | [`StatusResponse`](/reference/type-aliases/statusresponse/)<`Code`> | ## Parameters | Parameter | Type | | ----------- | ------ | | `code` | `Code` | | `response?` | `T` | ## Returns [`ElysiaStatus`](/reference/classes/elysiastatus/)<`Code`, `T`, [`NumericStatus`](/reference/type-aliases/numericstatus/)<`Code`>> --- Source: [reference/variables/statusmap/index.md](reference/variables/statusmap/index.md) # StatusMap ```ts const StatusMap: object; ``` Defined in: elysia/dist/constants.d.ts:2 ## Type Declaration | Name | Type | Defined in | | ------------------------------------- | ----- | ----------------------------- | | []()`Accepted` | `202` | elysia/dist/constants.d.ts:9 | | []()`Already Reported` | `208` | elysia/dist/constants.d.ts:15 | | []()`Bad Gateway` | `502` | elysia/dist/constants.d.ts:55 | | []()`Bad Request` | `400` | elysia/dist/constants.d.ts:23 | | []()`Conflict` | `409` | elysia/dist/constants.d.ts:32 | | []()`Continue` | `100` | elysia/dist/constants.d.ts:3 | | []()`Created` | `201` | elysia/dist/constants.d.ts:8 | | []()`Early Hints` | `103` | elysia/dist/constants.d.ts:6 | | []()`Enhance Your Calm` | `420` | elysia/dist/constants.d.ts:42 | | []()`Expectation Failed` | `417` | elysia/dist/constants.d.ts:40 | | []()`Failed Dependency` | `424` | elysia/dist/constants.d.ts:46 | | []()`Forbidden` | `403` | elysia/dist/constants.d.ts:26 | | []()`Found` | `302` | elysia/dist/constants.d.ts:18 | | []()`Gateway Timeout` | `504` | elysia/dist/constants.d.ts:57 | | []()`Gone` | `410` | elysia/dist/constants.d.ts:33 | | []()`HTTP Version Not Supported` | `505` | elysia/dist/constants.d.ts:58 | | []()`I'm a teapot` | `418` | elysia/dist/constants.d.ts:41 | | []()`Insufficient Storage` | `507` | elysia/dist/constants.d.ts:60 | | []()`Internal Server Error` | `500` | elysia/dist/constants.d.ts:53 | | []()`Length Required` | `411` | elysia/dist/constants.d.ts:34 | | []()`Locked` | `423` | elysia/dist/constants.d.ts:45 | | []()`Loop Detected` | `508` | elysia/dist/constants.d.ts:61 | | []()`Method Not Allowed` | `405` | elysia/dist/constants.d.ts:28 | | []()`Misdirected Request` | `421` | elysia/dist/constants.d.ts:43 | | []()`Moved Permanently` | `301` | elysia/dist/constants.d.ts:17 | | []()`Multi-Status` | `207` | elysia/dist/constants.d.ts:14 | | []()`Multiple Choices` | `300` | elysia/dist/constants.d.ts:16 | | []()`Network Authentication Required` | `511` | elysia/dist/constants.d.ts:63 | | []()`No Content` | `204` | elysia/dist/constants.d.ts:11 | | []()`Non-Authoritative Information` | `203` | elysia/dist/constants.d.ts:10 | | []()`Not Acceptable` | `406` | elysia/dist/constants.d.ts:29 | | []()`Not Extended` | `510` | elysia/dist/constants.d.ts:62 | | []()`Not Found` | `404` | elysia/dist/constants.d.ts:27 | | []()`Not Implemented` | `501` | elysia/dist/constants.d.ts:54 | | []()`Not Modified` | `304` | elysia/dist/constants.d.ts:20 | | []()`OK` | `200` | elysia/dist/constants.d.ts:7 | | []()`Partial Content` | `206` | elysia/dist/constants.d.ts:13 | | []()`Payload Too Large` | `413` | elysia/dist/constants.d.ts:36 | | []()`Payment Required` | `402` | elysia/dist/constants.d.ts:25 | | []()`Permanent Redirect` | `308` | elysia/dist/constants.d.ts:22 | | []()`Precondition Failed` | `412` | elysia/dist/constants.d.ts:35 | | []()`Precondition Required` | `428` | elysia/dist/constants.d.ts:49 | | []()`Processing` | `102` | elysia/dist/constants.d.ts:5 | | []()`Proxy Authentication Required` | `407` | elysia/dist/constants.d.ts:30 | | []()`Range Not Satisfiable` | `416` | elysia/dist/constants.d.ts:39 | | []()`Request Header Fields Too Large` | `431` | elysia/dist/constants.d.ts:51 | | []()`Request Timeout` | `408` | elysia/dist/constants.d.ts:31 | | []()`Reset Content` | `205` | elysia/dist/constants.d.ts:12 | | []()`See Other` | `303` | elysia/dist/constants.d.ts:19 | | []()`Service Unavailable` | `503` | elysia/dist/constants.d.ts:56 | | []()`Switching Protocols` | `101` | elysia/dist/constants.d.ts:4 | | []()`Temporary Redirect` | `307` | elysia/dist/constants.d.ts:21 | | []()`Too Early` | `425` | elysia/dist/constants.d.ts:47 | | []()`Too Many Requests` | `429` | elysia/dist/constants.d.ts:50 | | []()`Unauthorized` | `401` | elysia/dist/constants.d.ts:24 | | []()`Unavailable For Legal Reasons` | `451` | elysia/dist/constants.d.ts:52 | | []()`Unprocessable Content` | `422` | elysia/dist/constants.d.ts:44 | | []()`Unsupported Media Type` | `415` | elysia/dist/constants.d.ts:38 | | []()`Upgrade Required` | `426` | elysia/dist/constants.d.ts:48 | | []()`URI Too Long` | `414` | elysia/dist/constants.d.ts:37 | | []()`Variant Also Negotiates` | `506` | elysia/dist/constants.d.ts:59 | --- Source: [reference/variables/statusmapback/index.md](reference/variables/statusmapback/index.md) # StatusMapBack ```ts const StatusMapBack: object; ``` Defined in: elysia/dist/constants.d.ts:69 ## Type Declaration | Name | Type | Defined in | | --------- | ----------------------------------- | ------------------------------ | | []()`100` | `"Continue"` | elysia/dist/constants.d.ts:70 | | []()`101` | `"Switching Protocols"` | elysia/dist/constants.d.ts:71 | | []()`102` | `"Processing"` | elysia/dist/constants.d.ts:72 | | []()`103` | `"Early Hints"` | elysia/dist/constants.d.ts:73 | | []()`200` | `"OK"` | elysia/dist/constants.d.ts:74 | | []()`201` | `"Created"` | elysia/dist/constants.d.ts:75 | | []()`202` | `"Accepted"` | elysia/dist/constants.d.ts:76 | | []()`203` | `"Non-Authoritative Information"` | elysia/dist/constants.d.ts:77 | | []()`204` | `"No Content"` | elysia/dist/constants.d.ts:78 | | []()`205` | `"Reset Content"` | elysia/dist/constants.d.ts:79 | | []()`206` | `"Partial Content"` | elysia/dist/constants.d.ts:80 | | []()`207` | `"Multi-Status"` | elysia/dist/constants.d.ts:81 | | []()`208` | `"Already Reported"` | elysia/dist/constants.d.ts:82 | | []()`300` | `"Multiple Choices"` | elysia/dist/constants.d.ts:83 | | []()`301` | `"Moved Permanently"` | elysia/dist/constants.d.ts:84 | | []()`302` | `"Found"` | elysia/dist/constants.d.ts:85 | | []()`303` | `"See Other"` | elysia/dist/constants.d.ts:86 | | []()`304` | `"Not Modified"` | elysia/dist/constants.d.ts:87 | | []()`307` | `"Temporary Redirect"` | elysia/dist/constants.d.ts:88 | | []()`308` | `"Permanent Redirect"` | elysia/dist/constants.d.ts:89 | | []()`400` | `"Bad Request"` | elysia/dist/constants.d.ts:90 | | []()`401` | `"Unauthorized"` | elysia/dist/constants.d.ts:91 | | []()`402` | `"Payment Required"` | elysia/dist/constants.d.ts:92 | | []()`403` | `"Forbidden"` | elysia/dist/constants.d.ts:93 | | []()`404` | `"Not Found"` | elysia/dist/constants.d.ts:94 | | []()`405` | `"Method Not Allowed"` | elysia/dist/constants.d.ts:95 | | []()`406` | `"Not Acceptable"` | elysia/dist/constants.d.ts:96 | | []()`407` | `"Proxy Authentication Required"` | elysia/dist/constants.d.ts:97 | | []()`408` | `"Request Timeout"` | elysia/dist/constants.d.ts:98 | | []()`409` | `"Conflict"` | elysia/dist/constants.d.ts:99 | | []()`410` | `"Gone"` | elysia/dist/constants.d.ts:100 | | []()`411` | `"Length Required"` | elysia/dist/constants.d.ts:101 | | []()`412` | `"Precondition Failed"` | elysia/dist/constants.d.ts:102 | | []()`413` | `"Payload Too Large"` | elysia/dist/constants.d.ts:103 | | []()`414` | `"URI Too Long"` | elysia/dist/constants.d.ts:104 | | []()`415` | `"Unsupported Media Type"` | elysia/dist/constants.d.ts:105 | | []()`416` | `"Range Not Satisfiable"` | elysia/dist/constants.d.ts:106 | | []()`417` | `"Expectation Failed"` | elysia/dist/constants.d.ts:107 | | []()`418` | `"I'm a teapot"` | elysia/dist/constants.d.ts:108 | | []()`420` | `"Enhance Your Calm"` | elysia/dist/constants.d.ts:109 | | []()`421` | `"Misdirected Request"` | elysia/dist/constants.d.ts:110 | | []()`422` | `"Unprocessable Content"` | elysia/dist/constants.d.ts:111 | | []()`423` | `"Locked"` | elysia/dist/constants.d.ts:112 | | []()`424` | `"Failed Dependency"` | elysia/dist/constants.d.ts:113 | | []()`425` | `"Too Early"` | elysia/dist/constants.d.ts:114 | | []()`426` | `"Upgrade Required"` | elysia/dist/constants.d.ts:115 | | []()`428` | `"Precondition Required"` | elysia/dist/constants.d.ts:116 | | []()`429` | `"Too Many Requests"` | elysia/dist/constants.d.ts:117 | | []()`431` | `"Request Header Fields Too Large"` | elysia/dist/constants.d.ts:118 | | []()`451` | `"Unavailable For Legal Reasons"` | elysia/dist/constants.d.ts:119 | | []()`500` | `"Internal Server Error"` | elysia/dist/constants.d.ts:120 | | []()`501` | `"Not Implemented"` | elysia/dist/constants.d.ts:121 | | []()`502` | `"Bad Gateway"` | elysia/dist/constants.d.ts:122 | | []()`503` | `"Service Unavailable"` | elysia/dist/constants.d.ts:123 | | []()`504` | `"Gateway Timeout"` | elysia/dist/constants.d.ts:124 | | []()`505` | `"HTTP Version Not Supported"` | elysia/dist/constants.d.ts:125 | | []()`506` | `"Variant Also Negotiates"` | elysia/dist/constants.d.ts:126 | | []()`507` | `"Insufficient Storage"` | elysia/dist/constants.d.ts:127 | | []()`508` | `"Loop Detected"` | elysia/dist/constants.d.ts:128 | | []()`510` | `"Not Extended"` | elysia/dist/constants.d.ts:129 | | []()`511` | `"Network Authentication Required"` | elysia/dist/constants.d.ts:130 | --- Source: [reference/variables/t/index.md](reference/variables/t/index.md) # t ```ts const t: TypeBuilder; ``` Defined in: elysia/dist/type/index.d.ts:11 --- Source: [reference/variables/typesystem/index.md](reference/variables/typesystem/index.md) # TypeSystem ```ts const TypeSystem: typeof System; ``` Defined in: elysia/dist/type/index.d.ts:19 `typebox/system` is a subgraph of `typebox/type`, so a static re-export here would pin \~231 KB of the deferral back into the eager graph. The proxy also guarantees Elysia’s own `Settings` default lands BEFORE the namespace is handed out, keeping a user’s explicit `TypeSystem.Settings.Set(...)` last — the ordering the eager `setupTypebox()` used to provide --- Source: [reference/variables/validationdetail/index.md](reference/variables/validationdetail/index.md) # validationDetail ```ts const validationDetail: (message) => (error) => any; ``` Defined in: elysia/dist/error.d.ts:39 Wrap a string into a TypeBox `error` callback that overrides the default validation message. Use as `t.Number({ error: validationDetail('x must be a number') })`. ## Type Parameters | Type Parameter | | -------------- | | `T` | ## Parameters | Parameter | Type | | --------- | ---- | | `message` | `T` | ## Returns (`error`) => `any` --- Source: [table-of-content/index.md](table-of-content/index.md) # Table of contents There’s no single way to learn Elysia. The interactive tutorial is a good place to get familiar with it: [Interactive tutorial](/tutorial/) [Learn Elysia with an editor and playground in your browser.](/tutorial/) [Ask Elysia](https://elysiajs.com/) [Not sure what you’re looking for? Use the Ask Elysia assistant on elysiajs.com. That site documents Elysia 1.x, so its answers may not match v2.](https://elysiajs.com/) ## First up Start with these two pages for the core concepts and recommended patterns: ### [Key concept](/key-concept) [Understand Elysia’s core concepts and how to use them effectively](/key-concept) ### [Best practice](/essential/best-practice) [Learn recommended patterns for writing Elysia applications](/essential/best-practice) ### AI skills and LLM references Elysia offers `llms.txt` and skills for AI agents such as **Claude Code** and **OpenCode**. Select a skill version that matches the Elysia version in your project. ```plaintext bunx skills add elysiajs/skills ``` ### [llms.txt](/llms.txt) [Read this site’s Elysia v2 documentation index for use with LLMs.](/llms.txt) ### [llms-full.txt](/llms-full.txt) [Read this site’s Elysia v2 documentation in one Markdown file.](/llms-full.txt) ## From other frameworks If you have used other popular frameworks like Express, Fastify, or Hono, you will find Elysia right at home with just a few differences. ### [From Express](/migrate/from-express) [Comparison between Express and Elysia](/migrate/from-express) ### [From Fastify](/migrate/from-fastify) [Comparison between Fastify and Elysia](/migrate/from-fastify) ### [From Hono](/migrate/from-hono) [Comparison between Hono and Elysia](/migrate/from-hono) ### [From tRPC](/migrate/from-trpc) [Comparison between tRPC and Elysia](/migrate/from-trpc) ## Essential chapter Here are the foundations of Elysia. We highly recommend you go through these pages before jumping to other topics: ### [Routing](/essential/route) [Understand how routing works in Elysia](/essential/route) ### [Handler](/essential/handler) [Learn how to handle requests and return responses](/essential/handler) ### [Validation](/essential/validation) [How to enforce type safety with Elysia](/essential/validation) ### [Life cycle](/essential/life-cycle) [Learn the stages of the request lifecycle](/essential/life-cycle) ### [Plugin](/essential/plugin) [Learn how to compose Elysia applications with plugins](/essential/plugin) ## More patterns If you feel like exploring more Elysia features, check out: ### [Handler](/essential/handler) [Learn how to send files, stream responses, and use server-sent events](/essential/handler) ### [WebSocket](/patterns/websocket) [Create real-time applications with Elysia](/patterns/websocket) ### [Eden](/eden) [Learn more about Eden and how to use it effectively](/eden) ### [OpenTelemetry](/patterns/opentelemetry) [Monitor your application with OpenTelemetry](/patterns/opentelemetry) ### [Deploy to production](/patterns/deploy) [Learn how to deploy Elysia to production](/patterns/deploy) ## Integration with meta frameworks You can also use Elysia with meta frameworks such as Next.js, Nuxt, and Astro. ### [Astro](/integrations/astro) [Use Elysia in Astro endpoints](/integrations/astro) ### [Expo](/integrations/expo) [Use Elysia in Expo API routes](/integrations/expo) ### [Next.js](/integrations/nextjs) [Use Elysia in Next.js route handlers](/integrations/nextjs) ### [Nuxt](/integrations/nuxt) [Use Elysia in Nuxt server routes](/integrations/nuxt) ### [SvelteKit](/integrations/sveltekit) [Use Elysia in SvelteKit endpoints](/integrations/sveltekit) ## Integration with your favorite tools We have some integrations with popular tools: ### [AI SDK](/integrations/ai-sdk) [Learn how to use Vercel AI SDK with Elysia](/integrations/ai-sdk) ### [Better Auth](/integrations/better-auth) [Learn how to use Better Auth with Elysia](/integrations/better-auth) ### [Drizzle](/integrations/drizzle) [Elysia provides type-safe utilities with Drizzle](/integrations/drizzle) ### [Prisma](/integrations/prisma) [Learn how to use Prisma with Elysia](/integrations/prisma) ### [React Email](/integrations/react-email) [Use JSX to create email templates](/integrations/react-email) ## Community and support If you are stuck, feel free to ask our community on GitHub Discussions, Discord, and Twitter. ### [Discord](https://discord.gg/eaFJ2KDJck) [Official ElysiaJS Discord community server](https://discord.gg/eaFJ2KDJck) ### [Twitter](https://twitter.com/elysiajs) [Track updates and status of Elysia](https://twitter.com/elysiajs) ### [GitHub](https://github.com/elysiajs) [Source code and development](https://github.com/elysiajs) *** We hope you will love Elysia as much as we do! --- Source: [tutorial/features/end-to-end-type-safety/index.md](tutorial/features/end-to-end-type-safety/index.md) # End-to-end type safety Elysia provides an end-to-end type safety between backend and frontend **without code generation** similar to tRPC, using [Eden](/eden/). ```typescript import { Elysia } from 'elysia' import { treaty } from '@elysia/eden' // Backend export const app = new Elysia() .get('/', 'Hello Elysia!') .listen(3000) // Frontend const client = treaty('localhost:3000') const { data, error } = await client.get() console.log(data) // Hello Elysia! ``` This works by inferring the types from the Elysia instance, and uses type hints to provide type safety for the client. See [Eden treaty](/eden/treaty/overview/). ## Assignment Let’s click the icon in the preview to see how’s the request is logged. --- Source: [tutorial/features/mount/index.md](tutorial/features/mount/index.md) # Mount Elysia provides a [Elysia.mount](/patterns/mount/) to interoperate between backend frameworks that is built on Web Standard like Hono, H3, etc. ```typescript import { Elysia, t } from 'elysia' import { Hono } from 'hono' const hono = new Hono() .get('/', (c) => c.text('Hello from Hono')) new Elysia() .get('/', 'Hello from Elysia') .mount('/hono', hono.fetch) .listen(3000) ``` This allows us to gradually migrate our application to Elysia, or use multiple frameworks in a single application. ## Assignment Let’s use the preview to **GET ‘/hono’** to see if our Hono route is working. Try to modify the code and see how it changes! --- Source: [tutorial/features/openapi/index.md](tutorial/features/openapi/index.md) # OpenAPI Elysia is built around OpenAPI, and supports OpenAPI documentation out of the box. We can use [OpenAPI plugin](/patterns/openapi/) to show an API documentation. ```typescript import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .post( '/', { body: t.Object({ age: t.Number() }) }, ({ body }) => body ) .listen(3000) ``` Once added, we can access our API documentation by switching the request bar path to `/openapi`. ## Detail We can provide API documentation with a `detail` field which follows OpenAPI 3.0 specification (with auto-completion): ```typescript import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .post( '/', { body: t.Object({ age: t.Number() }), detail: { summary: 'Create a user', description: 'Create a user with age', tags: ['User'], } }, ({ body }) => body ) .listen(3000) ``` ## Reference model We can also define reusable schema with [Reference model](/patterns/openapi/#reference-model): ```typescript import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) .model({ age: t.Object({ age: t.Number() }) }) .post( '/', { age: t.Object({ age: t.Number() }), body: 'age', detail: { summary: 'Create a user', description: 'Create a user with age', tags: ['User'], } }, ({ body }) => body ) .listen(3000) ``` When we defined a reference model, it will be shown in the **Components** section of the OpenAPI documentation. ## Type gen [OpenAPI type gen](/blog/openapi-type-gen/) can document your API **without manual annotation** infers directly from TypeScript type. No Zod, TypeBox, manual interface declaration, etc. **This feature is unique to Elysia**, and is not available in other JavaScript frameworks. For example, if you use Drizzle ORM or Prisma, Elysia can infer the schema directly from the query. ![Drizzle](/blog/openapi-type-gen/drizzle-typegen.webp) > Returning Drizzle query from Elysia route handler will be automatically inferred into OpenAPI schema. To use [OpenAPI type gen](/blog/openapi-type-gen/), simply apply the `fromTypes` plugin before `openapi` plugin. ```typescript import { Elysia } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' new Elysia() .use(openapi({ references: fromTypes() })) .get('/', { hello: 'world' }) .listen(3000) ``` ### Browser environment Unfortunately, this feature requires the **fs** module to read your source code, and is not available in this web playground. As Elysia is running directly in your browser (not a separated server). You can try this feature locally with [Type Gen Example repository](https://github.com/SaltyAom/elysia-typegen-example): Terminal window ```bash git clone https://github.com/SaltyAom/elysia-typegen-example && \ cd elysia-typegen-example && \ bun install && \ bun run dev ``` ## Assignment Let’s use the preview to **GET ‘/openapi’** and see how our API documentation looks. This API documentation is reflected from your code. Try to modify the code and see how the documentation changes! --- Source: [tutorial/features/unit-test/index.md](tutorial/features/unit-test/index.md) # Unit test Elysia provides a **Elysia.fetch** function to easily test your application. **Elysia.fetch** takes a Web Standard Request, and returns a Response similar to the [browser’s fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). ```typescript import { Elysia } from 'elysia' const app = new Elysia() .get('/', 'Hello World') const response = await app.fetch(new Request('http://localhost/')) console.log(await response.text()) ``` This will run a request like an **actual request** (not simulated). ### Test This allows us to easily test our application without running a server. Bun Test ```typescript import { describe, it, expect } from 'bun:test' import { Elysia } from 'elysia' describe('Elysia', () => { it('should return Hello World', async () => { const app = new Elysia().get('/', 'Hello World') const response = await app.fetch(new Request('http://localhost/')) const text = await response.text() expect(text).toBe('Hello World') }) }) ``` Vitest ```typescript import { describe, it, expect } from 'vitest' import { Elysia } from 'elysia' describe('Elysia', () => { it('should return Hello World', async () => { const app = new Elysia().get('/', 'Hello World') const response = await app.fetch(new Request('http://localhost/')) const text = await response.text() expect(text).toBe('Hello World') }) }) ``` Jest ```typescript import { describe, test, expect } from '@jest/globals' import { Elysia } from 'elysia' describe('Elysia', () => { test('should return Hello World', async () => { const app = new Elysia().get('/', 'Hello World') const response = await app.fetch(new Request('http://localhost/')) const text = await response.text() expect(text).toBe('Hello World') }) }) ``` See [Unit test](/patterns/unit-test/). ## Assignment Let’s click the icon in the preview to see how’s the request is logged. --- Source: [tutorial/getting-started/encapsulation/index.md](tutorial/getting-started/encapsulation/index.md) # Encapsulation Elysia hooks are **encapsulated** to its own instance only. If you create a new instance, it will not share hooks with other instances. ```ts import { Elysia } from 'elysia' const profile = new Elysia() .onBeforeHandle( ({ query: { name }, status }) => { if(!name) return status(401) } ) .get('/profile', () => 'Hi!') new Elysia() .use(profile) .patch('/rename', () => 'Ok! XD') .listen(3000) ``` > Try changing the path in the URL bar to **/rename** and see the result **Elysia isolates lifecycle** unless explicitly stated. This is similar to **export** in JavaScript, where you need to export the function to make it available outside the module. To **“export”** the lifecycle to other instances, you must specify the scope. ### Scope There are 3 scopes available: 1. **local** (default) - apply to only current instance and descendant only 2. **plugin** - apply to parent, current instance and descendants 3. **global** - apply to all instance that apply the plugin (all parents, current, and descendants) In our case, we want to apply the sign-in check to the `app` which is a direct parent, so we can use either **plugin** or **global**. ```ts import { Elysia } from 'elysia' const profile = new Elysia() .onBeforeHandle( 'plugin', ({ cookie }) => { throwIfNotSignIn(cookie) } ) .get('/profile', () => 'Hi there!') const app = new Elysia() .use(profile) // This has sign in check .patch('/rename', ({ body }) => updateProfile(body)) ``` Casting lifecycle to **“plugin”** will export lifecycle to **parent instance**. While **“global”** will export lifecycle to **all instances** that has a plugin. Learn more about this in [scope](/essential/plugin/#scope-level). ## Guard Similar to lifecycle, **schema** is also encapsulated to its own instance. We can specify guard scope similar to lifecycle. ```typescript import { Elysia, t } from 'elysia' const user = new Elysia() .guard('plugin', { query: t.Object({ age: t.Number(), name: t.Optional(t.String()) }), beforeHandle({ query: { age }, status }) { if(age < 18) return status(403) } }) .get('/profile', () => 'Hi!') .get('/settings', () => 'Settings') ``` It’s very important to note that every hook will affect all routes **after** its declaration. See [Scope](/essential/plugin/#scope) for more information. ## Assignment Let’s define a scope for `nameCheck`, and `ageCheck` to make our app works. --- Source: [tutorial/getting-started/guard/index.md](tutorial/getting-started/guard/index.md) # Guard When you need to apply multiple hooks to your application, instead of repeating hooks multiple times, you can use `guard` to bulk add hooks to your application. ```typescript import { Elysia, t } from 'elysia' new Elysia() .onBeforeHandle(({ query: { name }, status }) => { if(!name) return status(401) }) .onBeforeHandle(({ query: { name } }) => { console.log(name) }) .onAfterResponse(({ responseValue }) => { console.log(responseValue) }) .guard({ beforeHandle: [ ({ query: { name }, status }) => { if(!name) return status(401) }, ({ query: { name } }) => { console.log(name) } ], afterResponse({ responseValue }) { console.log(responseValue) } }) .get( '/auth', { query: t.Object({ name: t.String() }) }, ({ query: { name = 'anon' } }) => `Hello ${name}!` ) .get( '/profile', { query: t.Object({ name: t.String() }) }, ({ query: { name = 'anon' } }) => `Hello ${name}!` ) .listen(3000) ``` Not only that, you can also apply **schema** to multiple routes using `guard`. ```typescript import { Elysia, t } from 'elysia' new Elysia() .guard({ beforeHandle: [ ({ query: { name }, status }) => { if(!name) return status(401) }, ({ query: { name } }) => { console.log(name) } ], afterResponse({ responseValue }) { console.log(responseValue) }, query: t.Object({ name: t.String() }) }) .get( '/auth', { query: t.Object({ name: t.String() }) }, ({ query: { name = 'anon' } }) => `Hello ${name}!` ) .get( '/profile', { query: t.Object({ name: t.String() }) }, ({ query: { name = 'anon' } }) => `Hello ${name}!` ) .listen(3000) ``` This will apply hooks and schema to every route **after .guard** is called in the same instance. See [Guard](/essential/plugin/#guard) for more information. ## Assignment Let’s put 2 types of hooks into practice. --- Source: [tutorial/getting-started/handler-and-context/index.md](tutorial/getting-started/handler-and-context/index.md) # Handler and context **Handler** - a resource or a route function to send data back to client. ```ts import { Elysia } from 'elysia' new Elysia() // `() => 'hello world'` is a handler .get('/', () => 'hello world') .listen(3000) ``` A handler can also be a literal value, see [Handler](/essential/handler/) ```ts import { Elysia } from 'elysia' new Elysia() // `'hello world'` is a handler .get('/', 'hello world') .listen(3000) ``` Using an inline value can be useful for static resources like **files**. ## Context Contains information about each request. It is passed as the only argument of a handler. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', (context) => context.path) // ^ This is a context ``` **Context** stores information about the request like: - [body](/essential/validation/#body) - data sent by client to server like form data, JSON payload. - [query](/essential/validation/#query) - query string as an object. (Query is extracted from a value after pathname starting from ’?’ question mark sign) - [params](/essential/validation/#params) - Path parameters parsed as object - [headers](/essential/validation/#headers) - HTTP Header, additional information about the request like “Content-Type”. See [Context](/essential/handler/#context). ## Preview You can preview the result by looking under the **editor** section. There should be a tiny navigator on the **top left** of the preview window. You can use it to switch between path and method to see the response. You can also click to edit body, and headers. ## Assignment Let’s try extracting context parameters: --- Source: [tutorial/getting-started/life-cycle/index.md](tutorial/getting-started/life-cycle/index.md) # Lifecycle Lifecycle **hooks** are functions that are executed on a specific event during the request-response cycle. They allow you to run custom logic at a certain point - [request](/essential/life-cycle/#request) - when a request is received - [beforeHandle](/essential/life-cycle/#before-handle) - before executing a handler - [afterResponse](/essential/life-cycle/#after-response) - after a response is sent, etc. - [error](/essential/life-cycle/#error-error-handling) - when an error occurs This can be useful for tasks like logging, authentication, etc. To register a lifecycle hook, you can pass it in the second argument of a route method, before the handler: ```typescript import { Elysia } from 'elysia' new Elysia() .get('/1', () => 'Hello Elysia!') .get('/auth', { beforeHandle({ request, status }) { console.log('This is executed before handler') if(Math.random() <= 0.5) return status(418) } }, () => { console.log('This is executed after "beforeHandle"') return 'Oh you are lucky!' }) .get('/2', () => 'Hello Elysia!') ``` Here we use `status(418)` which is the “I’m a teapot” status code. You can also use the string name directly: `status("I'm a teapot")`. See [Status](/essential/handler/#status) for more on using status codes. When `beforeHandle` returns a value, it will skip the handler and return the value instead. This is useful for things like authentication, where you want to return a `401 Unauthorized` response if the user is not authenticated. See [Life cycle](/essential/life-cycle/), [Before handle](/essential/life-cycle/#before-handle) for a more detailed explanation. ## Hook A function that intercepts the **lifecycle event**. because a function **“hooks”** into the lifecycle event Hook can be categorized into 2 types: 1. [Local hook](/essential/life-cycle/#local-hook) - execute on a specific route 2. [Interceptor hook](/essential/life-cycle/#interceptor-hook) - execute on every route **after the hook is registered** ## Local hook A local hook is executed on a specific route. To use a local hook, you can inline hook into a route handler: ```typescript // Similar to previous code snippet import { Elysia } from 'elysia' new Elysia() .get('/1', () => 'Hello Elysia!') .get('/auth', { // This is a Local Hook beforeHandle({ request, status }) { console.log('Run before handler') if(Math.random() <= 0.5) return status(418) } }, () => { console.log('Run after "beforeHandle"') return 'Oh you are lucky!' }) .get('/2', () => 'Hello Elysia!') ``` ## Interceptor hook Register hook into every **handler that came after the hook is called** for the current instance only. To add an interceptor hook, call the lifecycle method, such as `.onBeforeHandle()`: ```typescript import { Elysia } from 'elysia' new Elysia() .get('/1', () => 'Hello Elysia!') .onBeforeHandle(({ request, status }) => { console.log('This is executed before handler') if(Math.random() <= 0.5) return status(418) }) // "beforeHandle" is applied .get('/auth', () => { console.log('This is executed after "beforeHandle"') return 'Oh you are lucky!' }) // "beforeHandle" is also applied .get('/2', () => 'Hello Elysia!') ``` Unlike Local Hook, Interceptor Hook will add the hook to every route that came after the hook is registered. ## Assignment Let’s put 2 types of hooks into practice. --- Source: [tutorial/getting-started/plugin/index.md](tutorial/getting-started/plugin/index.md) # Plugin Every Elysia instance can be plug-and-play with other instances using the `use` method. ```typescript import { Elysia } from 'elysia' const user = new Elysia() .get('/profile', 'User Profile') .get('/settings', 'User Settings') new Elysia() .use(user) .get('/', 'Home') .listen(3000) ``` Once applied, all routes from `user` instance will be available in `app` instance. ### Plugin config You can also create a plugin that takes an argument and returns an Elysia instance to make a more dynamic plugin. ```typescript import { Elysia } from 'elysia' const user = ({ log = false }) => new Elysia() .onBeforeHandle(({ request }) => { if (log) console.log(request) }) .get('/profile', 'User Profile') .get('/settings', 'User Settings') new Elysia() .use(user({ log: true })) .get('/', 'Home') .listen(3000) ``` It’s also recommended that you read about [Key Concept: Dependency](/key-concept/#dependency) to understand how Elysia handles dependencies between plugins. ## Assignment Let’s apply the `user` instance to the `app` instance. --- Source: [tutorial/getting-started/status-and-headers/index.md](tutorial/getting-started/status-and-headers/index.md) # Status A status code is an indicator of how the server handles the request. You must have heard of the infamous **404 Not Found** when you visit a non-existing page. That’s a **status code**. By default, Elysia will return **200 OK** for a successful request. Elysia also returns many other status codes depending on the situation like: - 400 Bad Request - 422 Unprocessable Entity - 500 Internal Server Error You can also return a status code by returning your response using the `status` function. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ status }) => status(418, "I'm a teapot")) .listen(3000) ``` The status code can be a number or a string status name. These are equivalent: ```typescript status(418, "I'm a teapot") status("I'm a teapot", "I'm a teapot") ``` String status names provide TypeScript autocompletion for all valid HTTP statuses. See [Status](/essential/handler/#status). ## Redirect Similarly, you can also redirect the request to another URL by returning the `redirect` function. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ redirect }) => redirect('https://next.elysiajs.com/')) .listen(3000) ``` See [Redirect](/essential/handler/#redirect). ## Headers Unlike status code and redirect, which you can return directly, you might need to set headers multiple times in your application. That’s why instead of returning a `headers` function, Elysia provides a `set.headers` object to set headers. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ set }) => { set.headers['x-powered-by'] = 'Elysia' return 'Hello World' }) .listen(3000) ``` Because `headers` represents **request headers**, Elysia distinguishes between request headers and response headers by prefixing **set.headers** for response. See [Headers](/essential/handler/#setheaders). ## Assignment Let’s exercise what we have learned. --- Source: [tutorial/getting-started/validation/index.md](tutorial/getting-started/validation/index.md) # Validation Elysia offers data validation out of the box. You can use `t` from `elysia` to define a schema. ```typescript import { Elysia, t } from 'elysia' new Elysia() .post( '/user', { body: t.Object({ name: t.String(), age: t.Number() }) }, ({ body: { name } }) => `Hello ${name}!` ) .listen(3000) ``` When you define a schema, Elysia will ensure the data is in a correct shape. If the data doesn’t match the schema, Elysia will return a **422 Unprocessable Entity** error. See [Validation](/essential/validation/). ### Bring your own Alternatively, Elysia support **Standard Schema**, allowing you to use a library of your choice like **zod**, **yup** or **valibot**. ```typescript import { Elysia } from 'elysia' import { z } from 'zod' new Elysia() .post( '/user', { body: z.object({ name: z.string(), age: z.number() }) }, ({ body: { name } }) => `Hello ${name}!` ) .listen(3000) ``` See [Standard schema](/essential/validation/#standard-schema) for all compatible schema. ## Validation type You can validate the following property: - `body` - `query` - `params` - `headers` - `cookie` - `response` Once a schema is defined, Elysia will infer types for you so you don’t have to define a separate schema in TypeScript. See [Schema type](/essential/validation/#schema-type) for each type. ## Response validation When you define a validation schema for `response`, Elysia will validate the response before sending it to the client, and type check the response for you. You can also specify which status code to validate: ```typescript import { Elysia, t } from 'elysia' new Elysia() .get( '/user', { response: { 200: t.Literal('Hello Elysia!'), 418: t.Object({ message: t.Literal("I'm a teapot") }) } }, () => `Hello Elysia!` ) .listen(3000) ``` See [Response validation](/essential/validation/#response). ## Assignment Let’s exercise what we have learned. --- Source: [tutorial/getting-started/your-first-route/index.md](tutorial/getting-started/your-first-route/index.md) # Your first route When we enter a website, it takes 1. **path** like `/`, `/about`, or `/contact` 2. **method** like `GET`, `POST`, or `DELETE` To determine which resource to show, simply called **“route”**. In Elysia, we can define a route by: 1. Call method named after HTTP method 2. Path being the first argument 3. Handler being the second argument ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello World!') .listen(3000) ``` ## Routing Path in Elysia can be grouped into 3 types: 1. static paths - static string to locate the resource 2. dynamic paths - segment can be any value 3. wildcards - path until a specific point can be anything See [Route](/essential/route/). ## Static path Static path is a hardcoded string to locate the resource on the server. ```ts import { Elysia } from 'elysia' new Elysia() .get('/hello', 'hello') .get('/hi', 'hi') .listen(3000) ``` See [Static path](/essential/route/#static-path). ## Dynamic path Dynamic paths match some part and capture the value to extract extra information. To define a dynamic path, we can use a colon `:` followed by a name. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/id/:id', ({ params: { id } }) => id) .listen(3000) ``` Here, a dynamic path is created with `/id/:id`, which tells Elysia to capture the value `:id` segment with value like **/id/1**, **/id/123**, **/id/anything**. See [Dynamic path](/essential/route/#dynamic-path). ### Optional path parameters We can make a path parameter optional by adding a question mark `?` after the parameter name. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/id/:id?', ({ params: { id } }) => `id ${id}`) .listen(3000) ``` See [Optional path parameters](/essential/route/#optional-params). ## Wildcards Dynamic paths allow capturing a single segment while wildcards allow capturing the rest of the path. To define a wildcard, we can use an asterisk `*`. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/id/*', ({ params }) => params['*']) .listen(3000) ``` See [Wildcards](/essential/route/#wildcard). ## Assignment Let’s recap, and create 3 paths with different types: --- Source: [tutorial/index.md](tutorial/index.md) # Welcome to Elysia It’s great to have you here! This playground will help you get started with Elysia interactively. Unlike traditional backend frameworks, **Elysia can run in a browser**! Although it doesn’t support all features, it’s a perfect environment for learning and experimentation. You can open the API docs at any time with the **Docs** tab at the top of this pane, or by clicking a link in the text. ## What is Elysia Elysia is an ergonomic framework for humans. Ok, seriously, Elysia is a backend TypeScript framework that focuses on developer experience and performance. What makes Elysia different from other frameworks is: 1. Spectacular performance comparable to Golang. 2. Extraordinary TypeScript support with **type soundness**. 3. Built around OpenAPI from the ground up. 4. Offers End-to-end Type Safety like tRPC. 5. Uses Web Standards, allowing you to run your code anywhere like Cloudflare Workers, Deno, Bun, Node.js and more. 6. It is, of course, **designed for humans** first. Although Elysia has some framework-specific concepts to learn, once users get the hang of it, many find it very enjoyable and intuitive to work with. ## How to use this playground Playground is divided into 3 sections: 1. Documentation and task on the left side (what you’re currently reading). 2. Code editor in the top right 3. Preview, output, and console in the bottom right ## Assignment For the first assignment, let’s modify the code to make the server respond with `"Hello Elysia!"` instead of `"Hello World!"`. Feel free to look around the code editor and preview section to get familiar with the environment. --- Source: [tutorial/patterns/cookie/index.md](tutorial/patterns/cookie/index.md) # Cookie You interact with cookies by using [cookie](/patterns/cookie/) from context. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.value = Number(visit.value ?? 0) + 1 return `You have visited ${visit.value} times` }) .listen(3000) ``` Cookie is a reactive object. Once modified, it will be reflected in response. ## Value Elysia will then try to coerce it into its respective value when a type annotation if provided. ```typescript import { Elysia, t } from 'elysia' new Elysia() .get('/', { cookie: t.Object({ visit: t.Optional( t.Object({ total: t.Number() }) ) }) }, ({ cookie: { visit } }) => { visit.value ??= { total: 0 } visit.value.total++ return `You have visited ${visit.value.total} times` }) .listen(3000) ``` We can use [cookie schema](/patterns/cookie/#cookie-schema) to validate and parse cookie. ## Attribute We can get/set cookie attribute by its respective property name. Otherwise, use `.set()` to bulk set attribute. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.value = Number(visit.value ?? 0) + 1 visit.httpOnly = true visit.path = '/' visit.set({ sameSite: 'lax', secure: true, maxAge: 60 * 60 * 24 * 7 }) return `You have visited ${visit.value} times` }) .listen(3000) ``` See [Cookie attribute](/patterns/cookie/#assign-property). ## Remove We can remove cookie by calling `.remove()` method. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.remove() return `Cookie removed` }) .listen(3000) ``` ## Cookie signature Elysia can sign cookies to prevent tampering by: 1. Provide a cookie secret to the Elysia constructor. 2. Use `t.Cookie` to provide secret for each cookie. ```typescript import { Elysia, t } from 'elysia' new Elysia({ cookie: { secrets: 'Fischl von Luftschloss Narfidort', } }) .get('/', { cookie: t.Cookie({ visit: t.Optional(t.Number()) }, { secrets: 'Fischl von Luftschloss Narfidort', sign: ['visit'] }) }, ({ cookie: { visit } }) => { visit.value ??= 0 visit.value++ return `You have visited ${visit.value} times` }) .listen(3000) ``` If multiple secrets are provided, Elysia will use the first secret to sign cookies, and try to verify with the rest. See [Cookie signature](/patterns/cookie/#cookie-signature), [Cookie rotation](/patterns/cookie/#cookie-rotation). ## Assignment Let’s create a simple counter that tracks how many times you have visited the site. --- Source: [tutorial/patterns/error-handling/index.md](tutorial/patterns/error-handling/index.md) # Error handling [error](/essential/life-cycle/#error-error-handling) is called when an **error is thrown**. It accepts context similar to a handler, with an `error` property containing the thrown error. Use `instanceof` to check its class. ```typescript import { Elysia, NotFound } from 'elysia' new Elysia() .onError(({ error, status }) => { if(error instanceof NotFound) return 'uhe~ are you lost?' return status(418, "My bad! But I'm cute so you'll forgive me, right?") }) .get('/', () => 'ok') .listen(3000) ``` You can return a [status](/essential/handler/#status) to override the default error status. ## Custom error Register an error class with `.onError(ErrorClass, handler)` to handle that type: ```typescript import { Elysia } from 'elysia' class NicheError extends Error { constructor(message: string) { super(message) } } new Elysia() .onError(NicheError, ({ error, status }) => { // Typed as NicheError console.log(error) return status(418, "We have no idea how you got here") }) .get('/', () => { throw new NicheError('Custom error message') }) .listen(3000) ``` The handler receives an error typed as `NicheError`, so its properties are available with autocomplete. ### Error status code You can also provide a custom status code by adding a **status** property to class: ```typescript import { Elysia } from 'elysia' class NicheError extends Error { status = 418 constructor(message: string) { super(message) } } ``` Elysia will use this status code if the error is thrown, see [Custom status code](/patterns/error-handling/#custom-status-code). ### Error response Return a response from the class handler to choose the body and status: ```typescript import { Elysia } from 'elysia' class NicheError extends Error { status = 418 } new Elysia() .onError(NicheError, ({ error, status }) => status(error.status, { message: error.message }) ) .get('/', () => { throw new NicheError('Custom error message') }) ``` See [Class error handlers](/patterns/error-handling/#third-party-errors). ## Assignment Let’s try to extend Elysia’s context. --- Source: [tutorial/patterns/extends-context/index.md](tutorial/patterns/extends-context/index.md) # Extends context Elysia provides a context with small utilities to help you get started. You can extend Elysia’s context with: 1. [Decorate](/essential/handler/#extend-context) 2. [State](/essential/handler/#extend-context) 3. [Derive](/essential/life-cycle/#derive) ## Decorate **Singleton**, and **immutable** properties that are shared across all requests. ```typescript import { Elysia } from 'elysia' class Logger { log(value: string) { console.log(value) } } new Elysia() .decorate('logger', new Logger()) .get('/', ({ logger }) => { logger.log('hi') return 'hi' }) ``` Decorated value it will be available in the context as a read-only property, see [Decorate](/essential/handler/#extend-context). ## State A **mutable** reference that is shared across all requests. ```typescript import { Elysia } from 'elysia' new Elysia() .state('count', 0) .get('/', ({ store }) => { store.count++ return store.count }) ``` State will be available in **context.store** that is shared across every request, see [State](/essential/handler/#extend-context). The first two requests return `1`, then `2`. Increment `store.count` to update the shared state. Destructuring a number with `({ store: { count } })` copies its current value into a local variable. Incrementing that variable does not change `store.count`. ## Derive [Decorate](/essential/handler/#extend-context) value is registered as a singleton. [Derive](/essential/life-cycle/#derive) adds context values **per request**, after validation. ```typescript import { Elysia } from 'elysia' new Elysia() .derive(({ headers: { authorization } }) => ({ authorization })) .get('/', ({ authorization }) => authorization) ``` Any **returned value will be available in context** except status, which will be send to client directly, and abort the subsequent handlers. `derive` runs before the handler, after input has been validated and converted. Return an object to add values to context, or return `status()` to end the request early. ## Scope [State](/essential/handler/#extend-context), and [Decorate](/essential/handler/#extend-context) are shared across all requests, and instances. [Derive](/essential/life-cycle/#derive) values are per request, and have an encapsulation scope (as they’re based on life-cycle event). If you want to use a derived value from a plugin, you would have to declare a [Scope](/essential/plugin/#scope). ```typescript import { Elysia } from 'elysia' const plugin = new Elysia() .derive( 'plugin', ({ headers: { authorization } }) => ({ authorization }) ) new Elysia() .use(plugin) .get('/', ({ authorization }) => authorization) .listen(3000) ``` ## Assignment Let’s try to extend Elysia’s context. --- Source: [tutorial/patterns/macro/index.md](tutorial/patterns/macro/index.md) # Macro Reusable route options. Imagine we have an authentication check like this: ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/user', { cookie: t.Object({ session: t.String() }), beforeHandle({ cookie: { session } }) { if(!session.value) throw 'Unauthorized' } }, ({ body }) => body) .listen(3000) ``` If we have multiple routes that require authentication, we have to repeat the same options over and over again. Instead, we can use a macro to reuse route options: ```typescript import { Elysia, t } from 'elysia' new Elysia() .macro({ auth: { cookie: t.Object({ session: t.String() }), // psuedo auth check beforeHandle({ cookie: { session }, status }) { if(!session.value) return status(401) } } }) .post('/user', { auth: true }, ({ body }) => body) .listen(3000) ``` **auth** will then inline both **cookie**, and **beforeHandle** to the route. Simply put, [Macro](/patterns/macro/) **is a reusable route option**, similar to function but as a route option with **type soundness**. ## Assignment Let’s define a macro to check if a body is a Fibonacci number: ```typescript function isFibonacci(n: number) { let a = 0, b = 1 while(b < n) [a, b] = [b, a + b] return b === n || n === 0 } ``` --- Source: [tutorial/patterns/standalone-schema/index.md](tutorial/patterns/standalone-schema/index.md) # Merge schemas When we define a schema using [Guard](/essential/validation/#guard), the schema will be added to a route. But it will be **overridden** if the route provides a schema: ```typescript import { Elysia, t } from 'elysia' new Elysia() .guard({ body: t.Object({ age: t.Number() }) }) .post( '/user', { // This will override the guard schema body: t.Object({ name: t.String() }) }, ({ body }) => body ) .listen(3000) ``` If we want a schema to **co-exist** with route schema, we can define it as **merged guard schema**: ```typescript import { Elysia, t } from 'elysia' new Elysia() .guard({ schema: 'merge', body: t.Object({ age: t.Number() }) }) .post( '/user', // body will have both age and name property { body: t.Object({ name: t.String() }) }, ({ body }) => body ) .listen(3000) ``` ## Schema library interoperability Schemas between merged guard schemas can be from different validation libraries. For example you can define a merged guard schema using **zod**, and a local schema using **t** from **elysia**, and both will works interchangeably. ## Assignment Let’s make both `age` and `name` properties required in the request body by using merged guard schema. --- Source: [tutorial/patterns/validation-error/index.md](tutorial/patterns/validation-error/index.md) # Validation error If you use `t` from `elysia` for validation, you can provide a custom error message based on the field that fails the validation. ```typescript import { Elysia, t } from 'elysia' new Elysia() .post( '/', { body: t.Object({ age: t.Number({ error: 'Age must be a number' }) }, { error: 'Body must be an object' }) }, ({ body }) => body ) .listen(3000) ``` Elysia will override the default error message with the custom one you provide, see [Custom validation message](/patterns/error-handling/#custom-validation-message). ## Validation detail By default Elysia also provides a [Validation detail](/patterns/error-handling/#validation-detail) to explain what’s wrong with the validation as follows: ```json { "type": "validation", "code": "validation", "title": "Validation Error", "status": 422, "detail": "must be number", "on": "params", "property": "/id", "found": { "id": "string" }, "expected": { "id": 0 }, "errors": [ { "keyword": "type", "schemaPath": "#/properties/id", "instancePath": "/id", "params": { "type": "number" }, "message": "must be number" } ] } ``` However, when you provide a custom error message, it will completely override [Validation detail](/patterns/error-handling/#validation-detail) To bring back the validation detail, you can wrap your custom error message in a [Validation detail](/patterns/error-handling/#validation-detail) function. ```typescript import { Elysia, t, validationDetail } from 'elysia' new Elysia() .post( '/', { body: t.Object({ age: t.Number({ error: validationDetail('Age must be a number') }) }, { error: validationDetail('Body must be an object') }) }, ({ body }) => body ) .listen(3000) ``` ## Assignment Provide your own validation message when the request body contains an invalid age. --- Source: [tutorial/whats-next/index.md](tutorial/whats-next/index.md) # Congratulations! You have completed the tutorial. Now you’re ready to build your own application with Elysia! ## First up We highly recommend that you check out these 2 pages first before getting started with Elysia: ### [Key concept](/key-concept/) [Core concepts of Elysia and how to use them effectively](/key-concept/) ### [Best practice](/essential/best-practice/) [Understand best practices for writing Elysia code](/essential/best-practice/) ### llms.txt Alternatively, you can download [llms.txt](/llms.txt) or [llms-full.txt](/llms-full.txt) and feed it to your favorite LLMs like ChatGPT, Claude or Gemini to get a more interactive experience. ### [llms.txt](/llms.txt) [Download summarized Elysia documentation in Markdown format with reference for prompting LLMs](/llms.txt) ### [llms-full.txt](/llms-full.txt) [Download full Elysia documentation in Markdown format in a single file for prompting LLMs](/llms-full.txt) ### If you are stuck Feel free to ask our community on GitHub Discussions, Discord, and Twitter. ### [Discord](https://discord.gg/eaFJ2KDJck) [Official ElysiaJS discord community server](https://discord.gg/eaFJ2KDJck) ### [Twitter](https://twitter.com/elysiajs) [Track updates and status of Elysia](https://twitter.com/elysiajs) ### [GitHub](https://github.com/elysiajs) [Source code and development](https://github.com/elysiajs) ## From another framework? If you have used other popular frameworks like Express, Fastify, or Hono, you will find Elysia right at home with just a few differences. ### [From Express](/migrate/from-express/) [Comparison between Express and Elysia](/migrate/from-express/) ### [From Fastify](/migrate/from-fastify/) [Comparison between Fastify and Elysia](/migrate/from-fastify/) ### [From Hono](/migrate/from-hono/) [Comparison between Hono and Elysia](/migrate/from-hono/) ### [From tRPC](/migrate/from-trpc/) [Comparison between tRPC and Elysia](/migrate/from-trpc/) ## Essential chapter Here are the foundations of Elysia, we highly recommend that you go through these pages before jumping to other topics: ### [Route](/essential/route/) [Understand how routing works in Elysia](/essential/route/) ### [Handler](/essential/handler/) [Learn about how to handle request](/essential/handler/) ### [Validation](/essential/validation/) [How to enforce type safety with Elysia](/essential/validation/) ### [Lifecycle](/essential/life-cycle/) [Learn different type of lifecycle](/essential/life-cycle/) ### [Plugin](/essential/plugin/) [Learn how to extend Elysia with Plugin](/essential/plugin/) ## More patterns If you feel like exploring more Elysia features, check out: ### [Handler](/essential/handler/) [More patterns on how to send files, Server Sent Events, etc.](/essential/handler/) ### [WebSocket](/patterns/websocket/) [See how to create Real-Time applications with Elysia](/patterns/websocket/) ### [Eden](/eden/) [Learn more about Eden, and how to use it effectively](/eden/) ### [OpenTelemetry](/patterns/opentelemetry/) [Learn how to monitor your application with Open Telemetry](/patterns/opentelemetry/) ### [Deploy to production](/patterns/deploy/) [Learn how to deploy Elysia to production](/patterns/deploy/) ## Integration with meta framework We can also use Elysia with Meta Framework like Nextjs, Nuxt, Astro, etc. ### [Astro](/integrations/astro/) [Elysia in Astro Endpoint](/integrations/astro/) ### [Expo](/integrations/expo/) [Elysia in Expo API Route](/integrations/expo/) ### [Next.js](/integrations/nextjs/) [Elysia in Route Handler](/integrations/nextjs/) ### [Nuxt](/integrations/nuxt/) [Elysia in Nuxt Server Route](/integrations/nuxt/) ### [SvelteKit](/integrations/sveltekit/) [Elysia in SvelteKit Endpoint](/integrations/sveltekit/) ## Integration with your favorite tool We have some integration with popular tools: ### [AI SDK](/integrations/ai-sdk/) [Learn how to use Vercel AI SDK with Elysia](/integrations/ai-sdk/) ### [Better Auth](/integrations/better-auth/) [Learn how to use Better Auth with Elysia](/integrations/better-auth/) ### [Drizzle](/integrations/drizzle/) [Elysia provides a type safe utility with Drizzle](/integrations/drizzle/) ### [Prisma](/integrations/prisma/) [Learn how to use Prisma with Elysia](/integrations/prisma/) ### [React Email](/integrations/react-email/) [We can use JSX to create email template](/integrations/react-email/) *** We hope you will love Elysia as much as we do!