Skip to content

Instantly share code, notes, and snippets.

@caoer
Created December 25, 2024 18:55
Show Gist options
  • Save caoer/501e4a7b25c595b38c252305ec7d783b to your computer and use it in GitHub Desktop.
Save caoer/501e4a7b25c595b38c252305ec7d783b to your computer and use it in GitHub Desktop.

├── README.md └── docs ├── at-glance.md ├── blog.md ├── blog ├── elysia-02.md ├── elysia-03.md ├── elysia-04.md ├── elysia-05.md ├── elysia-06.md ├── elysia-07.md ├── elysia-08.md ├── elysia-10.md ├── elysia-11.md ├── elysia-12.md ├── elysia-supabase.md ├── integrate-trpc-with-elysia.md └── with-prisma.md ├── eden ├── fetch.md ├── installation.md ├── overview.md ├── test.md └── treaty │ ├── config.md │ ├── legacy.md │ ├── overview.md │ ├── parameters.md │ ├── response.md │ ├── unit-test.md │ └── websocket.md ├── essential ├── best-practice.md ├── handler.md ├── life-cycle.md ├── plugin.md ├── route.md ├── structure.md └── validation.md ├── index.md ├── integrations ├── astro.md ├── cheat-sheet.md ├── expo.md ├── nextjs.md └── sveltekit.md ├── key-concept.md ├── midori.md ├── patterns ├── configuration.md ├── cookie.md ├── deployment.md ├── macro.md ├── mount.md ├── trace.md ├── unit-test.md └── websocket.md ├── plugins ├── bearer.md ├── cors.md ├── cron.md ├── graphql-apollo.md ├── graphql-yoga.md ├── html.md ├── jwt.md ├── opentelemetry.md ├── overview.md ├── server-timing.md ├── static.md ├── stream.md ├── swagger.md └── trpc.md ├── quick-start.md ├── recipe ├── better-auth.md ├── drizzle.md ├── openapi.md ├── opentelemetry.md └── react-email.md ├── table-of-content.md └── tutorial.md

/README.md:

1 | ## Elysia documentation 2 | Written by VitePress 3 |


/docs/at-glance.md:

1 | --- 2 | title: At glance - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: At glance - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Designed with ergonomic design, extensive support for TypeScript, modern JavaScript API, optimized for Bun. Offers a unique experience unified type, and end-to-end type safety while maintaining excellent performance. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Designed with ergonomic design, extensive support for TypeScript, modern JavaScript API, optimized for Bun. Offers a unique experience unified type, and end-to-end type safety while maintaining excellent performance. 15 | --- 16 | 17 | <script setup> 18 | import Card from '../components/nearl/card.vue' 19 | import Deck from '../components/nearl/card-deck.vue' 20 | import Playground from '../components/nearl/playground.vue' 21 | 22 | import { Elysia } from 'elysia' 23 | 24 | const demo1 = new Elysia() 25 | .get('/', 'Hello Elysia') 26 | .get('/user/:id', ({ params: { id }}) => id) 27 | .post('/form', ({ body }) => body) 28 | 29 | const demo2 = new Elysia() 30 | .get('/user/:id', ({ params: { id }}) => id) 31 | .get('/user/abc', () => 'abc') 32 | </script> 33 | 34 | # At glance 35 | Elysia is an ergonomic web framework for building backend servers with Bun. 36 | 37 | Designed with simplicity and type-safety in mind, Elysia has a familiar API with extensive support for TypeScript, optimized for Bun. 38 | 39 | Here's a simple hello world in Elysia. 40 | 41 | typescript twoslash 42 | import { Elysia } from 'elysia' 43 | 44 | new Elysia() 45 | .get('/', 'Hello Elysia') 46 | .get('/user/:id', ({ params: { id }}) => id) 47 | .post('/form', ({ body }) => body) 48 | .listen(3000) 49 | 50 | 51 | Navigate to localhost:3000 and it should show 'Hello Elysia' as a result. 52 | 53 | <Playground 54 | :elysia="demo1" 55 | :alias="{ 56 | '/user/:id': '/user/1' 57 | }" 58 | :mock="{ 59 | '/user/:id': { 60 | GET: '1' 61 | }, 62 | '/form': { 63 | POST: JSON.stringify({ 64 | hello: 'Elysia' 65 | }) 66 | } 67 | }" 68 | /> 69 | 70 | ::: tip 71 | Hover over the code snippet to see the type definition. 72 | 73 | In the mock browser, click on the path highlighted in blue to change paths and preview the response. 74 | 75 | Elysia can run in the browser, and the results you see are actually run using Elysia. 76 | ::: 77 | 78 | ## Performance 79 | 80 | Building on Bun and extensive optimization like Static Code Analysis allows Elysia to generate optimized code on the fly. 81 | 82 | Elysia can outperform most of the web frameworks available today[1], and even match the performance of Golang and Rust frameworks[2]. 83 | 84 | | Framework | Runtime | Average | Plain Text | Dynamic Parameters | JSON Body | 85 | | ------------- | ------- | ----------- | ---------- | ------------------ | ---------- | 86 | | bun | bun | 262,660.433 | 326,375.76 | 237,083.18 | 224,522.36 | 87 | | elysia | bun | 255,574.717 | 313,073.64 | 241,891.57 | 211,758.94 | 88 | | hyper-express | node | 234,395.837 | 311,775.43 | 249,675 | 141,737.08 | 89 | | hono | bun | 203,937.883 | 239,229.82 | 201,663.43 | 170,920.4 | 90 | | h3 | node | 96,515.027 | 114,971.87 | 87,935.94 | 86,637.27 | 91 | | oak | deno | 46,569.853 | 55,174.24 | 48,260.36 | 36,274.96 | 92 | | fastify | bun | 65,897.043 | 92,856.71 | 81,604.66 | 23,229.76 | 93 | | fastify | node | 60,322.413 | 71,150.57 | 62,060.26 | 47,756.41 | 94 | | koa | node | 39,594.14 | 46,219.64 | 40,961.72 | 31,601.06 | 95 | | express | bun | 29,715.537 | 39,455.46 | 34,700.85 | 14,990.3 | 96 | | express | node | 15,913.153 | 17,736.92 | 17,128.7 | 12,873.84 | 97 | 98 | ## TypeScript 99 | 100 | Elysia is designed to help you write less TypeScript. 101 | 102 | Elysia's Type System is fine-tuned to infer your code into types automatically, without needing to write explicit TypeScript, while providing type-safety at both runtime and compile time to provide you with the most ergonomic developer experience. 103 | 104 | Take a look at this example: 105 | 106 | typescript twoslash 107 | import { Elysia } from 'elysia' 108 | 109 | new Elysia() 110 | .get('/user/:id', ({ params: { id } }) => id) 111 | // ^? 112 | .listen(3000) 113 | 114 | 115 |
116 | 117 | The above code creates a path parameter "id". The value that replaces :id will be passed to params.id both at runtime and in types without manual type declaration. 118 | 119 | <Playground 120 | :elysia="demo2" 121 | :alias="{ 122 | '/user/:id': '/user/123' 123 | }" 124 | :mock="{ 125 | '/user/:id': { 126 | GET: '123' 127 | }, 128 | }" 129 | /> 130 | 131 | Elysia's goal is to help you write less TypeScript and focus more on business logic. Let the complex types be handled by the framework. 132 | 133 | TypeScript is not needed to use Elysia, but it's recommended to use Elysia with TypeScript. 134 | 135 | ## Type Integrity 136 | 137 | To take a step further, Elysia provides Elysia.t, a schema builder to validate types and values at both runtime and compile-time to create a single source of truth for your data-type. 138 | 139 | Let's modify the previous code to accept only a numeric value instead of a string. 140 | 141 | typescript twoslash 142 | import { Elysia, t } from 'elysia' 143 | 144 | new Elysia() 145 | .get('/user/:id', ({ params: { id } }) => id, { 146 | // ^? 147 | params: t.Object({ 148 | id: t.Numeric() 149 | }) 150 | }) 151 | .listen(3000) 152 | 153 | 154 | This code ensures that our path parameter id will always be a numeric string and then transforms it into a number automatically at both runtime and compile-time (type-level). 155 | 156 | ::: tip 157 | Hover over "id" in the above code snippet to see a type definition. 158 | ::: 159 | 160 | With Elysia's schema builder, we can ensure type safety like a strongly-typed language with a single source of truth. 161 | 162 | ## Standard 163 | 164 | Elysia adopts many standards by default, like OpenAPI, and WinterCG compliance, allowing you to integrate with most of the industry standard tools or at least easily integrate with tools you are familiar with. 165 | 166 | For instance, because Elysia adopts OpenAPI by default, generating documentation with Swagger is as easy as adding a one-liner: 167 | 168 | typescript twoslash 169 | import { Elysia, t } from 'elysia' 170 | import { swagger } from '@elysiajs/swagger' 171 | 172 | new Elysia() 173 | .use(swagger()) 174 | .get('/user/:id', ({ params: { id } }) => id, { 175 | params: t.Object({ 176 | id: t.Number() 177 | }) 178 | }) 179 | .listen(3000) 180 | 181 | 182 | With the Swagger plugin, you can seamlessly generate a Swagger page without additional code or specific config and share it with your team effortlessly. 183 | 184 | ## End-to-end Type Safety 185 | 186 | With Elysia, type safety is not limited to server-side only. 187 | 188 | With Elysia, you can synchronize your types with your frontend team automatically like tRPC, with Elysia's client library, "Eden". 189 | 190 | typescript twoslash 191 | import { Elysia, t } from 'elysia' 192 | import { swagger } from '@elysiajs/swagger' 193 | 194 | const app = new Elysia() 195 | .use(swagger()) 196 | .get('/user/:id', ({ params: { id } }) => id, { 197 | params: t.Object({ 198 | id: t.Number() 199 | }) 200 | }) 201 | .listen(3000) 202 | 203 | export type App = typeof app 204 | 205 | 206 | And on your client-side: 207 | 208 | typescript twoslash 209 | // @filename: server.ts 210 | import { Elysia, t } from 'elysia' 211 | 212 | const app = new Elysia() 213 | .get('/user/:id', ({ params: { id } }) => id, { 214 | params: t.Object({ 215 | id: t.Number() 216 | }) 217 | }) 218 | .listen(3000) 219 | 220 | export type App = typeof app 221 | 222 | // @filename: client.ts 223 | // ---cut--- 224 | // client.ts 225 | import { treaty } from '@elysiajs/eden' 226 | import type { App } from './server' 227 | 228 | const app = treaty<App>('localhost:3000') 229 | 230 | // Get data from /user/617 231 | const { data } = await app.user({ id: 617 }).get() 232 | // ^? 233 | 234 | console.log(data) 235 | 236 | 237 | With Eden, you can use the existing Elysia types to query an Elysia server without code generation and synchronize types for both frontend and backend automatically. 238 | 239 | Elysia is not only about helping you create a confident backend but for all that is beautiful in this world. 240 | 241 | ## Platform Agnostic 242 | 243 | Elysia was designed for Bun, but is not limited to Bun. Being WinterCG compliant allows you to deploy Elysia servers on Cloudflare Workers, Vercel Edge Functions, and most other runtimes that support Web Standard Requests. 244 | 245 | ## Our Community 246 | 247 | If you have questions or get stuck regarding Elysia, feel free to ask our community on GitHub Discussions, Discord, and Twitter. 248 | 249 | 250 | 251 | Official ElysiaJS discord community server 252 | 253 | 254 | Track update and status of Elysia 255 | 256 | 257 | Source code and development 258 | 259 | 260 | 261 | --- 262 | 263 | 1. Measured in requests/second. The benchmark for parsing query, path parameter and set response header on Debian 11, Intel i7-13700K tested on Bun 0.7.2 on 6 Aug 2023. See the benchmark condition here. 264 | 265 | 2. Based on TechEmpower Benchmark round 22. 266 |


/docs/blog.md:

1 | --- 2 | title: Elysia Blog 3 | layout: page 4 | sidebar: false 5 | editLink: false 6 | search: false 7 | head: 8 | - - meta 9 | - property: 'og:title' 10 | content: Blog - ElysiaJS 11 | 12 | - - meta 13 | - name: 'description' 14 | content: Update of ElysiaJS, from core maintainers 15 | 16 | - - meta 17 | - property: 'og:description' 18 | content: Update of ElysiaJS from core maintainers 19 | --- 20 | 21 | <script setup> 22 | import Blogs from '../components/blog/Landing.vue' 23 | </script> 24 | 25 | <Blogs 26 | :blogs="[ 27 | { 28 | title: 'Elysia 1.2 - You and Me', 29 | href: '/blog/elysia-12', 30 | detail: 'Introducing Adapter for universal runtime suppport, Object macro with resolve, Parser with custom name, WebSocket with lifecycle, TypeBox 0.34 with recursive type, and Eden validation inference.' 31 | }, 32 | { 33 | title: 'Elysia 1.1 - Grown-up's Paradise', 34 | href: '/blog/elysia-11', 35 | detail: 'Introducing OpenTelemetry, and Trace v2. Data coercion and normalization. Guard plugin and bulk cast. Optional path parameter. Decorator and Response status reconcilation. Generator response stream.' 36 | }, 37 | { 38 | title: 'Elysia 1.0 - Lament of the Fallen', 39 | href: '/blog/elysia-10', 40 | detail: 'Introducing Sucrose, a better static code analysis engine, improved starts up time up to 14x, remove 40 routes/instance limitation, faster type inference up to ~3.8x, Eden Treaty 2, Hook type (breaking change), and inline error for strict type check.' 41 | }, 42 | { 43 | title: 'Introducing Elysia 0.8 - Gate of Steiner', 44 | href: '/blog/elysia-08', 45 | detail: 'Introducing Macro API, a new way to interact with Elysia. New Lifecycle, resolve, and mapResponse to interact with Elysia even more. Static Content to compile static resource ahead of time. Default Property, Default Header and several improvement.' 46 | }, 47 | { 48 | title: 'Introducing Elysia 0.7 - Stellar Stellar', 49 | href: '/blog/elysia-07', 50 | detail: 'Introducing up to 13x faster type inference. Declarative telemetry with trace. Reactive cookie model, and cookie validation. TypeBox 0.31 and custom decoder support. Rewritten Web Socket. Definitions remapping and custom affix. Leading more solid foundation for Elysia for a brighter future.' 51 | }, 52 | { 53 | title: 'Introducing Elysia 0.6 - This Game', 54 | href: '/blog/elysia-06', 55 | detail: 'Introducing re-imagined plugin model, dynamic mode, better developer experience with declarative custom error, customizable loose and strict path mapping, TypeBox 0.30 and WinterCG framework interlop. Pushing the boundary of what is possible once again' 56 | }, 57 | { 58 | title: 'Accelerate your next Prisma server with Elysia', 59 | href: '/blog/with-prisma', 60 | detail: 'With the support of Prisma with Bun and Elysia, we are entering a new era of a new level of developer experience. For Prisma we can accelerate our interaction with database, Elysia accelerate our creation of backend web server in term of both developer experience and performance.' 61 | }, 62 | { 63 | title: 'Introducing Elysia 0.5 - Radiant', 64 | href: '/blog/elysia-05', 65 | detail: 'Introducing Static Code Analysis, New router Memoirist, TypeBox 0.28, Numeric type, inline schema, state/decorate/model/group rework, and type stability.' 66 | }, 67 | { 68 | title: 'Introducing Elysia 0.4 - 月夜の音楽会 (Moonlit Night Concert)', 69 | href: '/blog/elysia-04', 70 | detail: 'Introducing Ahead of Time Compilation, TypeBox 0.26, Response validation per status, and Separation of Elysia Fn.' 71 | }, 72 | { 73 | title: 'Elysia with Supabase. Your next backend at sonic speed', 74 | href: '/blog/elysia-supabase', 75 | detail: 'Elysia, and Supabase are a great match for rapidly developing prototype in less than a hour, let's take a look of how we can take advantage of both.' 76 | }, 77 | { 78 | title: 'Introducing Elysia 0.3 - 大地の閾を探して [Looking for Edge of Ground]', 79 | href: '/blog/elysia-03', 80 | detail: 'Introducing Elysia Fn, Type Rework for highly scalable TypeScript performance, File Upload support and validation, Reworked Eden Treaty.' 81 | }, 82 | { 83 | title: 'Integrate existing tRPC server to Bun with Elysia', 84 | href: '/blog/integrate-trpc-with-elysia', 85 | detail: 'Learn how to integrate existing tRPC to Elysia and Bun with Elysia tRPC plugin and more about Eden end-to-end type-safety for Elysia.' 86 | }, 87 | { 88 | title: 'Introducing Elysia 0.2 - The Blessing', 89 | href: '/blog/elysia-02', 90 | detail: 'Introducing Elysia 0.2, bringing more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate.' 91 | } 92 | ]" 93 | /> 94 |


/docs/blog/elysia-02.md:

1 | --- 2 | title: Elysia 0.2 - The Blessing 3 | sidebar: false 4 | editLink: false 5 | search: false 6 | head: 7 | - - meta 8 | - property: 'og:title' 9 | content: Introducing Elysia 0.2 - The Blessing 10 | 11 | - - meta 12 | - name: 'description' 13 | content: Introducing Elysia 0.2, bringing more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate. 14 | 15 | - - meta 16 | - property: 'og:description' 17 | content: Introducing Elysia 0.2, bringing more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate. 18 | 19 | - - meta 20 | - property: 'og:image' 21 | content: https://elysiajs.com/blog/elysia-02/blessing.webp 22 | 23 | - - meta 24 | - property: 'twitter:image' 25 | content: https://elysiajs.com/blog/elysia-02/blessing.webp 26 | --- 27 | 28 | <script setup> 29 | import Blog from '../../components/blog/Layout.vue' 30 | </script> 31 | 32 | <Blog 33 | title="Elysia 0.2 - The Blessing" 34 | src="/blog/elysia-02/blessing.webp" 35 | alt="blue to purple aurora in the night sky above of snow mountain" 36 | author="saltyaom" 37 | date="29 Jan 2023" 38 | > 39 | 40 | 「Blessing」brings more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate. 41 | 42 | Named after YOASOBI's song「祝福」, an opening for Witch from "Mobile Suit Gundam: The Witch from Mercury". 43 | 44 | ## Defers / Lazy Loading Module 45 | With Elysia 0.2 now add support for the lazy loading module and async plugin. 46 | 47 | This made it possible to defer plugin registration and incrementally apply after the Elysia server is started to achieve the fastest possible start-up time in Serverless/Edge environments. 48 | 49 | To create defers module, simply mark the plugin as async: 50 | typescript 51 | const plugin = async (app: Elysia) => { 52 | const stuff = await doSomeHeavyWork() 53 | 54 | return app.get('/heavy', stuff) 55 | } 56 | 57 | app.use(plugin) 58 | 59 | 60 | ### Lazy Loading 61 | Some modules might be heavy and importing before starting the server might not be a good idea. 62 | 63 | We can tell Elysia to skip the module then register the module later, and register the module when finish loading by using import statement in use: 64 | typescript 65 | app.use(import('./some-heavy-module')) 66 | 67 | 68 | This will register the module after the import is finished making the module lazy-load. 69 | 70 | Defers Plugin and lazy loading module will have all type-inference available right out of the box. 71 | 72 | 73 | ## Reference Model 74 | Now Elysia can memorize schema and reference the schema directly in Schema fields, without creating an import file via Elysia.setModel 75 | 76 | This list of schema available, brings auto-completion, complete type-inference, and validation as you expected from inline schema. 77 | 78 | To use a reference model, first, register the model with setModel, then write a model name to reference a model in schema 79 | typescript 80 | const app = new Elysia() 81 | .setModel({ 82 | sign: t.Object({ 83 | username: t.String(), 84 | password: t.String() 85 | }) 86 | }) 87 | .post('/sign', ({ body }) => body, { 88 | schema: { 89 | body: 'sign', 90 | response: 'sign' 91 | } 92 | }) 93 | 94 | 95 | This will bring auto-completion of known models. 96 | Screenshot 2566-01-23 at 13 24 28 97 | 98 | And type reference stopping you from accidentally returning invalid type. 99 | Screenshot 2566-01-23 at 13 26 00 100 | 101 | Using @elysiajs/swagger will also create a separate Model section for listing available models. 102 | Screenshot 2566-01-23 at 13 23 41 103 | 104 | Reference also handles validation as you expected. 105 | 106 | In short, it's as same as inline schema but now you only need to type the name of the schema to handle validation and typing instead of a long list of imports. 107 | 108 | ## OpenAPI Detail field 109 | Introducing new field schema.detail for customizing detail for the route following the standard of OpenAPI Schema V2 with auto-completion. 110 | 111 | Screenshot 2566-01-23 at 13 54 11 112 | 113 | This allows you to write better documentation and fully editable Swagger as you want: 114 | Screenshot 2566-01-23 at 13 23 41 115 | 116 | ## Union Type 117 | The previous version of Elysia sometime has a problem with distinct Union types, as Elysia tries to catch the response to create a full type reference for Eden. 118 | 119 | Results in invalidation of possible types, 120 | 121 | ## Union Response 122 | Made possible by Union Type, now returning multiple response status for schema now available using schema.response[statusCode] 123 | 124 | typescript 125 | app 126 | .post( 127 | '/json/:id', 128 | ({ body, params: { id } }) => ({ 129 | ...body, 130 | id 131 | }), 132 | { 133 | schema: { 134 | body: 'sign', 135 | response: { 136 | 200: t.Object({ 137 | username: t.String(), 138 | password: t.String(), 139 | id: t.String() 140 | }), 141 | 400: t.Object({ 142 | error: t.String() 143 | }) 144 | } 145 | } 146 | } 147 | ) 148 | 149 | 150 | Elysia will try to validate all schema in response allowing one of the types to be returned. 151 | 152 | Return types are also supported report in Swagger's response. 153 | 154 | ## Faster Type Inference 155 | As Elysia 0.1 explore the possibility of using type inference for improving better Developer Experience, we found that sometimes it takes a long time to update type inference because of heavy type inference and in-efficient custom generic. 156 | 157 | With Elysia 0.2 now optimized for faster type-inference, preventing duplication of heavy type unwrap, results in better performance for updating type and inference. 158 | 159 | ## Ecosystem 160 | With Elysia 0.2 enabling async plugin and deferred module many new plugins that isn't possible before became reality. 161 | 162 | Like: 163 | - Elysia Static plugin with the non-blocking capability 164 | - Eden with union-type inference for multiple responses 165 | - New Elysia Apollo Plugin for Elysia 166 | 167 | ### Notable Improvement: 168 | - onRequest and onParse now can access PreContext 169 | - Support application/x-www-form-urlencoded by default 170 | - body parser now parse content-type with extra attribute eg. application/json;charset=utf-8 171 | - Decode URI parameter path parameter 172 | - Eden now reports an error if Elysia is not installed 173 | - Skip declaration of existing model and decorators 174 | 175 | ### Breaking Changes: 176 | - onParse now accepts (context: PreContext, contentType: string) instead of (request: Request, contentType: string) 177 | - To migrate, add .request to context to access Request 178 | 179 | ### Afterward 180 | Thank you for supporting Elysia and being interested in this project. 181 | 182 | This release brings better DX and hopefully all you need to write great software with Bun. 183 | 184 | Now we have Discord server where you can ask any questions about Elysia or just hang out and chill around is also welcome. 185 | 186 | With the wonderful tools, we are happy to see what wonderful software you will build. 187 | 188 | > Not to be part of those images someone paints 189 | > 190 | > Not advancing in that show chosen by someone else 191 | > 192 | > You and I, alive to write our story 193 | > 194 | > Will never let you be lone and be gone from your side 195 | > 196 |


/docs/blog/elysia-03.md:

1 | --- 2 | title: Elysia 0.3 - 大地の閾を探して [Looking for Edge of Ground] 3 | sidebar: false 4 | editLink: false 5 | search: false 6 | head: 7 | - - meta 8 | - property: 'og:title' 9 | content: Introducing Elysia 0.3 - 大地の閾を探して [Looking for Edge of Ground] 10 | 11 | - - meta 12 | - name: 'description' 13 | content: Introducing Elysia Fn, Type Rework for highly scalable TypeScript performance, File Upload support and validation, Reworked Eden Treaty. 14 | 15 | - - meta 16 | - property: 'og:description' 17 | content: Introducing Elysia Fn, Type Rework for highly scalable TypeScript performance, File Upload support and validation, Reworked Eden Treaty. 18 | 19 | - - meta 20 | - property: 'og:image' 21 | content: https://elysiajs.com/blog/elysia-03/edge-of-ground.webp 22 | 23 | - - meta 24 | - property: 'twitter:image' 25 | content: https://elysiajs.com/blog/elysia-03/edge-of-ground.webp 26 | --- 27 | 28 | <script setup> 29 | import Blog from '../../components/blog/Layout.vue' 30 | </script> 31 | 32 | <Blog 33 | title="Elysia 0.3 - 大地の閾を探して [Looking for Edge of Ground]" 34 | src="/blog/elysia-03/edge-of-ground.webp" 35 | alt="shattered glass pieces floating in the abyss" 36 | author="saltyaom" 37 | date="17 Mar 2023" 38 | > 39 | 40 | Named after Camellia's song「大地の閾を探して [Looking for Edge of Ground]」ft. Hatsune Miku, is the last track of my most favorite's Camellia album,「U.U.F.O」. This song has a high impact on me personally, so I'm not taking the name lightly. 41 | 42 | This is the most challenging update, bringing the biggest release of Elysia yet, with rethinking and redesigning of Elysia architecture to be highly scalable while making less breaking change as possible. 43 | 44 | I'm pleased to announce the release candidate of Elysia 0.3 with exciting new features coming right up. 45 | 46 | ## Elysia Fn 47 | Introducing Elysia Fn, run any backend function on the frontend with full auto-completion and full type support. 48 | 49 | 52 | 53 | For rapid development, Elysia Fn allows you to "expose" backend code to call from the frontend with full type-safety, autocompletion, original code comment, and "click-to-definition", allowing you to speed up the development. 54 | 55 | You can use Elysia Fn with Eden for full-type safety via Eden Fn. 56 | 57 | ### Permission 58 | You can limit allow or deny scopes of the function, check for authorization header and other headers' fields, validate parameters, or limit keys access programmatically. 59 | 60 | Keys checking supports type-safety and auto-completion of all possible functions, so you're not missing out on some function or accidentally typing down the wrong name. 61 | Narrowed Key 62 | 63 | And narrowing the scope of property programmatically also narrow down the type of parameters, or in other words, full type-safety. 64 | Narrowed Params 65 | 66 | ### Technical detail 67 | In technical detail, Elysia Fn uses JavaScript's Proxy to capture object property, and parameters to create batched requests to the server to handle and returns the value across the network. 68 | Elysia Fn extends superjson, allowing native type in JavaScript like Error, Map, Set, and undefined to parse across JSON data. 69 | 70 | Elysia Fn supports multiple use-cases, for example accessing Prisma on the client-side Nextjs app. 71 | Theoretically, it's possible to use Redis, Sequelize, RabbitMQ, and more. 72 | As Elysia is running on Bun, Elysia Fn can run over 1.2 million operation/second concurrently (tested on M1 Max). 73 | 74 | Learn more about Elysia Fn at Eden Fn. 75 | 76 | ## Type Rework 77 | Over 6.5-9x faster for type checking, and uncountable type's LoC reduction. 78 | 79 | Elysia 0.3, over 80% of Elysia, and Eden types have been rewritten to focus on performance, type-inference, and fast auto-completion. 80 | 81 | Testing for over 350 routes with complex types, Elysia uses only 0.22 82 | seconds to generate a type declaration to use with Eden. 83 | 84 | As the Elysia route now compile directly to literal object instead of Typebox reference, Elysia type declaration is much smaller than it used to be on 0.2 and is easier to be consumed by Eden. And by much smaller, it means 50-99% smaller. 85 | 86 | Not only Elysia integration with TypeScript is significantly faster, but Elysia is better at understanding TypeScript and your code better. 87 | 88 | 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. 89 | Inlining use function now infers the parent type, and the nested guard can reference types of models from the parent more accurately. 90 | 91 | Type Declaration is now also can be built, and exported. 92 | 93 | With the rewrite of type, 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. 94 | For more detail, see this thread on Twitter 95 | 96 | ## File Upload 97 | Thanks to Bun 0.5.7, Form Data is implemented and enabled by default in Elysia 0.3 with multipart/formdata. 98 | 99 | To define type completion and validation for uploading a file, Elysia.t now extends TypeBox with File and Files for file validation. 100 | 101 | 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 of files per field. 102 | 103 | Elysia 0.3 also features schema.contentType to explicitly validate incoming request type to strictly check headers before validating the data. 104 | 105 | ## OpenAPI Schema 3.0.x 106 | With Elysia 0.3, Elysia now uses OpenAPI schema 3.0.x by default for better stating API definitions, and better support for multiple types based on content-type. 107 | 108 | schema.details are 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. 109 | 110 | ## Eden Rework 111 | To support more demand for Elysia, supporting Elysia Fn, Rest all together, Eden has been reworked to scale with the new architecture. 112 | 113 | Eden now exports 3 types of function. 114 | - Eden Treaty eden/treaty: Original Eden syntax you know and love 115 | - Eden Fn eden/fn: Access to Eden Fn 116 | - Eden Fetch eden/fetch: Fetch-like syntax, for highly complex Elysia type (> 1,000 route / Elysia instance) 117 | 118 | With the rework of type definitions and support for Elysia Eden, Eden is now much faster and better at inference type from the server. 119 | 120 | Auto-completion and faster and use 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). 121 | 122 | 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 in any matter of condition. 123 | Narrowed error.webp 124 | 125 | ### Notable Improvement: 126 | - Add string format: 'email', 'uuid', 'date', 'date-time' 127 | - Update @sinclair/typebox to 0.25.24 128 | - Update Raikiri to 0.2.0-beta.0 (ei) 129 | - Add file upload test thanks to #21 (@amirrezamahyari) 130 | - Pre compile lowercase method for Eden 131 | - Reduce complex instruction for most Elysia types 132 | - Compile ElysiaRoute type to literal 133 | - Optimize type compliation, type inference and auto-completion 134 | - Improve type compilation speed 135 | - Improve TypeScript inference between plugin registration 136 | - Optimize TypeScript inference size 137 | - Context creation optimization 138 | - Use Raikiri router by default 139 | - Remove unused function 140 | - Refactor registerSchemaPath to support OpenAPI 3.0.3 141 | - Add error inference for Eden 142 | - Mark @sinclair/typebox as optional peerDenpendencies 143 | 144 | Fix: 145 | - Raikiri 0.2 thrown error on not found 146 | - Union response with t.File is not working 147 | - Definitions isn't defined on Swagger 148 | - details are missing on group plugin 149 | - group plugin, isn't unable to compile schema 150 | - group is not exportable because EXPOSED is a private property 151 | - Multiple cookies doesn't set content-type to application/json 152 | - EXPOSED is not export when using fn.permission 153 | - Missing merged return type for .ws 154 | - Missing nanoid 155 | - context side-effects 156 | - t.Files in swagger is referring to single file 157 | - Eden response type is unknown 158 | - Unable to type setModel inference definition via Eden 159 | - Handle error thrown in non permission function 160 | - Exported variable has or is using name 'SCHEMA' from external module 161 | - Exported variable has or is using name 'DEFS' from external module 162 | - Possible errors for building Elysia app with declaration: true in tsconfig.json 163 | 164 | Breaking Change: 165 | - Rename inject to derive 166 | - Depreacate ElysiaRoute, changed to inline 167 | - Remove derive 168 | - Update from OpenAPI 2.x to OpenAPI 3.0.3 169 | - Move context.store[SYMBOL] to meta[SYMBOL] 170 | 171 | 172 | ## Afterward 173 | 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 Type Rework of Elysia, making type-checking and auto-completion much faster. 174 | 175 | I'm excited to see how you will use Elysia to create the wonderful things you are going to build. 176 | 177 | We have Discord server dedicated to Elysia. Feel free to say hi or just chill and hang out. 178 | 179 | Thank you for supporting Elysia. 180 | 181 | > Under a celestial map that never have ends 182 | > 183 | > On a cliff that never have name 184 | > 185 | > I just holwed 186 | > 187 | > Hoping the neverending reverberation will reach you 188 | > 189 | > And I believe someday, I will stand on edge of the ground 190 | > 191 | > (Until the day I can be back to you to tell it) 192 | > 193 |


/docs/blog/elysia-04.md:

1 | --- 2 | title: Elysia 0.4 - 月夜の音楽会 (Moonlit Night Concert) 3 | sidebar: false 4 | editLink: false 5 | search: false 6 | head: 7 | - - meta 8 | - property: 'og:title' 9 | content: Introducing Elysia 0.4 - 月夜の音楽会 (Moonlit Night Concert) 10 | 11 | - - meta 12 | - name: 'description' 13 | content: Introducing Ahead of Time Compilation, TypeBox 0.26, Response validation per status, and Separation of Elysia Fn. 14 | 15 | - - meta 16 | - property: 'og:description' 17 | content: Introducing Ahead of Time Compilation, TypeBox 0.26, Response validation per status, and Separation of Elysia Fn 18 | 19 | - - meta 20 | - property: 'og:image' 21 | content: https://elysiajs.com/blog/elysia-04/moonlit-night-concert.webp 22 | 23 | - - meta 24 | - property: 'twitter:image' 25 | content: https://elysiajs.com/blog/elysia-04/moonlit-night-concert.webp 26 | --- 27 | 28 | <script setup> 29 | import Blog from '../../components/blog/Layout.vue' 30 | </script> 31 | 32 | <Blog 33 | title="Elysia 0.4 - 月夜の音楽会 (Moonlit Night Concert)" 34 | src="/blog/elysia-04/moonlit-night-concert.webp" 35 | alt="shattered glass pieces floating in the abyss" 36 | author="saltyaom" 37 | date="30 Mar 2023" 38 | > 39 | 40 | Named after the opening music of "The Liar Princess and the Blind Prince" trailer, 「月夜の音楽会」(Moonlit Night Concert) composed and sang by Akiko Shikata. 41 | 42 | This version doesn't introduce an exciting new feature, later but a foundation for more solid ground, and internal improvement for the future of Elysia. 43 | 44 | ## Ahead of Time Complie 45 | By default, Elysia has to deal with conditional checking in various situations, for example, checking if the life-cycle of the route existed before performing, or unwrapping validation schema if provided. 46 | 47 | This introduces a minimal overhead to Elysia and overall because even if the route doesn't have a life-cycle event attached to it, it needs to be runtime checked. 48 | 49 | Since every function is checked on compile time, it's not possible to have a conditional async, for example, a simple route that returns a file should be synced, but since it's compile-time checking, some routes might be async thus making the same simple route async too. 50 | 51 | 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. 52 | 53 | We fix this small overhead by introducing Ahead Time Compilation. 54 | 55 | As the name suggests, instead of checking dynamic life-cycle and validation on the runtime, Elysia checks life-cycle, validation, and the possibility of an async function and generates a compact function, removing an unnecessary part like an un-used life-cycle and validation. 56 | 57 | Making conditional async function possible, since instead of using a centralized function for handling, we compose a new function especially created for each route instead. Elysia then checks all life-cycle functions and handlers to see if there's an async, and if not, the function will be synced to reduce additional overhead. 58 | 59 | ## TypeBox 0.26 60 | TypeBox is a library that powered Elysia's validation and type provider to create a type-level single source of truth, re-exported as Elysia.t. 61 | 62 | In this update, we update TypeBox from 0.25.4 to 0.26. 63 | 64 | This brings a lot of improvement and new features, for example, a Not type and Convert for coercion value which we might support in some next version of Elysia. 65 | 66 | But the one benefit for Elysia would be, Error.First() which allows us to get the first error of type instead of using Iterator, this reduces overhead in creating a new Error to send back to the client. 67 | 68 | There are some changes to TypeBox and Elysia.t that normally wouldn't have much effect on your end, but you can see what's a new feature in TypeBox release here. 69 | 70 | ## Validate response per status 71 | Previously, Elysia's response validate multiple status responses using union type. 72 | 73 | This might have unexpected results for highly dynamic apps with a strict response for status. 74 | For example if you have a route like: 75 | ts 76 | app.post('/strict-status', process, { 77 | schema: { 78 | response: { 79 | 200: t.String(), 80 | 400: t.Number() 81 | } 82 | } 83 | }) 84 | 85 | 86 | It's expected that if 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 union. This might leave an unexpected value to the end user and a type error for Eden Treaty. 87 | 88 | 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. 89 | 90 | ## Separation of Elysia Fn 91 | 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. 92 | 93 | But with the primitive type support, Elysia Fn depends on "superjson" which is around 38% of Elysia's dependency size. 94 | 95 | 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 same as cargo add --feature. 96 | 97 | This makes Elysia lighter for servers that don't use Elysia Fn, Following our philosophy, To ensure that you have what you actually need 98 | 99 | However, if you forgot 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 the same thing. 100 | 101 | For migration, besides a breaking change of installing @elysiajs/fn explicitly, there's no migration need. 102 | 103 | ## Conditional Route 104 | This release introduces .if method for registering a conditional route or plugin. 105 | 106 | This allows you to declaratively for a specific conditional, for example excluding Swagger documentation from the production environment. 107 | ts 108 | const isProduction = process.env.NODE_ENV === 'production' 109 | 110 | const app = new Elysia().if(!isProduction, (app) => 111 | app.use(swagger()) 112 | ) 113 | 114 | 115 | Eden Treaty will be able to recognize the route as if it's a normal route/plugin. 116 | 117 | ## Custom Validation Error 118 | Big thanks to amirrezamahyari on #31 which allows Elysia to access TypeBox's error property, for a better programmatically error response. 119 | 120 | ts 121 | new Elysia() 122 | .onError(({ code, error, set }) => { 123 | if (code === 'NOT_FOUND') { 124 | set.status = 404 125 | 126 | return 'Not Found :(' 127 | } 128 | 129 | if (code === 'VALIDATION') { 130 | set.status = 400 131 | 132 | return { 133 | fields: error.all() 134 | } 135 | } 136 | }) 137 | .post('/sign-in', () => 'hi', { 138 | schema: { 139 | body: t.Object({ 140 | username: t.String(), 141 | password: t.String() 142 | }) 143 | } 144 | }) 145 | .listen(3000) 146 | 147 | 148 | Now you can create a validation error for your API not limited to only the first error. 149 | 150 | --- 151 | 152 | ### Notable Improvement: 153 | - Update TypeBox to 0.26.8 154 | - Inline a declaration for response type 155 | - Refactor some type for faster response 156 | - Use Typebox Error().First() instead of iteration 157 | - Add innerHandle for returning an actual response (for benchmark) 158 | 159 | ### Breaking Change: 160 | - Separate .fn to @elysiajs/fn 161 | 162 | ## Afterward 163 | This release might not be a big release with a new exciting feature, but this improve a solid foundation, and Proof of Concept for planned I have for Elysia in the future, and making Elysia even faster and more versatile than it was. 164 | 165 | I'm really excited for what will be unfold in the future. 166 | 167 | Thank you for your continuous support for Elysia~ 168 | 169 | > the moonlit night concert, our secret 170 | > 171 | > let’s start again without letting go of this hand 172 | 173 | > the moonlit night concert, our bonds 174 | > 175 | > I want to tell you, “you are not a liar” 176 | 177 | > the memories in my heart is like flower that keeps blooming 178 | > 179 | > no matter what you look like, you are my songstress 180 | > 181 | > be by my side tonight 182 | 183 |


/docs/eden/fetch.md:

1 | --- 2 | title: Eden Fetch - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Fetch - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: A fetch-like alternative to Eden Treaty with faster type inference. With Eden Fetch, you can make requests to an Elysia server with end-to-end type-safety without the need of code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: A fetch-like alternative to Eden Treaty with faster type inference. With Eden Fetch, you can make requests to an Elysia server with end-to-end type-safety without the need of code generation. 15 | --- 16 | 17 | # Eden Fetch 18 | A fetch-like alternative to Eden Treaty . 19 | 20 | With Eden Fetch can interact with Elysia server in a type-safe manner using Fetch API. 21 | 22 | --- 23 | 24 | First export your existing Elysia server type: 25 | typescript 26 | // server.ts 27 | import { Elysia, t } from 'elysia' 28 | 29 | const app = new Elysia() 30 | .get('/hi', () => 'Hi Elysia') 31 | .get('/id/:id', ({ params: { id } }) => id) 32 | .post('/mirror', ({ body }) => body, { 33 | body: t.Object({ 34 | id: t.Number(), 35 | name: t.String() 36 | }) 37 | }) 38 | .listen(3000) 39 | 40 | export type App = typeof app 41 | 42 | 43 | Then import the server type, and consume the Elysia API on client: 44 | typescript 45 | import { edenFetch } from '@elysiajs/eden' 46 | import type { App } from './server' 47 | 48 | const fetch = edenFetch<App>('http://localhost:3000') 49 | 50 | // response type: 'Hi Elysia' 51 | const pong = await fetch('/hi', {}) 52 | 53 | // response type: 1895 54 | const id = await fetch('/id/:id', { 55 | params: { 56 | id: '1895' 57 | } 58 | }) 59 | 60 | // response type: { id: 1895, name: 'Skadi' } 61 | const nendoroid = await fetch('/mirror', { 62 | method: 'POST', 63 | body: { 64 | id: 1895, 65 | name: 'Skadi' 66 | } 67 | }) 68 | 69 | 70 | ## Error Handling 71 | You can handle errors the same way as Eden Treaty: 72 | typescript 73 | import { edenFetch } from '@elysiajs/eden' 74 | import type { App } from './server' 75 | 76 | const fetch = edenFetch<App>('http://localhost:3000') 77 | 78 | // response type: { id: 1895, name: 'Skadi' } 79 | const { data: nendoroid, error } = await fetch('/mirror', { 80 | method: 'POST', 81 | body: { 82 | id: 1895, 83 | name: 'Skadi' 84 | } 85 | }) 86 | 87 | if(error) { 88 | switch(error.status) { 89 | case 400: 90 | case 401: 91 | throw error.value 92 | break 93 | 94 | case 500: 95 | case 502: 96 | throw error.value 97 | break 98 | 99 | default: 100 | throw error.value 101 | break 102 | } 103 | } 104 | 105 | const { id, name } = nendoroid 106 | 107 | 108 | ## When should I use Eden Fetch over Eden Treaty 109 | Unlike Elysia < 1.0, Eden Fetch is not faster than Eden Treaty anymore. 110 | 111 | The preference is base on you and your team agreement, however we recommend to use Eden Treaty instead. 112 | 113 | For Elysia < 1.0: 114 | 115 | Using Eden Treaty requires a lot of down-level iteration to map all possible types in a single go, while in contrast, Eden Fetch can be lazily executed until you pick a route. 116 | 117 | With complex types and a lot of server routes, using Eden Treaty on a low-end development device can lead to slow type inference and auto-completion. 118 | 119 | But as Elysia has tweaked and optimized a lot of types and inference, Eden Treaty can perform very well in the considerable amount of routes. 120 | 121 | If your single process contains more than 500 routes, and you need to consume all of the routes in a single frontend codebase, then you might want to use Eden Fetch as it has a significantly better TypeScript performance than Eden Treaty. 122 |


/docs/eden/installation.md:

1 | --- 2 | title: Eden Installation - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Installation - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Start by installing Eden on your frontend with "bun add elysia @elysiajs/eden", then expose your Elysia server type and then start using Eden Treaty or Eden Fetch. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Start by installing Eden on your frontend with "bun add elysia @elysiajs/eden", then expose your Elysia server type and then start using Eden Treaty or Eden Fetch. 15 | --- 16 | 17 | # Eden Installation 18 | Start by installing Eden on your frontend: 19 | bash 20 | bun add @elysiajs/eden 21 | bun add -d elysia 22 | 23 | # If you use Bun specific feature, eg. `Bun.file` 24 | bun add -d @types/bun 25 | 26 | 27 | ::: tip 28 | Eden needs Elysia to infer utilities type. 29 | 30 | Make sure to install Elysia with the version matching on the server. 31 | ::: 32 | 33 | First, export your existing Elysia server type: 34 | typescript 35 | // server.ts 36 | import { Elysia, t } from 'elysia' 37 | 38 | const app = new Elysia() 39 | .get('/', () => 'Hi Elysia') 40 | .get('/id/:id', ({ params: { id } }) => id) 41 | .post('/mirror', ({ body }) => body, { 42 | body: t.Object({ 43 | id: t.Number(), 44 | name: t.String() 45 | }) 46 | }) 47 | .listen(3000) 48 | 49 | export type App = typeof app // [!code ++] 50 | 51 | 52 | Then consume the Elysia API on client side: 53 | typescript twoslash 54 | // @filename: server.ts 55 | import { Elysia, t } from 'elysia' 56 | 57 | const app = new Elysia() 58 | .get('/', 'Hi Elysia') 59 | .get('/id/:id', ({ params: { id } }) => id) 60 | .post('/mirror', ({ body }) => body, { 61 | body: t.Object({ 62 | id: t.Number(), 63 | name: t.String() 64 | }) 65 | }) 66 | .listen(3000) 67 | 68 | export type App = typeof app // [!code ++] 69 | 70 | // @filename: index.ts 71 | // ---cut--- 72 | // client.ts 73 | import { treaty } from '@elysiajs/eden' 74 | import type { App } from './server' // [!code ++] 75 | 76 | const client = treaty<App>('localhost:3000') // [!code ++] 77 | 78 | // response: Hi Elysia 79 | const { data: index } = await client.index.get() 80 | 81 | // response: 1895 82 | const { data: id } = await client.id({ id: 1895 }).get() 83 | 84 | // response: { id: 1895, name: 'Skadi' } 85 | const { data: nendoroid } = await client.mirror.post({ 86 | id: 1895, 87 | name: 'Skadi' 88 | }) 89 | 90 | // @noErrors 91 | client. 92 | // ^| 93 | 94 | 95 | ## Gotcha 96 | Sometimes Eden may not infer type from Elysia correctly, the following are the most common workaround to fix Eden type inference. 97 | 98 | ### Type Strict 99 | Make sure to enable strict mode in tsconfig.json 100 | json 101 | { 102 | "compilerOptions": { 103 | "strict": true // [!code ++] 104 | } 105 | } 106 | 107 | 108 | ### Unmatch Elysia version 109 | Eden depends Elysia class to import Elysia instance and infers type correctly. 110 | 111 | Make sure that both client and server have a matching Elysia version. 112 | 113 | You can check it with npm why command: 114 | 115 | bash 116 | npm why elysia 117 | 118 | 119 | And output should contain only one elysia version on top-level: 120 | 121 | 122 | [email protected] 123 | node_modules/elysia 124 | elysia@"1.1.25" from the root project 125 | peer elysia@">= 1.1.0" from @elysiajs/[email protected] 126 | node_modules/@elysiajs/html 127 | dev @elysiajs/html@"1.1.1" from the root project 128 | peer elysia@">= 1.1.0" from @elysiajs/[email protected] 129 | node_modules/@elysiajs/opentelemetry 130 | dev @elysiajs/opentelemetry@"1.1.7" from the root project 131 | peer elysia@">= 1.1.0" from @elysiajs/[email protected] 132 | node_modules/@elysiajs/swagger 133 | dev @elysiajs/swagger@"1.1.6" from the root project 134 | peer elysia@">= 1.1.0" from @elysiajs/[email protected] 135 | node_modules/@elysiajs/eden 136 | dev @elysiajs/eden@"1.1.3" from the root project 137 | 138 | 139 | 140 | ### TypeScript version 141 | Elysia uses newer features and syntax of TypeScript to infer types in a the most performant way. Features like Const Generic and Template Literal are heavily used. 142 | 143 | Make sure your client has a minimum TypeScript version if >= 5.0 144 | 145 | ### Method Chaining 146 | To make Eden works, Elysia must be using method chaining 147 | 148 | Elysia's type system is complex, methods usually introduce a new type to the instance. 149 | 150 | Using method chaining will help save that new type reference. 151 | 152 | For example: 153 | typescript twoslash 154 | import { Elysia } from 'elysia' 155 | 156 | new Elysia() 157 | .state('build', 1) 158 | // Store is strictly typed // [!code ++] 159 | .get('/', ({ store: { build } }) => build) 160 | .listen(3000) 161 | 162 | Using this, state now returns a new ElysiaInstance type, introducing build into store and replace the current one. 163 | 164 | Without using method chaining, Elysia doesn't save the new type when introduced, leading to no type inference. 165 | typescript twoslash 166 | // @errors: 2339 167 | import { Elysia } from 'elysia' 168 | 169 | const app = new Elysia() 170 | 171 | app.state('build', 1) 172 | 173 | app.get('/', ({ store: { build } }) => build) 174 | 175 | app.listen(3000) 176 | 177 | 178 | ### Type Definitions 179 | Sometimes, if you are using a Bun specific feature like Bun.file or similar API, you may need to install Bun type definitions to the client as well. 180 | 181 | bash 182 | bun add -d @types/bun 183 | 184 | 185 | ### Path alias (monorepo) 186 | If you are using path alias in your monorepo, make sure that frontend are able to resolve the path as same as backend. 187 | 188 | For example, if you have the following path alias for your backend in tsconfig.json: 189 | json 190 | { 191 | "compilerOptions": { 192 | "baseUrl": ".", 193 | "paths": { 194 | "@/*": ["./src/*"] 195 | } 196 | } 197 | } 198 | 199 | 200 | And your backend code is like this: 201 | typescript 202 | import { Elysia } from 'elysia' 203 | import { a, b } from '@/controllers' 204 | 205 | const app = new Elysia() 206 | .use(a) 207 | .use(b) 208 | .listen(3000) 209 | 210 | export type app = typeof app 211 | 212 | 213 | You must make sure that your frontend code is able to resolve the same path alias otherwise type inference will be resolved as any. 214 | 215 | typescript 216 | import { treaty } from '@elysiajs/eden' 217 | import type { app } from '@/index' 218 | 219 | const client = treaty<app>('localhost:3000') 220 | 221 | // This should be able to resolve the same module both frontend and backend, and not `any` 222 | import { a, b } from '@/controllers' 223 | 224 | 225 | To fix this, you must make sure that path alias is resolved to the same file in both frontend and backend. 226 | 227 | So you must change the path alias in tsconfig.json to: 228 | json 229 | { 230 | "compilerOptions": { 231 | "baseUrl": ".", 232 | "paths": { 233 | "@/*": ["../apps/backend/src/*"] 234 | } 235 | } 236 | } 237 | 238 | 239 | If configured correctly, you should be able to resolve the same module in both frontend and backend. 240 | typescript 241 | // This should be able to resolve the same module both frontend and backend, and not `any` 242 | import { a, b } from '@/controllers' 243 | 244 | 245 | #### Scope 246 | We recommended to add a scope prefix for each modules in your monorepo to avoid any confusion and conflict that may happen. 247 | 248 | json 249 | { 250 | "compilerOptions": { 251 | "baseUrl": ".", 252 | "paths": { 253 | "@frontend/*": ["./apps/frontend/src/*"], 254 | "@backend/*": ["./apps/backend/src/*"] 255 | } 256 | } 257 | } 258 | 259 | 260 | Then you can import the module like this: 261 | typescript 262 | // Should work in both frontend and backend and not return `any` 263 | import { a, b } from '@backend/controllers' 264 | 265 | 266 | We recommended creating a single tsconfig.json that define a baseUrl as the root of your repo, provide a path according to the module location, and create a tsconfig.json for each module that inherits the root tsconfig.json which has the path alias. 267 | 268 | You may find a working example of in this path alias example repo. 269 |


/docs/eden/overview.md:

1 | --- 2 | title: End-to-End Type Safety - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: End-to-End Type Safety - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia supports end-to-end type safety with Elysia Eden since start. End-to-end type-safety refers to a system in which every component of the system is checked for type consistency, meaning that data is passed between components only if the types of the data are compatible. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia supports end-to-end type safety with Elysia Eden since start. End-to-end type-safety refers to a system in which every component of the system is checked for type consistency, meaning that data is passed between components only if the types of the data are compatible. 15 | --- 16 | 17 | # End-to-End Type Safety 18 | Imagine you have a toy train set. 19 | 20 | Each piece of the train track has to fit perfectly with the next one, like puzzle pieces. 21 | 22 | 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. 23 | 24 | For a framework to have end-to-end type safety means you can connect client and server in a type-safe manner. 25 | 26 | Elysia provide end-to-end type safety without code generation out of the box with RPC-like connector, Eden 27 | 28 | 32 | 33 | Others framework that support e2e type safety: 34 | - tRPC 35 | - Remix 36 | - SvelteKit 37 | - Nuxt 38 | - TS-Rest 39 | 40 | 51 | 52 | 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. 53 | 54 | ## Eden 55 | Eden is a RPC-like client to connect Elysia end-to-end type safety using only TypeScript's type inference instead of code generation. 56 | 57 | Allowing you to sync client and server types effortlessly, weighing less than 2KB. 58 | 59 | Eden is consists of 2 modules: 60 | 1. Eden Treaty (recommended): an improved version RFC version of Eden Treaty 61 | 2. Eden Fetch: Fetch-like client with type safety. 62 | 63 | Below is an overview, use-case and comparison for each module. 64 | 65 | ## Eden Treaty (Recommended) 66 | Eden Treaty is an object-like representation of an Elysia server providing end-to-end type safety and a significantly improved developer experience. 67 | 68 | With Eden Treaty we can connect interact Elysia server with full-type support and auto-completion, error handling with type narrowing, and creating type safe unit test. 69 | 70 | Example usage of Eden Treaty: 71 | typescript twoslash 72 | // @filename: server.ts 73 | import { Elysia, t } from 'elysia' 74 | 75 | const app = new Elysia() 76 | .get('/', 'hi') 77 | .get('/users', () => 'Skadi') 78 | .put('/nendoroid/:id', ({ body }) => body, { 79 | body: t.Object({ 80 | name: t.String(), 81 | from: t.String() 82 | }) 83 | }) 84 | .get('/nendoroid/:id/name', () => 'Skadi') 85 | .listen(3000) 86 | 87 | export type App = typeof app 88 | 89 | // @filename: index.ts 90 | // ---cut--- 91 | import { treaty } from '@elysiajs/eden' 92 | import type { App } from './server' 93 | 94 | const app = treaty<App>('localhost:3000') 95 | 96 | // @noErrors 97 | app. 98 | // ^| 99 | 100 | 101 | 102 | 103 | // Call [GET] at '/' 104 | const { data } = await app.index.get() 105 | 106 | // Call [POST] at '/nendoroid/:id' 107 | const { data: nendoroid, error } = await app.nendoroid({ id: 1895 }).put({ 108 | name: 'Skadi', 109 | from: 'Arknights' 110 | }) 111 | 112 | 113 | ## Eden Fetch 114 | A fetch-like alternative to Eden Treaty for developers that prefers fetch syntax. 115 | typescript 116 | import { edenFetch } from '@elysiajs/eden' 117 | import type { App } from './server' 118 | 119 | const fetch = edenFetch<App>('http://localhost:3000') 120 | 121 | const { data } = await fetch('/name/:name', { 122 | method: 'POST', 123 | params: { 124 | name: 'Saori' 125 | }, 126 | body: { 127 | branch: 'Arius', 128 | type: 'Striker' 129 | } 130 | }) 131 | 132 | 133 | ::: tip NOTE 134 | Unlike Eden Treaty, Eden Fetch doesn't provide Web Socket implementation for Elysia server 135 | ::: 136 |


/docs/eden/test.md:

1 | --- 2 | title: Eden Test - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Unit Test - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Using Eden, we can perform unit-test to provide end-to-end type safety, and auto-completion, tracking type safety from migration 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Using Eden, we can perform unit-test to provide end-to-end type safety, and auto-completion, tracking type safety from migration 15 | --- 16 | 17 | # Eden Test 18 | Using Eden, we can create an integration test with end-to-end type safety and auto-completion. 19 | 20 | 24 | 25 | > Using Eden Treaty to create tests by irvilerodrigues on Twitter 26 | 27 | ## Setup 28 | We can use Bun test to create tests. 29 | 30 | Create test/index.test.ts in the root of project directory with the following: 31 | 32 | typescript 33 | // test/index.test.ts 34 | import { describe, expect, it } from 'bun:test' 35 | 36 | import { edenTreaty } from '@elysiajs/eden' 37 | 38 | const app = new Elysia() 39 | .get('/', () => 'hi') 40 | .listen(3000) 41 | 42 | const api = edenTreaty<typeof app>('http://localhost:3000') 43 | 44 | describe('Elysia', () => { 45 | it('return a response', async () => { 46 | const { data } = await api.get() 47 | 48 | expect(data).toBe('hi') 49 | }) 50 | }) 51 | 52 | 53 | Then we can perform tests by running bun test 54 | 55 | bash 56 | bun test 57 | 58 | 59 | This allows us to perform integration tests programmatically instead of manual fetch while supporting type checking automatically. 60 |


/docs/eden/treaty/config.md:

1 | --- 2 | title: Eden Treaty Config - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Config - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Config 18 | Eden Treaty accepts 2 parameters: 19 | - urlOrInstance - URL endpoint or Elysia instance 20 | - options (optional) - Customize fetch behavior 21 | 22 | ## urlOrInstance 23 | Accept either URL endpoint as string or a literal Elysia instance. 24 | 25 | Eden will change the behavior based on type as follows: 26 | 27 | ### URL Endpoint (string) 28 | If URL endpoint is passed, Eden Treaty will use fetch or config.fetcher to create a network request to an Elysia instance. 29 | 30 | typescript 31 | import { treaty } from '@elysiajs/eden' 32 | import type { App } from './server' 33 | 34 | const api = treaty<App>('localhost:3000') 35 | 36 | 37 | You may or may not specified a protocol for URL endpoint. 38 | 39 | Elysia will appends the endpoints automatically as follows: 40 | 1. If protocol is specified, use the URL directly 41 | 2. If the URL is localhost and ENV is not production, use http 42 | 3. Otherwise use https 43 | 44 | This also apply to Web Socket as well for determining between ws:// or wss://. 45 | 46 | --- 47 | 48 | ### Elysia Instance 49 | If Elysia instance is passed, Eden Treaty will create a Request class and pass to Elysia.handle directly without creating a network request. 50 | 51 | This allows us to interact with Elysia server directly without request overhead, or the need start a server. 52 | 53 | typescript 54 | import { Elysia } from 'elysia' 55 | import { treaty } from '@elysiajs/eden' 56 | 57 | const app = new Elysia() 58 | .get('/hi', 'Hi Elysia') 59 | .listen(3000) 60 | 61 | const api = treaty(app) 62 | 63 | 64 | If an instance is passed, generic is not needed to be pass as Eden Treaty can infers the type from a parameter directly. 65 | 66 | This patterns is recommended for performing unit tests, or creating a type-safe reverse proxy server or micro-services. 67 | 68 | ## Options 69 | 2nd optional parameters for Eden Treaty to customize fetch behavior, accepting parameters as follows: 70 | - fetch - add default parameters to fetch intialization (RequestInit) 71 | - headers - define default headers 72 | - fetcher - custom fetch function eg. Axios, unfetch 73 | - onRequest - Intercept and modify fetch request before firing 74 | - onResponse - Intercept and modify fetch's response 75 | 76 | ## Fetch 77 | Default parameters append to 2nd parameters of fetch extends type of Fetch.RequestInit. 78 | 79 | typescript 80 | export type App = typeof app // [!code ++] 81 | import { treaty } from '@elysiajs/eden' 82 | // ---cut--- 83 | treaty<App>('localhost:3000', { 84 | fetch: { 85 | credentials: 'include' 86 | } 87 | }) 88 | 89 | 90 | All parameters that passed to fetch, will be passed to fetcher, which is an equivalent to: 91 | typescript 92 | fetch('http://localhost:3000', { 93 | credentials: 'include' 94 | }) 95 | 96 | 97 | ## Headers 98 | Provide an additional default headers to fetch, a shorthand of options.fetch.headers. 99 | 100 | typescript 101 | treaty<App>('localhost:3000', { 102 | headers: { 103 | 'X-Custom': 'Griseo' 104 | } 105 | }) 106 | 107 | 108 | All parameters that passed to fetch, will be passed to fetcher, which is an equivalent to: 109 | typescript twoslash 110 | fetch('localhost:3000', { 111 | headers: { 112 | 'X-Custom': 'Griseo' 113 | } 114 | }) 115 | 116 | 117 | headers may accepts the following as parameters: 118 | - Object 119 | - Function 120 | 121 | ### Headers Object 122 | If object is passed, then it will be passed to fetch directly 123 | 124 | typescript 125 | treaty<App>('localhost:3000', { 126 | headers: { 127 | 'X-Custom': 'Griseo' 128 | } 129 | }) 130 | 131 | 132 | ### Function 133 | You may specify a headers as a function to return custom headers based on condition 134 | 135 | typescript 136 | treaty<App>('localhost:3000', { 137 | headers(path, options) { 138 | if(path.startsWith('user')) 139 | return { 140 | authorization: 'Bearer 12345' 141 | } 142 | } 143 | }) 144 | 145 | 146 | You may return object to append its value to fetch headers. 147 | 148 | headers function accepts 2 parameters: 149 | - path string - path which will be sent to parameter 150 | - note: hostname will be exclude eg. (/user/griseo) 151 | - options RequestInit: Parameters that passed through 2nd parameter of fetch 152 | 153 | ### Array 154 | You may define a headers function as an array if multiple conditions are need. 155 | 156 | typescript 157 | treaty<App>('localhost:3000', { 158 | headers: [ 159 | (path, options) => { 160 | if(path.startsWith('user')) 161 | return { 162 | authorization: 'Bearer 12345' 163 | } 164 | } 165 | ] 166 | }) 167 | 168 | 169 | Eden Treaty will run all functions even if the value is already returned. 170 | 171 | ## Headers Priority 172 | Eden Treaty will prioritize the order headers if duplicated as follows: 173 | 1. Inline method - Passed in method function directly 174 | 2. headers - Passed in config.headers 175 | - If config.headers is array, parameters that come after will be prioritize 176 | 3. fetch - Passed in config.fetch.headers 177 | 178 | For example, for the following example: 179 | typescript 180 | const api = treaty<App>('localhost:3000', { 181 | headers: { 182 | authorization: 'Bearer Aponia' 183 | } 184 | }) 185 | 186 | api.profile.get({ 187 | headers: { 188 | authorization: 'Bearer Griseo' 189 | } 190 | }) 191 | 192 | 193 | This will be results in: 194 | typescript 195 | fetch('http://localhost:3000', { 196 | headers: { 197 | authorization: 'Bearer Griseo' 198 | } 199 | }) 200 | 201 | 202 | If inline function doesn't specified headers, then the result will be "Bearer Aponia" instead. 203 | 204 | ## Fetcher 205 | Provide a custom fetcher function instead of using an environment's default fetch. 206 | 207 | typescript 208 | treaty<App>('localhost:3000', { 209 | fetcher(url, options) { 210 | return fetch(url, options) 211 | } 212 | }) 213 | 214 | 215 | It's recommended to replace fetch if you want to use other client other than fetch, eg. Axios, unfetch. 216 | 217 | ## OnRequest 218 | Intercept and modify fetch request before firing. 219 | 220 | You may return object to append the value to RequestInit. 221 | 222 | typescript 223 | treaty<App>('localhost:3000', { 224 | onRequest(path, options) { 225 | if(path.startsWith('user')) 226 | return { 227 | headers: { 228 | authorization: 'Bearer 12345' 229 | } 230 | } 231 | } 232 | }) 233 | 234 | 235 | If value is returned, Eden Treaty will perform a shallow merge for returned value and value.headers. 236 | 237 | onRequest accepts 2 parameters: 238 | - path string - path which will be sent to parameter 239 | - note: hostname will be exclude eg. (/user/griseo) 240 | - options RequestInit: Parameters that passed through 2nd parameter of fetch 241 | 242 | ### Array 243 | You may define an onRequest function as an array if multiples conditions are need. 244 | 245 | typescript 246 | treaty<App>('localhost:3000', { 247 | onRequest: [ 248 | (path, options) => { 249 | if(path.startsWith('user')) 250 | return { 251 | headers: { 252 | authorization: 'Bearer 12345' 253 | } 254 | } 255 | } 256 | ] 257 | }) 258 | 259 | 260 | Eden Treaty will run all functions even if the value is already returned. 261 | 262 | ## onResponse 263 | Intercept and modify fetch's response or return a new value. 264 | 265 | typescript 266 | treaty<App>('localhost:3000', { 267 | onResponse(response) { 268 | if(response.ok) 269 | return response.json() 270 | } 271 | }) 272 | 273 | 274 | onRequest accepts 1 parameter: 275 | - response Response - Web Standard Response normally returned from fetch 276 | 277 | ### Array 278 | You may define an onResponse function as an array if multiple conditions are need. 279 | 280 | typescript 281 | treaty<App>('localhost:3000', { 282 | onResponse: [ 283 | (response) => { 284 | if(response.ok) 285 | return response.json() 286 | } 287 | ] 288 | }) 289 | 290 | Unlike headers and onRequest, Eden Treaty will loop through functions until a returned value is found or error thrown, the returned value will be use as a new response. 291 |


/docs/eden/treaty/legacy.md:

1 | --- 2 | title: Eden Treaty Legacy - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Legacy - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Eden Treaty Legacy 18 | 19 | ::: tip NOTE 20 | This is a documentation for Eden Treaty 1 or (edenTreaty) 21 | 22 | For a new project, we recommended starting with Eden Treaty 2 (treaty) instead. 23 | ::: 24 | 25 | Eden Treaty is an object-like representation of an Elysia server. 26 | 27 | Providing accessor like a normal object with type directly from the server, helping us to move faster, and make sure that nothing break 28 | 29 | --- 30 | 31 | To use Eden Treaty, first export your existing Elysia server type: 32 | typescript 33 | // server.ts 34 | import { Elysia, t } from 'elysia' 35 | 36 | const app = new Elysia() 37 | .get('/', () => 'Hi Elysia') 38 | .get('/id/:id', ({ params: { id } }) => id) 39 | .post('/mirror', ({ body }) => body, { 40 | body: t.Object({ 41 | id: t.Number(), 42 | name: t.String() 43 | }) 44 | }) 45 | .listen(3000) 46 | 47 | export type App = typeof app // [!code ++] 48 | 49 | 50 | Then import the server type, and consume the Elysia API on client: 51 | typescript 52 | // client.ts 53 | import { edenTreaty } from '@elysiajs/eden' 54 | import type { App } from './server' // [!code ++] 55 | 56 | const app = edenTreaty<App>('http://localhost:') 57 | 58 | // response type: 'Hi Elysia' 59 | const { data: pong, error } = app.get() 60 | 61 | // response type: 1895 62 | const { data: id, error } = app.id['1895'].get() 63 | 64 | // response type: { id: 1895, name: 'Skadi' } 65 | const { data: nendoroid, error } = app.mirror.post({ 66 | id: 1895, 67 | name: 'Skadi' 68 | }) 69 | 70 | 71 | ::: tip 72 | Eden Treaty is fully type-safe with auto-completion support. 73 | ::: 74 | 75 | ## Anatomy 76 | Eden Treaty will transform all existing paths to object-like representation, that can be described as: 77 | typescript 78 | EdenTreaty.<1>.<2>.<n>.<method>({ 79 | ...body, 80 | $query?: {}, 81 | $fetch?: RequestInit 82 | }) 83 | 84 | 85 | ### Path 86 | Eden will transform / into . which can be called with a registered method, for example: 87 | - /path -> .path 88 | - /nested/path -> .nested.path 89 | 90 | ### Path parameters 91 | Path parameters will be mapped automatically by their name in the URL. 92 | 93 | - /id/:id -> .id.<anyThing> 94 | - eg: .id.hi 95 | - eg: .id['123'] 96 | 97 | ::: tip 98 | If a path doesn't support path parameters, TypeScript will show an error. 99 | ::: 100 | 101 | ### Query 102 | You can append queries to path with $query: 103 | typescript 104 | app.get({ 105 | $query: { 106 | name: 'Eden', 107 | code: 'Gold' 108 | } 109 | }) 110 | 111 | 112 | ### Fetch 113 | Eden Treaty is a fetch wrapper, you can add any valid Fetch parameters to Eden by passing it to $fetch: 114 | typescript 115 | app.post({ 116 | $fetch: { 117 | headers: { 118 | 'x-organization': 'MANTIS' 119 | } 120 | } 121 | }) 122 | 123 | 124 | ## Error Handling 125 | Eden Treaty will return a value of data and error as a result, both fully typed. 126 | typescript 127 | // response type: { id: 1895, name: 'Skadi' } 128 | const { data: nendoroid, error } = app.mirror.post({ 129 | id: 1895, 130 | name: 'Skadi' 131 | }) 132 | 133 | if(error) { 134 | switch(error.status) { 135 | case 400: 136 | case 401: 137 | warnUser(error.value) 138 | break 139 | 140 | case 500: 141 | case 502: 142 | emergencyCallDev(error.value) 143 | break 144 | 145 | default: 146 | reportError(error.value) 147 | break 148 | } 149 | 150 | throw error 151 | } 152 | 153 | const { id, name } = nendoroid 154 | 155 | 156 | Both data, and error will be typed as nullable until you can confirm their statuses with a type guard. 157 | 158 | To put it simply, if fetch is successful, data will have a value and error will be null, and vice-versa. 159 | 160 | ::: tip 161 | Error is wrapped with an Error with its value return from the server can be retrieve from Error.value 162 | ::: 163 | 164 | ### Error type based on status 165 | Both Eden Treaty and Eden Fetch can narrow down an error type based on status code if you explicitly provided an error type in the Elysia server. 166 | 167 | typescript 168 | // server.ts 169 | import { Elysia, t } from 'elysia' 170 | 171 | const app = new Elysia() 172 | .model({ 173 | nendoroid: t.Object({ 174 | id: t.Number(), 175 | name: t.String() 176 | }), 177 | error: t.Object({ 178 | message: t.String() 179 | }) 180 | }) 181 | .get('/', () => 'Hi Elysia') 182 | .get('/id/:id', ({ params: { id } }) => id) 183 | .post('/mirror', ({ body }) => body, { 184 | body: 'nendoroid', 185 | response: { 186 | 200: 'nendoroid', // [!code ++] 187 | 400: 'error', // [!code ++] 188 | 401: 'error' // [!code ++] 189 | } 190 | }) 191 | .listen(3000) 192 | 193 | export type App = typeof app 194 | 195 | 196 | An on the client side: 197 | typescript 198 | const { data: nendoroid, error } = app.mirror.post({ 199 | id: 1895, 200 | name: 'Skadi' 201 | }) 202 | 203 | if(error) { 204 | switch(error.status) { 205 | case 400: 206 | case 401: 207 | // narrow down to type 'error' described in the server 208 | warnUser(error.value) 209 | break 210 | 211 | default: 212 | // typed as unknown 213 | reportError(error.value) 214 | break 215 | } 216 | 217 | throw error 218 | } 219 | 220 | 221 | ## WebSocket 222 | Eden supports WebSocket using the same API as a normal route. 223 | typescript 224 | // Server 225 | import { Elysia, t } from 'elysia' 226 | 227 | const app = new Elysia() 228 | .ws('/chat', { 229 | message(ws, message) { 230 | ws.send(message) 231 | }, 232 | body: t.String(), 233 | response: t.String() 234 | }) 235 | .listen(3000) 236 | 237 | type App = typeof app 238 | 239 | 240 | To start listening to real-time data, call the .subscribe method: 241 | typescript 242 | // Client 243 | import { edenTreaty } from '@elysiajs/eden' 244 | const app = edenTreaty<App>('http://localhost:') 245 | 246 | const chat = app.chat.subscribe() 247 | 248 | chat.subscribe((message) => { 249 | console.log('got', message) 250 | }) 251 | 252 | chat.send('hello from client') 253 | 254 | 255 | We can use schema to enforce type-safety on WebSockets, just like a normal route. 256 | 257 | --- 258 | 259 | Eden.subscribe returns EdenWebSocket which extends the WebSocket class with type-safety. The syntax is identical with the WebSocket 260 | 261 | If more control is need, EdenWebSocket.raw can be accessed to interact with the native WebSocket API. 262 | 263 | ## File Upload 264 | You may either pass one of the following to the field to attach file: 265 | - File 266 | - FileList 267 | - Blob 268 | 269 | Attaching a file will results content-type to be multipart/form-data 270 | 271 | Suppose we have the server as the following: 272 | typescript 273 | // server.ts 274 | import { Elysia } from 'elysia' 275 | 276 | const app = new Elysia() 277 | .post('/image', ({ body: { image, title } }) => title, { 278 | body: t.Object({ 279 | title: t.String(), 280 | image: t.Files(), 281 | }) 282 | }) 283 | .listen(3000) 284 | 285 | export type App = typeof app 286 | 287 | 288 | We may use the client as follows: 289 | typescript 290 | // client.ts 291 | import { edenTreaty } from '@elysia/eden' 292 | import type { Server } from './server' 293 | 294 | export const client = edenTreaty<Server>('http://localhost:3000') 295 | 296 | const id = <T extends HTMLElement = HTMLElement>(id: string) => 297 | document.getElementById(id)! as T 298 | 299 | const { data } = await client.image.post({ 300 | title: "Misono Mika", 301 | image: id<HTMLInputElement>('picture').files!, 302 | }) 303 | 304 |


/docs/eden/treaty/overview.md:

1 | --- 2 | title: Overview - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Overview - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: 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, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: 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, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Eden Treaty 18 | 19 | Eden Treaty is an object representation to interact with a server and features type safety, auto-completion, and error handling. 20 | 21 | To use Eden Treaty, first export your existing Elysia server type: 22 | 23 | typescript 24 | // server.ts 25 | import { Elysia, t } from 'elysia' 26 | 27 | const app = new Elysia() 28 | .get('/hi', () => 'Hi Elysia') 29 | .get('/id/:id', ({ params: { id } }) => id) 30 | .post('/mirror', ({ body }) => body, { 31 | body: t.Object({ 32 | id: t.Number(), 33 | name: t.String() 34 | }) 35 | }) 36 | .listen(3000) 37 | 38 | export type App = typeof app // [!code ++] 39 | 40 | 41 | Then import the server type and consume the Elysia API on the client: 42 | 43 | typescript twoslash 44 | // @filename: server.ts 45 | import { Elysia, t } from 'elysia' 46 | 47 | const app = new Elysia() 48 | .get('/hi', () => 'Hi Elysia') 49 | .get('/id/:id', ({ params: { id } }) => id) 50 | .post('/mirror', ({ body }) => body, { 51 | body: t.Object({ 52 | id: t.Number(), 53 | name: t.String() 54 | }) 55 | }) 56 | .listen(3000) 57 | 58 | export type App = typeof app // [!code ++] 59 | 60 | // @filename: client.ts 61 | // ---cut--- 62 | // client.ts 63 | import { treaty } from '@elysiajs/eden' 64 | import type { App } from './server' // [!code ++] 65 | 66 | const app = treaty<App>('localhost:3000') 67 | 68 | // response type: 'Hi Elysia' 69 | const { data, error } = await app.hi.get() 70 | // ^? 71 | 72 | 73 | ## Tree like syntax 74 | 75 | HTTP Path is a resource indicator for a file system tree. 76 | 77 | File system consists of multiple levels of folders, for example: 78 | 79 | - /documents/elysia 80 | - /documents/kalpas 81 | - /documents/kelvin 82 | 83 | Each level is separated by / (slash) and a name. 84 | 85 | However in JavaScript, instead of using "/" (slash) we use "." (dot) to access deeper resources. 86 | 87 | Eden Treaty turns an Elysia server into a tree-like file system that can be accessed in the JavaScript frontend instead. 88 | 89 | | Path | Treaty | 90 | | ------------ | ------------ | 91 | | / | .index | 92 | | /hi | .hi | 93 | | /deep/nested | .deep.nested | 94 | 95 | Combined with the HTTP method, we can interact with the Elysia server. 96 | 97 | | Path | Method | Treaty | 98 | | ------------ | ------ | ------------------- | 99 | | / | GET | .index.get() | 100 | | /hi | GET | .hi.get() | 101 | | /deep/nested | GET | .deep.nested.get() | 102 | | /deep/nested | POST | .deep.nested.post() | 103 | 104 | ## Dynamic path 105 | 106 | However, dynamic path parameters cannot be expressed using notation. If they are fully replaced, we don't know what the parameter name is supposed to be. 107 | 108 | typescript 109 | // ❌ Unclear what the value is supposed to represent? 110 | treaty.item['skadi'].get() 111 | 112 | 113 | To handle this, we can specify a dynamic path using a function to provide a key value instead. 114 | 115 | typescript 116 | // ✅ Clear that value is dynamic path is 'name' 117 | treaty.item({ name: 'Skadi' }).get() 118 | 119 | 120 | | Path | Treaty | 121 | | --------------- | -------------------------------- | 122 | | /item | .item | 123 | | /item/:name | .item({ name: 'Skadi' }) | 124 | | /item/:name/id | .item({ name: 'Skadi' }).id | 125 |


/docs/eden/treaty/parameters.md:

1 | --- 2 | title: Eden Treaty Parameters - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Parameters - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Parameters 18 | 19 | We need to send a payload to server eventually. 20 | 21 | To handle this, Eden Treaty's methods accept 2 parameters to send data to server. 22 | 23 | Both parameters is type safe and will be guided by TypeScript automatically: 24 | 25 | 1. body 26 | 2. additional parameters 27 | - query 28 | - headers 29 | - fetch 30 | 31 | typescript 32 | import { Elysia, t } from 'elysia' 33 | import { treaty } from '@elysiajs/eden' 34 | 35 | const app = new Elysia() 36 | .post('/user', ({ body }) => body, { 37 | body: t.Object({ 38 | name: t.String() 39 | }) 40 | }) 41 | .listen(3000) 42 | 43 | const api = treaty<typeof app>('localhost:3000') 44 | 45 | // ✅ works 46 | api.user.post({ 47 | name: 'Elysia' 48 | }) 49 | 50 | // ✅ also works 51 | api.user.post({ 52 | name: 'Elysia' 53 | }, { 54 | // This is optional as not specified in schema 55 | headers: { 56 | authorization: 'Bearer 12345' 57 | }, 58 | query: { 59 | id: 2 60 | } 61 | }) 62 | 63 | 64 | Unless if the method doesn't accept body, then body will be omitted and left with single parameter only. 65 | 66 | If the method "GET" or "HEAD": 67 | 68 | 1. Additional parameters 69 | - query 70 | - headers 71 | - fetch 72 | 73 | typescript 74 | import { Elysia } from 'elysia' 75 | import { treaty } from '@elysiajs/eden' 76 | 77 | const app = new Elysia() 78 | .get('/hello', () => 'hi') 79 | .listen(3000) 80 | 81 | const api = treaty<typeof app>('localhost:3000') 82 | 83 | // ✅ works 84 | api.hello.get({ 85 | // This is optional as not specified in schema 86 | headers: { 87 | hello: 'world' 88 | } 89 | }) 90 | 91 | 92 | ## Empty body 93 | If body is optional or not need but query or headers is required, you may pass the body as null or undefined instead. 94 | 95 | typescript 96 | import { Elysia, t } from 'elysia' 97 | import { treaty } from '@elysiajs/eden' 98 | 99 | const app = new Elysia() 100 | .post('/user', () => 'hi', { 101 | query: t.Object({ 102 | name: t.String() 103 | }) 104 | }) 105 | .listen(3000) 106 | 107 | const api = treaty<typeof app>('localhost:3000') 108 | 109 | api.user.post(null, { 110 | query: { 111 | name: 'Ely' 112 | } 113 | }) 114 | 115 | 116 | ## Fetch parameters 117 | 118 | Eden Treaty is a fetch wrapper, we may add any valid Fetch parameters to Eden by passing it to $fetch: 119 | 120 | typescript 121 | import { Elysia, t } from 'elysia' 122 | import { treaty } from '@elysiajs/eden' 123 | 124 | const app = new Elysia() 125 | .get('/hello', () => 'hi') 126 | .listen(3000) 127 | 128 | const api = treaty<typeof app>('localhost:3000') 129 | 130 | const controller = new AbortController() 131 | 132 | const cancelRequest = setTimeout(() => { 133 | controller.abort() 134 | }, 5000) 135 | 136 | await api.hello.get({ 137 | fetch: { 138 | signal: controller.signal 139 | } 140 | }) 141 | 142 | clearTimeout(cancelRequest) 143 | 144 | 145 | ## File Upload 146 | We may either pass one of the following to attach file(s): 147 | - File 148 | - File[] 149 | - FileList 150 | - Blob 151 | 152 | Attaching a file will results content-type to be multipart/form-data 153 | 154 | Suppose we have the server as the following: 155 | typescript 156 | import { Elysia, t } from 'elysia' 157 | import { treaty } from '@elysiajs/eden' 158 | 159 | const app = new Elysia() 160 | .post('/image', ({ body: { image, title } }) => title, { 161 | body: t.Object({ 162 | title: t.String(), 163 | image: t.Files() 164 | }) 165 | }) 166 | .listen(3000) 167 | 168 | export const api = treaty<typeof app>('localhost:3000') 169 | 170 | const images = document.getElementById('images') as HTMLInputElement 171 | 172 | const { data } = await api.image.post({ 173 | title: "Misono Mika", 174 | image: images.files!, 175 | }) 176 | 177 |


/docs/eden/treaty/response.md:

1 | --- 2 | title: Eden Treaty Response - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Response - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Response 18 | Once the fetch method is called, Eden Treaty return a promise containing an object with the following properties: 19 | - data - returned value of the response (2xx) 20 | - error - returned value from the response (>= 3xx) 21 | - response Response - Web Standard Response class 22 | - status number - HTTP status code 23 | - headers FetchRequestInit['headers'] - response headers 24 | 25 | 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 error() helper function to handle the error, and Eden will provide type narrowing for the error value. 26 | 27 | typescript 28 | import { Elysia, t } from 'elysia' 29 | import { treaty } from '@elysiajs/eden' 30 | 31 | const app = new Elysia() 32 | .post('/user', ({ body: { name }, error }) => { 33 | if(name === 'Otto') 34 | return error(400, 'Bad Request') 35 | 36 | return name 37 | }, { 38 | body: t.Object({ 39 | name: t.String() 40 | }) 41 | }) 42 | .listen(3000) 43 | 44 | const api = treaty<typeof app>('localhost:3000') 45 | 46 | const submit = async (name: string) => { 47 | const { data, error } = await api.user.post({ 48 | name 49 | }) 50 | 51 | // type: string | null 52 | console.log(data) 53 | 54 | if (error) 55 | switch(error.status) { 56 | case 400: 57 | // Error type will be narrow down 58 | throw error.value 59 | 60 | default: 61 | throw error.value 62 | } 63 | 64 | // Once the error is handled, type will be unwrapped 65 | // type: string 66 | return data 67 | } 68 | 69 | 70 | By default, Elysia infers error and response type to TypeScript automatically, and Eden will be providing auto-completion and type narrowing for accurate behavior. 71 | 72 | ::: tip 73 | If the server responds with an HTTP status >= 300, then value will be always be null, and error will have a returned value instead. 74 | 75 | Otherwise, response will be passed to data. 76 | ::: 77 | 78 | ## Stream response 79 | Eden will interpret a stream response as AsyncGenerator allowing us to use for await loop to consume the stream. 80 | 81 | 82 | typescript twoslash 83 | import { Elysia } from 'elysia' 84 | import { treaty } from '@elysiajs/eden' 85 | 86 | const app = new Elysia() 87 | .get('/ok', function* () { 88 | yield 1 89 | yield 2 90 | yield 3 91 | }) 92 | 93 | const { data, error } = await treaty(app).ok.get() 94 | if (error) throw error 95 | 96 | for await (const chunk of data) 97 | console.log(chunk) 98 | // ^? 99 | 100 |


/docs/eden/treaty/unit-test.md:

1 | --- 2 | title: Eden Treaty Unit Test - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Unit Test - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # Unit Test 18 | According to Eden Treaty config and Unit Test, we may pass an Elysia instance to Eden Treaty directly to interact with Elysia server directly without sending a network request. 19 | 20 | We may use this patterns to create a unit test with end-to-end type safety and type-level test all at once. 21 | 22 | typescript twoslash 23 | // test/index.test.ts 24 | import { describe, expect, it } from 'bun:test' 25 | import { Elysia } from 'elysia' 26 | import { treaty } from '@elysiajs/eden' 27 | 28 | const app = new Elysia().get('/hello', 'hi') 29 | const api = treaty(app) 30 | 31 | describe('Elysia', () => { 32 | it('return a response', async () => { 33 | const { data } = await api.hello.get() 34 | 35 | expect(data).toBe('hi') 36 | // ^? 37 | 38 | }) 39 | }) 40 | 41 | 42 | ## Type safety test 43 | To perform a type safety test, simply run tsc to test folders. 44 | 45 | bash 46 | tsc --noEmit test/**/*.ts 47 | 48 | 49 | This is useful to ensure type integrity for both client and server, especially during migrations. 50 |


/docs/eden/treaty/websocket.md:

1 | --- 2 | title: Eden Treaty Web Socket - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Eden Treaty Web Socket - ElysiaJS 7 | 8 | - - meta 9 | - name: 'og:description' 10 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Eden Treaty is an object-like representation of an Elysia server, providing an end-to-end type safety, and a significantly improved developer experience. With Eden, we can fetch an API from Elysia server fully type-safe without code generation. 15 | --- 16 | 17 | # WebSocket 18 | Eden Treaty supports WebSocket using subscribe method. 19 | 20 | typescript twoslash 21 | import { Elysia, t } from "elysia"; 22 | import { treaty } from "@elysiajs/eden"; 23 | 24 | const app = new Elysia() 25 | .ws("/chat", { 26 | body: t.String(), 27 | response: t.String(), 28 | message(ws, message) { 29 | ws.send(message); 30 | }, 31 | }) 32 | .listen(3000); 33 | 34 | const api = treaty<typeof app>("localhost:3000"); 35 | 36 | const chat = api.chat.subscribe(); 37 | 38 | chat.subscribe((message) => { 39 | console.log("got", message); 40 | }); 41 | 42 | chat.on("open", () => { 43 | chat.send("hello from client"); 44 | }); 45 | 46 | 47 | .subscribe accepts the same parameter as get and head. 48 | 49 | ## Response 50 | Eden.subscribe returns EdenWS which extends the WebSocket results in identical syntax. 51 | 52 | If more control is need, EdenWebSocket.raw can be accessed to interact with the native WebSocket API. 53 |


/docs/index.md:

1 | --- 2 | title: Elysia - Ergonomic Framework for Humans 3 | layout: page 4 | sidebar: false 5 | head: 6 | - - meta 7 | - property: 'og:title' 8 | content: Elysia - Ergonomic Framework for Humans 9 | 10 | - - meta 11 | - name: 'description' 12 | content: Elysia is an ergonomic framework for Humans. With end-to-end type safety and great developer experience. Elysia is familiar, fast, and first class TypeScript support with well-thought integration between services whether it's tRPC, Swagger or WebSocket. Elysia got you covered, start building next generation TypeScript web servers today. 13 | 14 | - - meta 15 | - property: 'og:description' 16 | content: Elysia is an ergonomic framework for Humans. With end-to-end type safety and great developer experience. Elysia is familiar, fast, and first class TypeScript support with well-thought integration between services whether it's tRPC, Swagger or WebSocket. Elysia got you covered, start building next generation TypeScript web servers today. 17 | --- 18 | 19 | <script setup> 20 | import Fern from '../components/fern/fern.vue' 21 | </script> 22 | 23 | 24 | 25 | 26 | 27 | typescript twoslash 28 | // @noErrors 29 | import { Elysia } from 'elysia' 30 | 31 | new Elysia() 32 | .get('/id/:id', ({ params, set }) => { 33 | // ^? 34 | 35 | 36 | 37 | 38 | set.headers.a 39 | // ^| 40 | 41 | 42 | return 'Su' 43 | }) 44 | 45 | .get('/optional/:name?', ({ params: { name } }) => { 46 | // ^? 47 | return name ?? 'Pardofelis' 48 | }) 49 | .listen(3000) 50 | 51 | 52 | 53 | 54 | 55 | 56 | typescript twoslash 57 | import { Elysia, t } from 'elysia' 58 | 59 | new Elysia() 60 | .patch('/profile', ({ body }) => body.profile, { 61 | // ^? 62 | 63 | 64 | 65 | 66 | body: t.Object({ 67 | id: t.Number(), 68 | profile: t.File({ type: 'image' }) 69 | }) 70 | }) 71 | .listen(3000) 72 | 73 | 74 | 75 | 76 | 77 | 78 | typescript twoslash 79 | // @errors: 2345 80 | import { Elysia, t } from 'elysia' 81 | 82 | new Elysia() 83 | .get('/profile', ({ error }) => { 84 | if(Math.random() > .5) 85 | return error(418, 'Mika') 86 | 87 | return 'ok' 88 | }, { 89 | response: { 90 | 200: t.Literal('ok'), 91 | 418: t.Literal('Nagisa') 92 | } 93 | }) 94 | .listen(3000) 95 | 96 | 97 | 98 | 99 | 100 | 101 | typescript twoslash 102 | // @noErrors 103 | import { Elysia, t } from 'elysia' 104 | 105 | const role = new Elysia({ name: 'macro' }) 106 | .macro(({ onBeforeHandle }) => ({ 107 | role(type: 'user' | 'staff' | 'admin') { 108 | onBeforeHandle(({ headers, error }) => { 109 | if(headers.authorization !== type) 110 | return error(401) 111 | }) 112 | } 113 | })) 114 | 115 | new Elysia() 116 | .use(role) 117 | .get('/admin/check', 'ok', { 118 | r 119 | // ^| 120 | }) 121 | .listen(3000) 122 | 123 | 124 | 125 | 126 | 127 | 128 | typescript 129 | import { Elysia, file } from 'elysia' 130 | 131 | new Elysia() 132 | .get('/', 'Hello World') 133 | .get('/image', file('mika.webp')) 134 | .get('/stream', function* () { 135 | yield 'Hello' 136 | yield 'World' 137 | }) 138 | .ws('/realtime', { 139 | message(ws, message) { 140 | ws.send('got:' + message) 141 | } 142 | }) 143 | .listen(3000) 144 | 145 | 146 | 147 | 148 | 149 | 150 | typescript 151 | import { Elysia } from 'elysia' 152 | import swagger from '@elysiajs/swagger' 153 | 154 | new Elysia() 155 | .use(swagger()) 156 | .use(character) 157 | .use(auth) 158 | .listen(3000) 159 | 160 | 161 | 162 | 163 | 164 | 165 | typescript twoslash 166 | // @noErrors 167 | // @filename: server.ts 168 | import { Elysia, t } from 'elysia' 169 | 170 | const app = new Elysia() 171 | .patch( 172 | '/profile', 173 | ({ body, error }) => { 174 | if(body.age < 18) 175 | return error(400, "Oh no") 176 | 177 | return body 178 | }, 179 | { 180 | body: t.Object({ 181 | age: t.Number() 182 | }) 183 | } 184 | ) 185 | .listen(80) 186 | 187 | export type App = typeof app 188 | 189 | // @filename: client.ts 190 | // ---cut--- 191 | import { treaty } from '@elysiajs/eden' 192 | import type { App } from './server' 193 | 194 | const api = treaty<App>('api.elysiajs.com') 195 | 196 | const { data } = await api.profile.patch({ 197 | // ^? 198 | age: 21 199 | }) 200 | 201 | 202 | 203 | 204 | 205 | 206 | typescript twoslash 207 | // @errors: 2345 2304 208 | // @filename: index.ts 209 | import { Elysia, t } from 'elysia' 210 | 211 | export const app = new Elysia() 212 | .put( 213 | '/user', 214 | ({ body, error }) => { 215 | if(body.username === 'mika') 216 | return error(400, { 217 | success: false, 218 | message: 'Username already taken' 219 | } as const) 220 | 221 | return { 222 | success: true, 223 | message: 'User created' 224 | } as const 225 | }, 226 | { 227 | body: t.Object({ 228 | username: t.String(), 229 | password: t.String() 230 | }) 231 | } 232 | ) 233 | 234 | // @filename: client.ts 235 | // ---cut--- 236 | import { treaty } from '@elysiajs/eden' 237 | import { app } from './index' 238 | import { test, expect } from 'bun:test' 239 | 240 | const server = treaty(app) 241 | 242 | test('should handle duplicated user', async () => { 243 | const { error } = await server.user.put({ 244 | username: 'mika', 245 | }) 246 | 247 | expect(error?.value).toEqual({ 248 | success: false, 249 | message: 'Username already taken' 250 | }) 251 | }) 252 | 253 | 254 | 255 | 256 | 257 | 258 | bash 259 | $ bun test 260 | 261 | 262 | 263 | 264 | 265 |


/docs/integrations/astro.md:

1 | --- 2 | title: Integration with Astro - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Integration with Astro - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: You can run Elysia on Astro. Elysia will work normally as expected because of WinterCG compliance. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: You can run Elysia on Astro. Elysia will work normally as expected because of WinterCG compliance. 15 | --- 16 | 17 | # Integration with Astro 18 | 19 | With Astro Endpoint, we can run Elysia on Astro directly. 20 | 21 | 1. Set output to server in astro.config.mjs 22 | 23 | javascript 24 | // astro.config.mjs 25 | import { defineConfig } from 'astro/config' 26 | 27 | // https://astro.build/config 28 | export default defineConfig({ 29 | output: 'server' // [!code ++] 30 | }) 31 | 32 | 33 | 2. Create pages/[...slugs].ts 34 | 3. Create or import an existing Elysia server in [...slugs].ts 35 | 4. Export the handler with the name of method you want to expose 36 | 37 | typescript 38 | // pages/[...slugs].ts 39 | import { Elysia, t } from 'elysia' 40 | 41 | const app = new Elysia() 42 | .get('/api', () => 'hi') 43 | .post('/api', ({ body }) => body, { 44 | body: t.Object({ 45 | name: t.String() 46 | }) 47 | }) 48 | 49 | const handle = ({ request }: { request: Request }) => app.handle(request) // [!code ++] 50 | 51 | export const GET = handle // [!code ++] 52 | export const POST = handle // [!code ++] 53 | 54 | 55 | Elysia will work normally as expected because of WinterCG compliance. 56 | 57 | We recommended running Astro on Bun as Elysia is designed to be run on Bun 58 | 59 | ::: tip 60 | You can run Elysia server without running Astro on Bun thanks to WinterCG support. 61 | 62 | However some plugins like Elysia Static may not work if you are running Astro on Node. 63 | ::: 64 | 65 | 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. 66 | 67 | Please refer to Astro Endpoint for more information. 68 | 69 | ## Prefix 70 | 71 | If you place an Elysia server not in the root directory of the app router, you need to annotate the prefix to the Elysia server. 72 | 73 | For example, if you place Elysia server in pages/api/[...slugs].ts, you need to annotate prefix as /api to Elysia server. 74 | 75 | typescript 76 | // pages/api/[...slugs].ts 77 | import { Elysia, t } from 'elysia' 78 | 79 | const app = new Elysia({ prefix: '/api' }) // [!code ++] 80 | .get('/', () => 'hi') 81 | .post('/', ({ body }) => body, { 82 | body: t.Object({ 83 | name: t.String() 84 | }) 85 | }) 86 | 87 | const handle = ({ request }: { request: Request }) => app.handle(request) // [!code ++] 88 | 89 | export const GET = handle // [!code ++] 90 | export const POST = handle // [!code ++] 91 | 92 | 93 | This will ensure that Elysia routing will work properly in any location you place it. 94 |


/docs/integrations/cheat-sheet.md:

1 | --- 2 | title: Cheat Sheet (Elysia by example) - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Cheat Sheet (Elysia by example) - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia's cheat sheet in summary and how it work with "Elysia by example" 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia's cheat sheet in summary and how it work with "Elysia by example" 15 | --- 16 | 17 | # Cheat Sheet 18 | Here are a quick overview for a common Elysia patterns 19 | 20 | ## Hello World 21 | A simple hello world 22 | 23 | typescript 24 | import { Elysia } from 'elysia' 25 | 26 | new Elysia() 27 | .get('/', () => 'Hello World') 28 | .listen(3000) 29 | 30 | 31 | ## Custom HTTP Method 32 | Define route using custom HTTP methods/verbs 33 | 34 | See Route 35 | 36 | typescript 37 | import { Elysia } from 'elysia' 38 | 39 | new Elysia() 40 | .get('/hi', () => 'Hi') 41 | .post('/hi', () => 'From Post') 42 | .put('/hi', () => 'From Put') 43 | .route('M-SEARCH', '/hi', () => 'Custom Method') 44 | .listen(3000) 45 | 46 | 47 | ## Path Parameter 48 | Using dynamic path parameter 49 | 50 | See Path 51 | 52 | typescript 53 | import { Elysia } from 'elysia' 54 | 55 | new Elysia() 56 | .get('/id/:id', ({ params: { id } }) => id) 57 | .get('/rest/*', () => 'Rest') 58 | .listen(3000) 59 | 60 | 61 | ## Return JSON 62 | Elysia converts response to JSON automatically 63 | 64 | See Handler 65 | 66 | typescript 67 | import { Elysia } from 'elysia' 68 | 69 | new Elysia() 70 | .get('/json', () => { 71 | return { 72 | hello: 'Elysia' 73 | } 74 | }) 75 | .listen(3000) 76 | 77 | 78 | ## Return a file 79 | A file can be return in as formdata response 80 | 81 | The response must be a 1-level deep object 82 | 83 | typescript 84 | import { Elysia, file } from 'elysia' 85 | 86 | new Elysia() 87 | .get('/json', () => { 88 | return { 89 | hello: 'Elysia', 90 | image: file('public/cat.jpg') 91 | } 92 | }) 93 | .listen(3000) 94 | 95 | 96 | ## Header and status 97 | Set a custom header and a status code 98 | 99 | See Handler 100 | 101 | typescript 102 | import { Elysia } from 'elysia' 103 | 104 | new Elysia() 105 | .get('/', ({ set, error }) => { 106 | set.headers['x-powered-by'] = 'Elysia' 107 | 108 | return error(418, "I'm a teapot") 109 | }) 110 | .listen(3000) 111 | 112 | 113 | ## Group 114 | Define a prefix once for sub routes 115 | 116 | See Group 117 | 118 | typescript 119 | import { Elysia } from 'elysia' 120 | 121 | new Elysia() 122 | .get("/", () => "Hi") 123 | .group("/auth", app => { 124 | return app 125 | .get("/", () => "Hi") 126 | .post("/sign-in", ({ body }) => body) 127 | .put("/sign-up", ({ body }) => body) 128 | }) 129 | .listen(3000) 130 | 131 | 132 | ## Schema 133 | Enforce a data type of a route 134 | 135 | See Validation 136 | 137 | typescript 138 | import { Elysia, t } from 'elysia' 139 | 140 | new Elysia() 141 | .post('/mirror', ({ body: { username } }) => username, { 142 | body: t.Object({ 143 | username: t.String(), 144 | password: t.String() 145 | }) 146 | }) 147 | .listen(3000) 148 | 149 | 150 | ## Lifecycle Hook 151 | Intercept an Elysia event in order 152 | 153 | See Lifecycle 154 | 155 | typescript 156 | import { Elysia, t } from 'elysia' 157 | 158 | new Elysia() 159 | .onRequest(() => { 160 | console.log('On request') 161 | }) 162 | .on('beforeHandle', () => { 163 | console.log('Before handle') 164 | }) 165 | .post('/mirror', ({ body }) => body, { 166 | body: t.Object({ 167 | username: t.String(), 168 | password: t.String() 169 | }), 170 | afterHandle: () => { 171 | console.log("After handle") 172 | } 173 | }) 174 | .listen(3000) 175 | 176 | 177 | ## Guard 178 | Enforce a data type of sub routes 179 | 180 | See Scope 181 | 182 | typescript twoslash 183 | // @errors: 2345 184 | import { Elysia, t } from 'elysia' 185 | 186 | new Elysia() 187 | .guard({ 188 | response: t.String() 189 | }, (app) => app 190 | .get('/', () => 'Hi') 191 | // Invalid: will throws error, and TypeScript will report error 192 | .get('/invalid', () => 1) 193 | ) 194 | .listen(3000) 195 | 196 | 197 | ## Customize context 198 | Add custom variable to route context 199 | 200 | See Context 201 | 202 | typescript 203 | import { Elysia } from 'elysia' 204 | 205 | new Elysia() 206 | .state('version', 1) 207 | .decorate('getDate', () => Date.now()) 208 | .get('/version', ({ 209 | getDate, 210 | store: { version } 211 | }) => `${version} ${getDate()}`) 212 | .listen(3000) 213 | 214 | 215 | ## Redirect 216 | Redirect a response 217 | 218 | See Handler 219 | 220 | typescript 221 | import { Elysia } from 'elysia' 222 | 223 | new Elysia() 224 | .get('/', () => 'hi') 225 | .get('/redirect', ({ redirect }) => { 226 | return redirect('/') 227 | }) 228 | .listen(3000) 229 | 230 | 231 | ## Plugin 232 | Create a separate instance 233 | 234 | See Plugin 235 | 236 | typescript 237 | import { Elysia } from 'elysia' 238 | 239 | const plugin = new Elysia() 240 | .state('plugin-version', 1) 241 | .get('/hi', () => 'hi') 242 | 243 | new Elysia() 244 | .use(plugin) 245 | .get('/version', ({ store }) => store['plugin-version']) 246 | .listen(3000) 247 | 248 | 249 | ## Web Socket 250 | Create a realtime connection using Web Socket 251 | 252 | See Web Socket 253 | 254 | typescript 255 | import { Elysia } from 'elysia' 256 | 257 | new Elysia() 258 | .ws('/ping', { 259 | message(ws, message) { 260 | ws.send('hello ' + message) 261 | } 262 | }) 263 | .listen(3000) 264 | 265 | 266 | ## OpenAPI documentation 267 | Create interactive documentation using Scalar (or optionally Swagger) 268 | 269 | See swagger 270 | 271 | typescript 272 | import { Elysia } from 'elysia' 273 | import { swagger } from '@elysiajs/swagger' 274 | 275 | const app = new Elysia() 276 | .use(swagger()) 277 | .listen(3000) 278 | 279 | console.log(`View documentation at "${app.server!.url}swagger" in your browser`); 280 | 281 | 282 | ## Unit Test 283 | Write a unit test of your Elysia app 284 | 285 | See Unit Test 286 | 287 | typescript 288 | // test/index.test.ts 289 | import { describe, expect, it } from 'bun:test' 290 | import { Elysia } from 'elysia' 291 | 292 | describe('Elysia', () => { 293 | it('return a response', async () => { 294 | const app = new Elysia().get('/', () => 'hi') 295 | 296 | const response = await app 297 | .handle(new Request('http://localhost/')) 298 | .then((res) => res.text()) 299 | 300 | expect(response).toBe('hi') 301 | }) 302 | }) 303 | 304 | 305 | ## Custom body parser 306 | Create custom logic for parsing body 307 | 308 | See Parse 309 | 310 | typescript 311 | import { Elysia } from 'elysia' 312 | 313 | new Elysia() 314 | .onParse(({ request, contentType }) => { 315 | if (contentType === 'application/custom-type') 316 | return request.text() 317 | }) 318 | 319 | 320 | ## GraphQL 321 | Create a custom GraphQL server using GraphQL Yoga or Apollo 322 | 323 | See GraphQL Yoga 324 | 325 | typescript 326 | import { Elysia } from 'elysia' 327 | import { yoga } from '@elysiajs/graphql-yoga' 328 | 329 | const app = new Elysia() 330 | .use( 331 | yoga({ 332 | typeDefs: /* GraphQL */` 333 | type Query { 334 | hi: String 335 | } 336 | `, 337 | resolvers: { 338 | Query: { 339 | hi: () => 'Hello from Elysia' 340 | } 341 | } 342 | }) 343 | ) 344 | .listen(3000) 345 | 346 |


/docs/integrations/expo.md:

1 | --- 2 | title: Integration with Expo - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Integration with Expo - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: With Expo App Router, you can run Elysia on Expo route. Elysia will work normally as expected thanks to WinterCG compliance. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: With Expo App Router, you can run Elysia on Expo route. Elysia will work normally as expected thanks to WinterCG compliance. 15 | --- 16 | 17 | # Integration with Expo 18 | 19 | Starting from Expo SDK 50, and App Router v3, Expo allows us to create API route directly in an Expo app. 20 | 21 | 1. Create an Expo app if it doesn't exist with: 22 | typescript 23 | bun create expo-app --template tabs 24 | 25 | 26 | 2. Create app/[...slugs]+api.ts 27 | 3. In [...slugs]+api.ts, create or import an existing Elysia server 28 | 4. Export the handler with the name of method you want to expose 29 | 30 | typescript 31 | // app/[...slugs]+api.ts 32 | import { Elysia, t } from 'elysia' 33 | 34 | const app = new Elysia() 35 | .get('/', () => 'hello Next') 36 | .post('/', ({ body }) => body, { 37 | body: t.Object({ 38 | name: t.String() 39 | }) 40 | }) 41 | 42 | export const GET = app.handle // [!code ++] 43 | export const POST = app.handle // [!code ++] 44 | 45 | 46 | Elysia will work normally as expected because of WinterCG compliance, however, some plugins like Elysia Static may not work if you are running Expo on Node. 47 | 48 | You can treat the Elysia server as if normal Expo API route. 49 | 50 | 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 with both client-side and server action. 51 | 52 | Please refer to API route for more information. 53 | 54 | ## Prefix 55 | If you place an Elysia server not in the root directory of the app router, you need to annotate the prefix to the Elysia server. 56 | 57 | For example, if you place Elysia server in app/api/[...slugs]+api.ts, you need to annotate prefix as /api to Elysia server. 58 | 59 | typescript 60 | // app/api/[...slugs]+api.ts 61 | import { Elysia, t } from 'elysia' 62 | 63 | const app = new Elysia({ prefix: '/api' }) 64 | .get('/', () => 'hi') 65 | .post('/', ({ body }) => body, { 66 | body: t.Object({ 67 | name: t.String() 68 | }) 69 | }) 70 | 71 | export const GET = app.handle 72 | export const POST = app.handle 73 | 74 | 75 | This will ensure that Elysia routing will work properly in any location you place in. 76 | 77 | ## Deployment 78 | You can either directly use API route using Elysia and deploy as normal Elysia app normally if need or using experimental Expo server runtime. 79 | 80 | If you are using Expo server runtime, you may use expo export command to create optimized build for your expo app, this will include an Expo function which is using Elysia at dist/server/_expo/functions/[...slugs]+api.js 81 | 82 | ::: tip 83 | Please note that Expo Functions are treated as Edge functions instead of normal server, so running the Edge function directly will not allocate any port. 84 | ::: 85 | 86 | You may use the Expo function adapter provided by Expo to deploy your Edge Function. 87 | 88 | Currently Expo support the following adapter: 89 | - Express 90 | - Netlify 91 | - Vercel 92 |


/docs/integrations/nextjs.md:

1 | --- 2 | title: Integration with Nextjs - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Integration with Nextjs - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: With Nextjs App Router, you can run Elysia on Nextjs route. Elysia will work normally as expected because of WinterCG compliance. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: With Nextjs App Router, you can run Elysia on Nextjs route. Elysia will work normally as expected because of WinterCG compliance. 15 | --- 16 | 17 | # Integration with Nextjs 18 | 19 | With Nextjs App Router, we can run Elysia on Nextjs route. 20 | 21 | 1. Create api/[[...slugs]]/route.ts inside app router 22 | 2. In route.ts, create or import an existing Elysia server 23 | 3. Export the handler with the name of method you want to expose 24 | 25 | typescript 26 | // app/api/[[...slugs]]/route.ts 27 | import { Elysia, t } from 'elysia' 28 | 29 | const app = new Elysia({ prefix: '/api' }) 30 | .get('/', () => 'hello Next') 31 | .post('/', ({ body }) => body, { 32 | body: t.Object({ 33 | name: t.String() 34 | }) 35 | }) 36 | 37 | export const GET = app.handle // [!code ++] 38 | export const POST = app.handle // [!code ++] 39 | 40 | 41 | Elysia will work normally as expected because of WinterCG compliance, however, some plugins like Elysia Static may not work if you are running Nextjs on Node. 42 | 43 | You can treat the Elysia server as a normal Nextjs API route. 44 | 45 | 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 with both client-side and server action 46 | 47 | Please refer to Nextjs Route Handlers for more information. 48 | 49 | ## Prefix 50 | 51 | Because our Elysia server is not in the root directory of the app router, you need to annotate the prefix to the Elysia server. 52 | 53 | For example, if you place Elysia server in app/user/[[...slugs]]/route.ts, you need to annotate prefix as /user to Elysia server. 54 | 55 | typescript 56 | // app/api/[[...slugs]]/route.ts 57 | import { Elysia, t } from 'elysia' 58 | 59 | const app = new Elysia({ prefix: '/user' }) // [!code ++] 60 | .get('/', () => 'hi') 61 | .post('/', ({ body }) => body, { 62 | body: t.Object({ 63 | name: t.String() 64 | }) 65 | }) 66 | 67 | export const GET = app.handle 68 | export const POST = app.handle 69 | 70 | 71 | This will ensure that Elysia routing will work properly in any location you place it. 72 |


/docs/integrations/sveltekit.md:

1 | --- 2 | title: Integration with SvelteKit - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Integration with SvelteKit - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: With SvelteKit, you can run Elysia on server routes. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: With SvelteKit, you can run Elysia on server routes. 15 | --- 16 | 17 | # Integration with SvelteKit 18 | 19 | With SvelteKit, you can run Elysia on server routes. 20 | 21 | 1. Create src/routes/[...slugs]/+server.ts. 22 | 2. In +server.ts, create or import an existing Elysia server 23 | 3. Export the handler with the name of method you want to expose 24 | 25 | typescript 26 | // src/routes/[...slugs]/+server.ts 27 | import { Elysia, t } from 'elysia'; 28 | 29 | const app = new Elysia() 30 | .get('/', () => 'hello SvelteKit') 31 | .post('/', ({ body }) => body, { 32 | body: t.Object({ 33 | name: t.String() 34 | }) 35 | }) 36 | 37 | type RequestHandler = (v: { request: Request }) => Response | Promise<Response> 38 | 39 | export const GET: RequestHandler = ({ request }) => app.handle(request) 40 | export const POST: RequestHandler = ({ request }) => app.handle(request) 41 | 42 | 43 | You can treat the Elysia server as a normal SvelteKit server route. 44 | 45 | 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 with both client-side and server action 46 | 47 | Please refer to SvelteKit Routing for more information. 48 | 49 | ## Prefix 50 | If you place an Elysia server not in the root directory of the app router, you need to annotate the prefix to the Elysia server. 51 | 52 | For example, if you place Elysia server in src/routes/api/[...slugs]/+server.ts, you need to annotate prefix as /api to Elysia server. 53 | 54 | typescript twoslash 55 | // src/routes/api/[...slugs]/+server.ts 56 | import { Elysia, t } from 'elysia'; 57 | 58 | const app = new Elysia({ prefix: '/api' }) // [!code ++] 59 | .get('/', () => 'hi') 60 | .post('/', ({ body }) => body, { 61 | body: t.Object({ 62 | name: t.String() 63 | }) 64 | }) 65 | 66 | type RequestHandler = (v: { request: Request }) => Response | Promise<Response> 67 | 68 | export const GET: RequestHandler = ({ request }) => app.handle(request) 69 | export const POST: RequestHandler = ({ request }) => app.handle(request) 70 | 71 | 72 | This will ensure that Elysia routing will work properly in any location you place it. 73 |


/docs/key-concept.md:

1 | --- 2 | title: Key Concept - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Key Concept - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Although Elysia is a simple library, it has some key concepts that you need to understand to use it effectively. This page will guide you through the key concepts of ElysiaJS. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Although Elysia is a simple library, it has some key concepts that you need to understand to use it effectively. This page will guide you through the key concepts of ElysiaJS. 15 | --- 16 | 17 | # Key Concept 18 | 19 | ### We highly recommended you to read this page before start using Elysia. 20 | 21 | Although Elysia is a simple library, it has some key concepts that you need to understand to use it effectively. 22 | 23 | This page covers most important concepts of Elysia that you should to know. 24 | 25 | ## Everything is a component 26 | 27 | Every Elysia instance is a component. 28 | 29 | A component is a plugin that could plug into other instances. 30 | 31 | It could be a router, a store, a service, or anything else. 32 | 33 | ts twoslash 34 | import { Elysia } from 'elysia' 35 | 36 | const store = new Elysia() 37 | .state({ visitor: 0 }) 38 | 39 | const router = new Elysia() 40 | .use(store) 41 | .get('/increase', ({ store }) => store.visitor++) 42 | 43 | const app = new Elysia() 44 | .use(router) 45 | .get('/', ({ store }) => store) 46 | .listen(3000) 47 | 48 | 49 | This force you to break down your application into small pieces, making it to add or remove features easily. 50 | 51 | Learn more about this in plugin. 52 | 53 | ## Scope 54 | By default, event/life-cycle in each instance is isolated from each other. 55 | 56 | ts twoslash 57 | // @errors: 2339 58 | import { Elysia } from 'elysia' 59 | 60 | const ip = new Elysia() 61 | .derive(({ server, request }) => ({ 62 | ip: server?.requestIP(request) 63 | })) 64 | .get('/ip', ({ ip }) => ip) 65 | 66 | const server = new Elysia() 67 | .use(ip) 68 | .get('/ip', ({ ip }) => ip) 69 | .listen(3000) 70 | 71 | 72 | In this example, the ip property is only share in it's own instance but not in the server instance. 73 | 74 | To share the lifecycle, in our case, an ip property with server instance, we need to explicitly says it could be shared. 75 | 76 | ts twoslash 77 | import { Elysia } from 'elysia' 78 | 79 | const ip = new Elysia() 80 | .derive( 81 | { as: 'global' }, // [!code ++] 82 | ({ server, request }) => ({ 83 | ip: server?.requestIP(request) 84 | }) 85 | ) 86 | .get('/ip', ({ ip }) => ip) 87 | 88 | const server = new Elysia() 89 | .use(ip) 90 | .get('/ip', ({ ip }) => ip) 91 | .listen(3000) 92 | 93 | 94 | In this example, ip property is shared between ip and server instance because we define it as global. 95 | 96 | This force you to think about the scope of each property preventing you from accidentally sharing the property between instances. 97 | 98 | Learn more about this in scope. 99 | 100 | ## Method Chaining 101 | Elysia code should always use method chaining. 102 | 103 | As Elysia type system is complex, every methods in Elysia returns a new type reference. 104 | 105 | This is important to ensure type integrity and inference. 106 | 107 | typescript twoslash 108 | import { Elysia } from 'elysia' 109 | 110 | new Elysia() 111 | .state('build', 1) 112 | // Store is strictly typed // [!code ++] 113 | .get('/', ({ store: { build } }) => build) 114 | // ^? 115 | .listen(3000) 116 | 117 | 118 | In the code above state returns a new ElysiaInstance type, adding a build type. 119 | 120 | ### ❌ Don't: Use Elysia without method chaining 121 | Without using method chaining, Elysia doesn't save these new types, leading to no type inference. 122 | 123 | typescript twoslash 124 | // @errors: 2339 125 | import { Elysia } from 'elysia' 126 | 127 | const app = new Elysia() 128 | 129 | app.state('build', 1) 130 | 131 | app.get('/', ({ store: { build } }) => build) 132 | 133 | app.listen(3000) 134 | 135 | 136 | We recommend to always use method chaining to provide an accurate type inference. 137 | 138 | ## Dependency 139 | By default, each instance will be re-execute everytime it's applied to another instance. 140 | 141 | This can cause a duplication of the same method being applied multiple times but some methods should be called once like lifecycle or routes. 142 | 143 | To prevent lifecycle methods from being duplicated, we can add an unique identifier to the instance. 144 | 145 | ts twoslash 146 | import { Elysia } from 'elysia' 147 | 148 | const ip = new Elysia({ name: 'ip' }) 149 | .derive( 150 | { as: 'global' }, 151 | ({ server, request }) => ({ 152 | ip: server?.requestIP(request) 153 | }) 154 | ) 155 | .get('/ip', ({ ip }) => ip) 156 | 157 | const router1 = new Elysia() 158 | .use(ip) 159 | .get('/ip-1', ({ ip }) => ip) 160 | 161 | const router2 = new Elysia() 162 | .use(ip) 163 | .get('/ip-2', ({ ip }) => ip) 164 | 165 | const server = new Elysia() 166 | .use(router1) 167 | .use(router2) 168 | 169 | 170 | This will prevent the ip property from being call multiple time by applying deduplication using an unique name. 171 | 172 | Once name is provided, the instance will become a singleton. Allowing Elysia to apply plugin deduplication. 173 | 174 | Allowing us to reuse the same instance multiple time without performance penalty. 175 | 176 | This force you to think about the dependency of each instance, allowing for easily applying migration or refactoring. 177 | 178 | Learn more about this in plugin deduplication. 179 | 180 | ## Type Inference 181 | Elysia has a complex type system that allows you to infer types from the instance. 182 | 183 | ts twoslash 184 | import { Elysia, t } from 'elysia' 185 | 186 | const app = new Elysia() 187 | .post('/', ({ body }) => body, { 188 | // ^? 189 | 190 | 191 | 192 | 193 | body: t.Object({ 194 | name: t.String() 195 | }) 196 | }) 197 | 198 | 199 | If possible, always use an inline function to provide an accurate type inference. 200 | 201 | If you need to apply a separate function, eg. MVC's controller pattern. It's recommended to destructure properties from inline function to prevent unnecessary type inference. 202 | 203 | ts twoslash 204 | import { Elysia, t } from 'elysia' 205 | 206 | abstract class Controller { 207 | static greet({ name }: { name: string }) { 208 | return 'hello ' + name 209 | } 210 | } 211 | 212 | const app = new Elysia() 213 | .post('/', ({ body }) => Controller.greet(body), { 214 | body: t.Object({ 215 | name: t.String() 216 | }) 217 | }) 218 | 219 | 220 | Learn more about this in Best practice: MVC Controller. 221 |


/docs/midori.md:

1 | --- 2 | title: Elysia - Ergonomic Framework for Humans 3 | layout: page 4 | sidebar: false 5 | head: 6 | - - meta 7 | - property: 'og:title' 8 | content: Elysia - Ergonomic Framework for Humans 9 | 10 | - - meta 11 | - name: 'description' 12 | content: Elysia is an ergonomic framework for Humans. With end-to-end type safety and great developer experience. Elysia is familiar, fast, and first class TypeScript support with well-thought integration between services whether it's tRPC, Swagger or WebSocket. Elysia got you covered, start building next generation TypeScript web servers today. 13 | 14 | - - meta 15 | - property: 'og:description' 16 | content: Elysia is an ergonomic framework for Humans. With end-to-end type safety and great developer experience. Elysia is familiar, fast, and first class TypeScript support with well-thought integration between services whether it's tRPC, Swagger or WebSocket. Elysia got you covered, start building next generation TypeScript web servers today. 17 | --- 18 | 19 | <script setup> 20 | import Landing from '../components/midori/index.vue' 21 | </script> 22 | 23 | 24 | 25 |
26 | typescript twoslash 27 | import { Elysia } from 'elysia' 28 | 29 | new Elysia() 30 | .get('/', 'Hello World') 31 | .get('/json', { 32 | hello: 'world' 33 | }) 34 | .get('/id/:id', ({ params: { id } }) => id) 35 | .listen(3000) 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | typescript twoslash 44 | import { Elysia, t } from 'elysia' 45 | 46 | new Elysia() 47 | .post( 48 | '/profile', 49 | // ↓ hover me ↓ 50 | ({ body }) => body, 51 | { 52 | body: t.Object({ 53 | username: t.String() 54 | }) 55 | } 56 | ) 57 | .listen(3000) 58 | 59 | 60 | 61 | 62 | 63 | 64 | ts twoslash 65 | // @filename: controllers.ts 66 | import { Elysia } from 'elysia' 67 | 68 | export const users = new Elysia() 69 | .get('/users', 'Dreamy Euphony') 70 | 71 | export const feed = new Elysia() 72 | .get('/feed', ['Hoshino', 'Griseo', 'Astro']) 73 | 74 | // @filename: server.ts 75 | // ---cut--- 76 | import { Elysia, t } from 'elysia' 77 | import { swagger } from '@elysiajs/swagger' 78 | import { users, feed } from './controllers' 79 | 80 | new Elysia() 81 | .use(swagger()) 82 | .use(users) 83 | .use(feed) 84 | .listen(3000) 85 | 86 | 87 | 88 | 89 | 90 | typescript twoslash 91 | // @filename: server.ts 92 | // ---cut--- 93 | // server.ts 94 | import { Elysia, t } from 'elysia' 95 | 96 | const app = new Elysia() 97 | .patch( 98 | '/user/profile', 99 | ({ body, error }) => { 100 | if(body.age < 18) 101 | return error(400, "Oh no") 102 | 103 | if(body.name === 'Nagisa') 104 | return error(418) 105 | 106 | return body 107 | }, 108 | { 109 | body: t.Object({ 110 | name: t.String(), 111 | age: t.Number() 112 | }) 113 | } 114 | ) 115 | .listen(80) 116 | 117 | export type App = typeof app 118 | 119 | 120 | 121 | 122 | 123 | typescript twoslash 124 | // @errors: 2322 1003 125 | // @filename: server.ts 126 | import { Elysia, t } from 'elysia' 127 | 128 | const app = new Elysia() 129 | .patch( 130 | '/user/profile', 131 | ({ body, error }) => { 132 | if(body.age < 18) 133 | return error(400, "Oh no") 134 | 135 | if(body.name === 'Nagisa') 136 | return error(418) 137 | 138 | return body 139 | }, 140 | { 141 | body: t.Object({ 142 | name: t.String(), 143 | age: t.Number() 144 | }) 145 | } 146 | ) 147 | .listen(80) 148 | 149 | export type App = typeof app 150 | 151 | // @filename: client.ts 152 | // ---cut--- 153 | // client.ts 154 | import { treaty } from '@elysiajs/eden' 155 | import type { App } from './server' 156 | 157 | const api = treaty<App>('localhost') 158 | 159 | const { data, error } = await api.user.profile.patch({ 160 | name: 'saltyaom', 161 | age: '21' 162 | }) 163 | 164 | if(error) 165 | switch(error.status) { 166 | case 400: 167 | throw error.value 168 | // ^? 169 | 170 | case 418: 171 | throw error.value 172 | // ^? 173 | } 174 | 175 | data 176 | // ^? 177 | 178 | 179 | 180 | 181 | 182 |


/docs/patterns/configuration.md:

1 | --- 2 | title: Configuration - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Configuration - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia can be configured by passing an object to the constructor. We can configure Elysia behavior by passing an object to the constructor. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia can be configured by passing an object to the constructor. We can configure Elysia behavior by passing an object to the constructor. 15 | --- 16 | 17 | # Configuration 18 | 19 | We can configure Elysia behavior by passing an object to the constructor. 20 | 21 | ts 22 | import { Elysia } from 'elysia' 23 | 24 | new Elysia({ 25 | prefix: '/group' 26 | }) 27 | 28 | 29 | Above is an example of configuring Elysia to use /group as the path prefix. 30 | 31 | ## Max body size 32 | We can set the maximum body size by setting serve.maxRequestBodySize in the serve configuration. 33 | 34 | ts 35 | import { Elysia } from 'elysia' 36 | 37 | new Elysia({ 38 | serve: { 39 | maxRequestBodySize: 1024 * 1024 * 256 // 256MB 40 | } 41 | }) 42 | 43 | 44 | By default the maximum request body size is 128MB (1024 * 1024 * 128). 45 | 46 | ## TLS 47 | We can enable TLS (known as successor of SSL) by passing in a value for key and cert; both are required to enable TLS. 48 | 49 | ts 50 | import { Elysia, file } from 'elysia' 51 | 52 | new Elysia({ 53 | serve: { 54 | tls: { 55 | cert: file('cert.pem'), 56 | key: file('key.pem') 57 | } 58 | } 59 | }) 60 | 61 | 62 | Elysia extends Bun configuration which supports TLS out of the box, powered by BoringSSL. 63 | 64 | See TLS Options for available configuration. 65 | 66 | ## Config 67 | Below is a config accepted by Elysia: 68 | 69 | ### prefix 70 | @default "" 71 | 72 | Path prefix of the instance 73 | 74 | ### name 75 | Name of the instance for debugging, and plugin deduplication purpose 76 | 77 | ### seed 78 | Seed for generating checksum for plugin deduplication 79 | 80 | ### detail 81 | OpenAPI documentation for documentation generation 82 | 83 | This configuration extends Swagger Specification. 84 | 85 | @see Swagger Specification 86 | 87 | ### tags 88 | OpenAPI tags for documentation generation 89 | 90 | Decorate all instance routes with tags 91 | 92 | This configuration extends Tag Object 93 | 94 | @see Tag Object 95 | 96 | ### precompile 97 | @default false 98 | 99 | Warm up Elysia before starting the server 100 | 101 | This will perform Ahead of Time compilation and generate code for route handlers 102 | 103 | If set to false, Elysia will perform Just in Time compilation 104 | 105 | Only required for root instance (instance which use listen) to effect 106 | 107 | ### aot 108 | @default false 109 | 110 | Ahead of Time compliation 111 | 112 | Reduced performance but faster startup time 113 | 114 | ## strictPath 115 | @default false 116 | 117 | Whether should Elysia tolerate suffix / or vice-versa 118 | 119 | #### Example 120 | If strictPath is set to true, Elysia will match /id and not /id/ 121 | 122 | ts 123 | import { Elysia } from 'elysia' 124 | 125 | new Elysia({ 126 | strictPath: true 127 | }) 128 | .get('/id', 'work') 129 | 130 | 131 | Normally, both /id and /id/ will match the route handler (default is false) 132 | 133 | ## cookie 134 | Set default cookie options 135 | 136 | ## normalize 137 | @default true 138 | 139 | If enabled, the handlers will run a clean on incoming and outgoing bodies instead of failing directly. 140 | 141 | This allows for sending unknown or disallowed properties in the bodies. These will simply be filtered out instead of failing the request. 142 | 143 | This has no effect when the schemas allow additional properties. 144 | 145 | Since this uses dynamic schema it may have an impact on performance. 146 | 147 | ## nativeStaticResponse 148 | @default true 149 | @since 1.1.11 150 | 151 | Enable Bun static response. 152 | 153 | Whether Elysia should use Bun's static response. 154 | 155 | This allows Elysia to improve static content performance and reduce memory significantly. 156 | 157 | #### Example 158 | Elysia will use Bun's static response for static content 159 | ts 160 | import { Elysia } from 'elysia' 161 | 162 | new Elysia() 163 | .get('/static', 'work') 164 | 165 | 166 | Above is an equivalent to: 167 | ts 168 | Bun.serve({ 169 | static: { 170 | 'static': 'work', 171 | '/static': '/work' 172 | } 173 | }) 174 | 175 | 176 | ::: tip 177 | This configuration will only work if using Bun > 1.1.27 as the server 178 | ::: 179 | 180 | ## websocket 181 | Override websocket configuration 182 | 183 | Recommended to leave this as default as Elysia will generate suitable configuration for handling WebSocket automatically 184 | 185 | This configuration extends Bun WebSocket API 186 | 187 | ## serve 188 | Bun serve configuration. 189 | 190 | ts 191 | import { Elysia } from 'elysia' 192 | 193 | new Elysia({ 194 | serve: { 195 | hostname: 'elysiajs.com', 196 | tls: { 197 | cert: Bun.file('cert.pem'), 198 | key: Bun.file('key.pem') 199 | } 200 | }, 201 | }) 202 | 203 | 204 | This configuration extends Bun Serve API and Bun TLS 205 | 206 | The following is ported from Bun JSDoc and Bun documentation: 207 | 208 | ### port 209 | @default 3000 210 | 211 | Port to listen on 212 | 213 | ### unix 214 | If set, the HTTP server will listen on a unix socket instead of a port. 215 | 216 | (Cannot be used with hostname+port) 217 | 218 | ### hostname 219 | @default 0.0.0.0 220 | 221 | Set the hostname which the server listens on 222 | 223 | ### maxRequestBodySize 224 | @default 1024 * 1024 * 128 (128MB) 225 | 226 | Set the maximum size of a request body (in bytes) 227 | 228 | ### reusePort 229 | @default true 230 | 231 | If the SO_REUSEPORT flag should be set 232 | 233 | This allows multiple processes to bind to the same port, which is useful for load balancing 234 | 235 | This configuration is override and turns on by default by Elysia 236 | 237 | ### id 238 | Uniquely identify a server instance with an ID 239 | 240 | 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. 241 | 242 | ### rejectUnauthorized 243 | @default NODE_TLS_REJECT_UNAUTHORIZED environment variable 244 | 245 | If set to false, any certificate is accepted. 246 | 247 | ## TLS options 248 | This configuration extends Bun TLS API 249 | 250 | ts 251 | import { Elysia } from 'elysia' 252 | 253 | new Elysia({ 254 | tls: { 255 | cert: Bun.file('cert.pem'), 256 | key: Bun.file('key.pem') 257 | } 258 | }) 259 | 260 | 261 | ### cert 262 | Cert chains in PEM format. One cert chain should be provided per private key. 263 | 264 | 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 265 | including the root CA (the root CA must be pre-known to the peer, see ca). 266 | 267 | When providing multiple cert chains, they do not have to be in the same order as their private keys in key. 268 | 269 | If the intermediate certificates are not provided, the peer will not be 270 | able to validate the certificate, and the handshake will fail. 271 | 272 | ### key 273 | Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. 274 | 275 | Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form . 276 | 277 | The object form can only occur in an array. 278 | 279 | object.passphrase is optional. Encrypted keys will be decrypted with 280 | 281 | object.passphrase if provided, or options.passphrase if it is not. 282 | 283 | ### ca 284 | Optionally override the trusted CA certificates. Default is to trust the well-known CAs curated by Mozilla. 285 | 286 | Mozilla's CAs are completely replaced when CAs are explicitly specified using this option. 287 | 288 | ### passphrase 289 | Shared passphrase for a single private key and/or a PFX. 290 | 291 | ### dhParamsFile 292 | File path to a .pem file custom Diffie Helman parameters 293 | 294 | ### requestCert 295 | @default false 296 | 297 | If set to true, the server will request a client certificate. 298 | 299 | ### secureOptions 300 | Optionally affect the OpenSSL protocol behavior, which is not usually necessary. 301 | 302 | This should be used carefully if at all! 303 | 304 | Value is a numeric bitmask of the SSL_OP_* options from OpenSSL Options 305 | 306 | ### serverName 307 | Explicitly set a server name 308 | 309 | ### lowMemoryMode 310 | @default false 311 | 312 | This sets OPENSSL_RELEASE_BUFFERS to 1. 313 | 314 | It reduces overall performance but saves some memory. 315 |


/docs/patterns/deployment.md:

1 | --- 2 | title: Production Deployment - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Production Deployment - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: This page 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia can be configured by passing an object to the constructor. We can configure Elysia behavior by passing an object to the constructor. 15 | --- 16 | 17 | # Deploy to production 18 | This page is a guide on how to deploy Elysia to production. 19 | 20 | ## Compile to binary 21 | We recommended running a build command before deploying to produciton as it could potentially reduce memory usage and file size significantly. 22 | 23 | We recommended compile Elysia into a single binary using the command as follows: 24 | bash 25 | bun build \ 26 | --compile \ 27 | --minify-whitespace \ 28 | --minify-syntax \ 29 | --target bun \ 30 | --outfile server \ 31 | ./src/index.ts 32 | 33 | 34 | This will generate a portable binary server which we can run to start our server. 35 | 36 | Compiling server to binary usually significantly reduce memory usage by 2-3x compared to development environment. 37 | 38 | This command is a bit long, so let's break it down: 39 | 1. --compile - Compile TypeScript to binary 40 | 2. --minify-whitespace - Remove unnecessary whitespace 41 | 3. --minify-syntax - Minify JavaScript syntax to reduce file size 42 | 4. --target bun - Target the bun platform, this can optimize the binary for the target platform 43 | 5. --outfile server - Output the binary as server 44 | 6. ./src/index.ts - The entry file of our server (codebase) 45 | 46 | To start our server, simly run the binary. 47 | bash 48 | ./server 49 | 50 | 51 | Once binary is compiled, you don't need Bun installed on the machine to run the server. 52 | 53 | This is great as the deployment server doesn't need to install an extra runtime to run making binary portable. 54 | 55 | ### Why not --minify 56 | Bun does have --minify flag that will minify the binary. 57 | 58 | However if we are using OpenTelemetry, it's going to reduce a function name to a single character. 59 | 60 | This make tracing harder than it should as OpenTelemetry rely on a function name. 61 | 62 | However, if you're not using OpenTelemetry, you may opt in for --minify instead 63 | bash 64 | bun build \ 65 | --compile \ 66 | --minify \ 67 | --target bun \ 68 | --outfile server \ 69 | ./src/index.ts 70 | 71 | 72 | ### Permission 73 | Some Linux distro might not be able to run the binary, we suggest enable executable permission to a binary if you're on Linux: 74 | bash 75 | chmod +x ./server 76 | 77 | ./server 78 | 79 | 80 | ### Unknown random Chinese error 81 | If you're trying to deploy a binary to your server but unable to run with random chinese character error. 82 | 83 | It means that the machine you're running on doesn't support AVX2. 84 | 85 | Unfortunately, Bun require machine that has an AVX2 hardware support. 86 | 87 | There's no workaround as far as we know. 88 | 89 | ## Compile to JavaScript 90 | If you are unable to compile to binary or you are deploying on a Window server. 91 | 92 | You may bundle your server to a JavaScript file instead. 93 | 94 | bash 95 | bun build \ 96 | --compile \ // [!code --] 97 | --minify-whitespace \ 98 | --minify-syntax \ 99 | --target bun \ 100 | --outfile ./dist/index.js \ 101 | ./src/index.ts 102 | 103 | 104 | This will generate a single portable JavaScript file that you can deploy on your server. 105 | bash 106 | NODE_ENV=production bun ./dist/index.js 107 | 108 | 109 | ## Docker 110 | On Docker, we recommended to always compile to binary to reduce base image overhead. 111 | 112 | Here's an example image using Distroless image using binary. 113 | dockerfile [Dockerfile] 114 | FROM oven/bun AS build 115 | 116 | WORKDIR /app 117 | 118 | # Cache packages installation 119 | COPY package.json package.json 120 | COPY bun.lockb bun.lockb 121 | 122 | RUN bun install 123 | 124 | COPY ./src ./src 125 | 126 | ENV NODE_ENV=production 127 | 128 | RUN bun build \ 129 | --compile \ 130 | --minify-whitespace \ 131 | --minify-syntax \ 132 | --target bun \ 133 | --outfile server \ 134 | ./src/index.ts 135 | 136 | FROM gcr.io/distroless/base 137 | 138 | WORKDIR /app 139 | 140 | COPY --from=build /app/server server 141 | 142 | ENV NODE_ENV=production 143 | 144 | CMD ["./server"] 145 | 146 | EXPOSE 3000 147 | 148 | 149 | ### Monorepo 150 | If you are using Elysia with Monorepo, you may need to include dependent packages. 151 | 152 | If you are using Turborepo, you may place a Dockerfile inside an your apps directory like apps/server/Dockerfile. This may apply to other monorepo manager such as Lerna, etc. 153 | 154 | Assume that our monorepo are using Turborepo with structure as follows: 155 | - apps 156 | - server 157 | - Dockerfile (place a Dockerfile here) 158 | - packages 159 | - config 160 | 161 | Then we can build our Dockerfile on monorepo root (not app root): 162 | bash 163 | docker build -t elysia-mono . 164 | 165 | 166 | With Dockerfile as follows: 167 | dockerfile [apps/server/Dockerfile] 168 | FROM oven/bun:1 AS build 169 | 170 | WORKDIR /app 171 | 172 | # Cache packages 173 | COPY package.json package.json 174 | COPY bun.lockb bun.lockb 175 | 176 | COPY /apps/server/package.json ./apps/server/package.json 177 | COPY /packages/config/package.json ./packages/config/package.json 178 | 179 | RUN bun install 180 | 181 | COPY /apps/server ./apps/server 182 | COPY /packages/config ./packages/config 183 | 184 | ENV NODE_ENV=production 185 | 186 | RUN bun build \ 187 | --compile \ 188 | --minify-whitespace \ 189 | --minify-syntax \ 190 | --target bun \ 191 | --outfile server \ 192 | ./src/index.ts 193 | 194 | FROM gcr.io/distroless/base 195 | 196 | WORKDIR /app 197 | 198 | COPY --from=build /app/server server 199 | 200 | ENV NODE_ENV=production 201 | 202 | CMD ["./server"] 203 | 204 | EXPOSE 3000 205 | 206 | 207 | ## Railway 208 | Railway is one of the popular deployment platform. 209 | 210 | Railway assign random port to expose for each deployment that can be access via PORT environment variable. 211 | 212 | We need to modify our Elysia server to accept PORT environment to comply with Railway port. 213 | 214 | Instead of a fixed port, we may use process.env.PORT and provide a fallback on development instead. 215 | ts 216 | new Elysia() 217 | .listen(3000) // [!code --] 218 | .listen(process.env.PORT ?? 3000) // [!code ++] 219 | 220 | 221 | This should allows Elysia to intercept port provided by Railway. 222 | 223 | ::: tip 224 | Elysia assign hostname to 0.0.0.0 automatically, which works with Railway 225 | ::: 226 |


/docs/patterns/macro.md:

1 | --- 2 | title: Macro - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Macro - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Macro allows us to define a custom field to the hook. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Macro allows us to define a custom field to the hook. 15 | --- 16 | 17 | # Macro 18 | 19 | <script setup> 20 | import Tab from '../../components/fern/tab.vue' 21 | </script> 22 | 23 | Macro allows us to define a custom field to the hook. 24 | 25 | <Tab 26 | id="macro" 27 | :names="['Macro v2', 'Macro v1']" 28 | :tabs="['macro2', 'macro1']" 29 | > 30 | 31 | 32 | 33 | Macro v1 use functional callback with event listener function. 34 | 35 | Elysia.macro allows us to compose custom heavy logic into a simple configuration available in hook, and guard with full type safety. 36 | 37 | typescript twoslash 38 | import { Elysia } from 'elysia' 39 | 40 | const plugin = new Elysia({ name: 'plugin' }) 41 | .macro(({ onBeforeHandle }) => ({ 42 | hi(word: string) { 43 | onBeforeHandle(() => { 44 | console.log(word) 45 | }) 46 | } 47 | })) 48 | 49 | const app = new Elysia() 50 | .use(plugin) 51 | .get('/', () => 'hi', { 52 | hi: 'Elysia' 53 | }) 54 | 55 | 56 | Accessing the path should log "Elysia" as the results. 57 | 58 | ### API 59 | 60 | macro should return an object, each key is reflected to the hook, and the provided value inside the hook will be sent back as the first parameter. 61 | 62 | In previous example, we create hi accepting a string. 63 | 64 | We then assigned hi to "Elysia", the value was then sent back to the hi function, and then the function added a new event to beforeHandle stack. 65 | 66 | Which is an equivalent of pushing function to beforeHandle as the following: 67 | 68 | typescript 69 | import { Elysia } from 'elysia' 70 | 71 | const app = new Elysia() 72 | .get('/', () => 'hi', { 73 | beforeHandle() { 74 | console.log('Elysia') 75 | } 76 | }) 77 | 78 | 79 | macro shine when a logic is more complex than accepting a new function, for example creating an authorization layer for each route. 80 | 81 | typescript twoslash 82 | // @filename: auth.ts 83 | import { Elysia } from 'elysia' 84 | 85 | export const auth = new Elysia() 86 | .macro(() => { 87 | return { 88 | isAuth(isAuth: boolean) {}, 89 | role(role: 'user' | 'admin') {}, 90 | } 91 | }) 92 | 93 | // @filename: index.ts 94 | // ---cut--- 95 | import { Elysia } from 'elysia' 96 | import { auth } from './auth' 97 | 98 | const app = new Elysia() 99 | .use(auth) 100 | .get('/', () => 'hi', { 101 | isAuth: true, 102 | role: 'admin' 103 | }) 104 | 105 | 106 | The field can accept anything ranging from string to function, allowing us to create a custom life cycle event. 107 | 108 | macro will be executed in order from top-to-bottom according to definition in hook, ensure that the stack should be handle in correct order. 109 | 110 | ### Parameters 111 | 112 | Elysia.macro parameters to interact with the life cycle event as the following: 113 | 114 | - onParse 115 | - onTransform 116 | - onBeforeHandle 117 | - onAfterHandle 118 | - onError 119 | - onResponse 120 | - events - Life cycle store 121 | - global: Life cycle of a global stack 122 | - local: Life cycle of an inline hook (route) 123 | 124 | Parameters start with on is a function to appends function into a life cycle stack. 125 | 126 | While events is an actual stack that stores an order of the life-cycle event. You may mutate the stack directly or using the helper function provided by Elysia. 127 | 128 | ### Options 129 | 130 | The life cycle function of an extension API accepts additional options to ensure control over life cycle events. 131 | 132 | - options (optional) - determine which stack 133 | - function - function to execute on the event 134 | 135 | typescript 136 | import { Elysia } from 'elysia' 137 | 138 | const plugin = new Elysia({ name: 'plugin' }) 139 | .macro(({ onBeforeHandle }) => { 140 | return { 141 | hi(word: string) { 142 | onBeforeHandle( 143 | { insert: 'before' }, // [!code ++] 144 | () => { 145 | console.log(word) 146 | } 147 | ) 148 | } 149 | } 150 | }) 151 | 152 | 153 | Options may accept the following parameter: 154 | 155 | - insert 156 | - Where should the function be added 157 | - value: 'before' | 'after' 158 | - @default: 'after' 159 | - stack 160 | - Determine which type of stack should be added 161 | - value: 'global' | 'local' 162 | - @default: 'local' 163 | 164 | 165 | 166 | 167 | 168 | Macro v2 use an object syntax with return lifecycle like inline hook. 169 | 170 | Elysia.macro allows us to compose custom heavy logic into a simple configuration available in hook, and guard with full type safety. 171 | 172 | typescript twoslash 173 | import { Elysia } from 'elysia' 174 | 175 | const plugin = new Elysia({ name: 'plugin' }) 176 | .macro({ 177 | hi(word: string) { 178 | return { 179 | beforeHandle() { 180 | console.log(word) 181 | } 182 | } 183 | } 184 | }) 185 | 186 | const app = new Elysia() 187 | .use(plugin) 188 | .get('/', () => 'hi', { 189 | hi: 'Elysia' 190 | }) 191 | 192 | 193 | Accessing the path should log "Elysia" as the results. 194 | 195 | ### API 196 | 197 | macro has the same API as hook. 198 | 199 | In previous example, we create a hi macro accepting a string. 200 | 201 | We then assigned hi to "Elysia", the value was then sent back to the hi function, and then the function added a new event to beforeHandle stack. 202 | 203 | Which is an equivalent of pushing function to beforeHandle as the following: 204 | 205 | typescript 206 | import { Elysia } from 'elysia' 207 | 208 | const app = new Elysia() 209 | .get('/', () => 'hi', { 210 | beforeHandle() { 211 | console.log('Elysia') 212 | } 213 | }) 214 | 215 | 216 | macro shine when a logic is more complex than accepting a new function, for example creating an authorization layer for each route. 217 | 218 | typescript twoslash 219 | // @filename: auth.ts 220 | import { Elysia } from 'elysia' 221 | 222 | export const auth = new Elysia() 223 | .macro({ 224 | isAuth(isAuth: boolean) { 225 | return { 226 | resolve() { 227 | return { 228 | user: 'saltyaom' 229 | } 230 | } 231 | } 232 | }, 233 | role(role: 'admin' | 'user') { 234 | return {} 235 | } 236 | }) 237 | 238 | // @filename: index.ts 239 | // ---cut--- 240 | import { Elysia } from 'elysia' 241 | import { auth } from './auth' 242 | 243 | const app = new Elysia() 244 | .use(auth) 245 | .get('/', ({ user }) => user, { 246 | // ^? 247 | isAuth: true, 248 | role: 'admin' 249 | }) 250 | 251 | 252 | Macro v2 can also register a new property to the context, allowing us to access the value directly from the context. 253 | 254 | The field can accept anything ranging from string to function, allowing us to create a custom life cycle event. 255 | 256 | macro will be executed in order from top-to-bottom according to definition in hook, ensure that the stack should be handle in correct order. 257 | 258 | 259 | 260 | 261 |


/docs/patterns/mount.md:

1 | --- 2 | title: Mount - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Mount - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Applying WinterCG interplopable code to run with Elysia or vice-versa. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Applying WinterCG interplopable code to run with Elysia or vice-versa. 15 | --- 16 | 17 | # Mount 18 | WinterCG is a standard for web-interoperable runtimes. Supported by Cloudflare, Deno, Vercel Edge Runtime, Netlify Function, and various others, it allows web servers to run interoperably across runtimes that use Web Standard definitions like Fetch, Request, and Response. 19 | 20 | Elysia is WinterCG compliant. We are optimized to run on Bun but also openly support other runtimes if possible. 21 | 22 | In theory, this allows any framework or code that is WinterCG compliant to be run together, allowing frameworks like Elysia, Hono, Remix, Itty Router to run together in a simple function. 23 | 24 | Adhering to this, we implemented the same logic for Elysia by introducing .mount method to run with any framework or code that is WinterCG compliant. 25 | 26 | ## Mount 27 | To use .mount, simply pass a fetch function: 28 | ts 29 | import { Elysia } from 'elysia' 30 | 31 | const app = new Elysia() 32 | .get('/', () => 'Hello from Elysia') 33 | .mount('/hono', hono.fetch) 34 | 35 | 36 | A fetch function is a function that accepts a Web Standard Request and returns a Web Standard Response with the definition of: 37 | ts 38 | // Web Standard Request-like object 39 | // Web Standard Response 40 | type fetch = (request: RequestLike) => Response 41 | 42 | 43 | By default, this declaration is used by: 44 | - Bun 45 | - Deno 46 | - Vercel Edge Runtime 47 | - Cloudflare Worker 48 | - Netlify Edge Function 49 | - Remix Function Handler 50 | - etc. 51 | 52 | This allows you to execute all the aforementioned code in a single server environment, making it possible to interact seamlessly with Elysia. You can also reuse existing functions within a single deployment, eliminating the need for a reverse proxy to manage multiple servers. 53 | 54 | If the framework also supports a .mount function, you can deeply nest a framework that supports it. 55 | ts 56 | import { Elysia } from 'elysia' 57 | import { Hono } from 'hono' 58 | 59 | const elysia = new Elysia() 60 | .get('/', () => 'Hello from Elysia inside Hono inside Elysia') 61 | 62 | const hono = new Hono() 63 | .get('/', (c) => c.text('Hello from Hono!')) 64 | .mount('/elysia', elysia.fetch) 65 | 66 | const main = new Elysia() 67 | .get('/', () => 'Hello from Elysia') 68 | .mount('/hono', hono.fetch) 69 | .listen(3000) 70 | 71 | 72 | ## Reusing Elysia 73 | Moreover, you can re-use multiple existing Elysia projects on your server. 74 | 75 | ts 76 | import { Elysia } from 'elysia' 77 | 78 | import A from 'project-a/elysia' 79 | import B from 'project-b/elysia' 80 | import C from 'project-c/elysia' 81 | 82 | new Elysia() 83 | .mount(A) 84 | .mount(B) 85 | .mount(C) 86 | 87 | 88 | If an instance passed to mount is an Elysia instance, it will be resolved with use automatically, providing type-safety and support for Eden by default. 89 | 90 | This makes the possibility of an interoperable framework and runtime a reality. 91 |


/docs/patterns/trace.md:

1 | --- 2 | title: Trace - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Trace - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Trace is an API to measure the performance of your server. Allowing us to interact with the duration span of each life-cycle events and measure the performance of each function to identify performance bottlenecks of the server. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Trace is an API to measure the performance of your server. Allowing us to interact with the duration span of each life-cycle events and measure the performance of each function to identify performance bottlenecks of the server. 15 | --- 16 | 17 | # Trace 18 | 19 | Performance is an important aspect for Elysia. 20 | 21 | We don't want to be fast for benchmarking purposes, we want you to have a real fast server in real-world scenario. 22 | 23 | There are many factors that can slow down our app - and it's hard to identify them, but trace can helps solve that problem by injecting start and stop code to each life-cycle. 24 | 25 | Trace allows us to inject code to before and after of each life-cycle event, block and interact with the execution of the function. 26 | 27 | ## Trace 28 | Trace use a callback listener to ensure that callback function is finished before moving on to the next lifecycle event. 29 | 30 | To use trace, you need to call trace method on the Elysia instance, and pass a callback function that will be executed for each life-cycle event. 31 | 32 | You may listen to each lifecycle by adding on prefix follows by life-cycle name, for example onHandle to listen to handle event. 33 | 34 | ts twoslash 35 | import { Elysia } from 'elysia' 36 | 37 | const app = new Elysia() 38 | .trace(async ({ onHandle }) => { 39 | onHandle(({ begin, onStop }) => { 40 | onStop(({ end }) => { 41 | console.log('handle took', end - begin, 'ms') 42 | }) 43 | }) 44 | }) 45 | .get('/', () => 'Hi') 46 | .listen(3000) 47 | 48 | 49 | Please refer to Life Cycle Events for more information: 50 | 51 | Elysia Life Cycle 52 | 53 | ## Children 54 | Every events except handle have a children, which is an array of events that are executed inside for each life-cycle event. 55 | 56 | You can use onEvent to listen to each child event in order 57 | 58 | ts twoslash 59 | import { Elysia } from 'elysia' 60 | 61 | const sleep = (time = 1000) => 62 | new Promise((resolve) => setTimeout(resolve, time)) 63 | 64 | const app = new Elysia() 65 | .trace(async ({ onBeforeHandle }) => { 66 | onBeforeHandle(({ total, onEvent }) => { 67 | console.log('total children:', total) 68 | 69 | onEvent(({ onStop }) => { 70 | onStop(({ elapsed }) => { 71 | console.log('child took', elapsed, 'ms') 72 | }) 73 | }) 74 | }) 75 | }) 76 | .get('/', () => 'Hi', { 77 | beforeHandle: [ 78 | function setup() {}, 79 | async function delay() { 80 | await sleep() 81 | } 82 | ] 83 | }) 84 | .listen(3000) 85 | 86 | 87 | In this example, total children will be 2 because there are 2 children in the beforeHandle event. 88 | 89 | Then we listen to each child event by using onEvent and print the duration of each child event. 90 | 91 | ## Trace Parameter 92 | When each lifecycle is called 93 | 94 | ts twoslash 95 | import { Elysia } from 'elysia' 96 | 97 | const app = new Elysia() 98 | // This is trace parameter 99 | // hover to view the type 100 | .trace((parameter) => { 101 | }) 102 | .get('/', () => 'Hi') 103 | .listen(3000) 104 | 105 | 106 | trace accept the following parameters: 107 | 108 | ### id - number 109 | Randomly generated unique id for each request 110 | 111 | ### context - Context 112 | Elysia's Context, eg. set, store, query, params 113 | 114 | ### set - Context.set 115 | Shortcut for context.set, to set a headers or status of the context 116 | 117 | ### store - Singleton.store 118 | Shortcut for context.store, to access a data in the context 119 | 120 | ### time - number 121 | Timestamp of when request is called 122 | 123 | ### on[Event] - TraceListener 124 | An event listener for each life-cycle event. 125 | 126 | You may listen to the following life-cycle: 127 | - onRequest - get notified of every new request 128 | - onParse - array of functions to parse the body 129 | - onTransform - transform request and context before validation 130 | - onBeforeHandle - custom requirement to check before the main handler, can skip the main handler if response returned. 131 | - onHandle - function assigned to the path 132 | - onAfterHandle - interact with the response before sending it back to the client 133 | - onMapResponse - map returned value into a Web Standard Response 134 | - onError - handle error thrown during processing request 135 | - onAfterResponse - cleanup function after response is sent 136 | 137 | ## Trace Listener 138 | A listener for each life-cycle event 139 | 140 | ts twoslash 141 | import { Elysia } from 'elysia' 142 | 143 | const app = new Elysia() 144 | .trace(({ onBeforeHandle }) => { 145 | // This is trace listener 146 | // hover to view the type 147 | onBeforeHandle((parameter) => { 148 | 149 | }) 150 | }) 151 | .get('/', () => 'Hi') 152 | .listen(3000) 153 | 154 | 155 | Each lifecycle listener accept the following 156 | 157 | ### name - string 158 | The name of the function, if the function is anonymous, the name will be anonymous 159 | 160 | ### begin - number 161 | The time when the function is started 162 | 163 | ### end - Promise<number> 164 | The time when the function is ended, will be resolved when the function is ended 165 | 166 | ### error - Promise<Error | null> 167 | Error that was thrown in the lifecycle, will be resolved when the function is ended 168 | 169 | ### onStop - callback?: (detail: TraceEndDetail) => any 170 | A callback that will be executed when the lifecycle is ended 171 | 172 | ts twoslash 173 | import { Elysia } from 'elysia' 174 | 175 | const app = new Elysia() 176 | .trace(({ onBeforeHandle, set }) => { 177 | onBeforeHandle(({ onStop }) => { 178 | onStop(({ elapsed }) => { 179 | set.headers['X-Elapsed'] = elapsed.toString() 180 | }) 181 | }) 182 | }) 183 | .get('/', () => 'Hi') 184 | .listen(3000) 185 | 186 | 187 | It's recommended to mutate context in this function as there's a lock mechanism to ensure the context is mutate successfully before moving on to the next lifecycle event 188 | 189 | ## TraceEndDetail 190 | A parameter that passed to onStop callback 191 | 192 | ### end - number 193 | The time when the function is ended 194 | 195 | ### error - Error | null 196 | Error that was thrown in the lifecycle 197 | 198 | ### elapsed - number 199 | Elapsed time of the lifecycle or end - begin 200 |


/docs/patterns/unit-test.md:

1 | --- 2 | title: Testing - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Testing - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: You can use bun:test to create a unit test with Elysia. Elysia instance has a handle method that accepts Request and will return a Response, the same as creating an HTTP request. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: You can use bun:test to create a unit test with Elysia. Elysia instance has a handle method that accepts Request and will return a Response, the same as creating an HTTP request. 15 | --- 16 | 17 | # Unit Test 18 | 19 | Being WinterCG compliant, we can use Request / Response classes to test an Elysia server. 20 | 21 | Elysia provides the Elysia.handle method, which accepts a Web Standard Request and returns Response, simulating an HTTP Request. 22 | 23 | Bun includes a built-in test runner that offers a Jest-like API through the bun:test module, facilitating the creation of unit tests. 24 | 25 | Create test/index.test.ts in the root of project directory with the following: 26 | 27 | typescript 28 | // test/index.test.ts 29 | import { describe, expect, it } from 'bun:test' 30 | import { Elysia } from 'elysia' 31 | 32 | describe('Elysia', () => { 33 | it('return a response', async () => { 34 | const app = new Elysia().get('/', () => 'hi') 35 | 36 | const response = await app 37 | .handle(new Request('http://localhost/')) 38 | .then((res) => res.text()) 39 | 40 | expect(response).toBe('hi') 41 | }) 42 | }) 43 | 44 | 45 | Then we can perform tests by running bun test 46 | 47 | bash 48 | bun test 49 | 50 | 51 | New requests to an Elysia server must be a fully valid URL, NOT a part of a URL. 52 | 53 | The request must provide URL as the following: 54 | 55 | | URL | Valid | 56 | | --------------------- | ----- | 57 | | http://localhost/user | ✅ | 58 | | /user | ❌ | 59 | 60 | We can also use other testing libraries like Jest to create Elysia unit tests. 61 | 62 | ## Eden Treaty test 63 | 64 | We may use Eden Treaty to create an end-to-end type safety test for Elysia server as follows: 65 | 66 | typescript twoslash 67 | // test/index.test.ts 68 | import { describe, expect, it } from 'bun:test' 69 | import { Elysia } from 'elysia' 70 | import { treaty } from '@elysiajs/eden' 71 | 72 | const app = new Elysia().get('/hello', 'hi') 73 | 74 | const api = treaty(app) 75 | 76 | describe('Elysia', () => { 77 | it('return a response', async () => { 78 | const { data, error } = await api.hello.get() 79 | 80 | expect(data).toBe('hi') 81 | // ^? 82 | }) 83 | }) 84 | 85 | 86 | See Eden Treaty Unit Test for setup and more information. 87 |


/docs/patterns/websocket.md:

1 | --- 2 | title: WebSocket - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'title' 6 | content: WebSocket - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia's WebSocket implementation. Start by declaring WebSocket route with "ws". WebSocket is a realtime protocol for communication between your client and server. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Elysia's WebSocket implementation. Start by declaring WebSocket route with "ws". WebSocket is a realtime protocol for communication between your client and server. 15 | --- 16 | 17 | # WebSocket 18 | 19 | WebSocket is a realtime protocol for communication between your client and server. 20 | 21 | Unlike HTTP where our client repeatedly asking the website for information and waiting for a reply each time, WebSocket sets up a direct line where our client and server can send messages back and forth directly, making the conversation quicker and smoother without having to start over each message. 22 | 23 | SocketIO is a popular library for WebSocket, but it is not the only one. Elysia uses uWebSocket which Bun uses under the hood with the same API. 24 | 25 | To use websocket, simply call Elysia.ws(): 26 | 27 | typescript 28 | import { Elysia } from 'elysia' 29 | 30 | new Elysia() 31 | .ws('/ws', { 32 | message(ws, message) { 33 | ws.send(message) 34 | } 35 | }) 36 | .listen(3000) 37 | 38 | 39 | ## WebSocket message validation: 40 | 41 | Same as normal route, WebSockets also accepts a schema object to strictly type and validate requests. 42 | 43 | typescript 44 | import { Elysia, t } from 'elysia' 45 | 46 | const app = new Elysia() 47 | .ws('/ws', { 48 | // validate incoming message 49 | body: t.Object({ 50 | message: t.String() 51 | }), 52 | query: t.Object({ 53 | id: t.String() 54 | }), 55 | message(ws, { message }) { 56 | // Get schema from `ws.data` 57 | const { id } = ws.data.query 58 | ws.send({ 59 | id, 60 | message, 61 | time: Date.now() 62 | }) 63 | } 64 | }) 65 | .listen(3000) 66 | 67 | 68 | WebSocket schema can validate the following: 69 | 70 | - message - An incoming message. 71 | - query - Query string or URL parameters. 72 | - params - Path parameters. 73 | - header - Request's headers. 74 | - cookie - Request's cookie 75 | - response - Value returned from handler 76 | 77 | By default Elysia will parse incoming stringified JSON message as Object for validation. 78 | 79 | ## Configuration 80 | 81 | You can set Elysia constructor to set the Web Socket value. 82 | 83 | ts 84 | import { Elysia } from 'elysia' 85 | 86 | new Elysia({ 87 | websocket: { 88 | idleTimeout: 30 89 | } 90 | }) 91 | 92 | 93 | Elysia's WebSocket implementation extends Bun's WebSocket configuration, please refer to Bun's WebSocket documentation for more information. 94 | 95 | The following are a brief configuration from Bun WebSocket 96 | 97 | ### perMessageDeflate 98 | 99 | @default false 100 | 101 | Enable compression for clients that support it. 102 | 103 | By default, compression is disabled. 104 | 105 | ### maxPayloadLength 106 | 107 | The maximum size of a message. 108 | 109 | ### idleTimeout 110 | 111 | @default 120 112 | 113 | After a connection has not received a message for this many seconds, it will be closed. 114 | 115 | ### backpressureLimit 116 | 117 | @default 16777216 (16MB) 118 | 119 | The maximum number of bytes that can be buffered for a single connection. 120 | 121 | ### closeOnBackpressureLimit 122 | 123 | @default false 124 | 125 | Close the connection if the backpressure limit is reached. 126 | 127 | ## Methods 128 | 129 | Below are the new methods that are available to the WebSocket route 130 | 131 | ## ws 132 | 133 | Create a websocket handler 134 | 135 | Example: 136 | 137 | typescript 138 | import { Elysia } from 'elysia' 139 | 140 | const app = new Elysia() 141 | .ws('/ws', { 142 | message(ws, message) { 143 | ws.send(message) 144 | } 145 | }) 146 | .listen(3000) 147 | 148 | 149 | Type: 150 | 151 | typescript 152 | .ws(endpoint: path, options: Partial<WebSocketHandler<Context>>): this 153 | 154 | 155 | * endpoint - A path to exposed as websocket handler 156 | * options - Customize WebSocket handler behavior 157 | 158 | ## WebSocketHandler 159 | 160 | WebSocketHandler extends config from config. 161 | 162 | Below is a config which is accepted by ws. 163 | 164 | ## open 165 | 166 | Callback function for new websocket connection. 167 | 168 | Type: 169 | 170 | typescript 171 | open(ws: ServerWebSocket<{ 172 | // uid for each connection 173 | id: string 174 | data: Context 175 | }>): this 176 | 177 | 178 | ## message 179 | 180 | Callback function for incoming websocket message. 181 | 182 | Type: 183 | 184 | typescript 185 | message( 186 | ws: ServerWebSocket<{ 187 | // uid for each connection 188 | id: string 189 | data: Context 190 | }>, 191 | message: Message 192 | ): this 193 | 194 | 195 | Message type based on schema.message. Default is string. 196 | 197 | ## close 198 | 199 | Callback function for closing websocket connection. 200 | 201 | Type: 202 | 203 | typescript 204 | close(ws: ServerWebSocket<{ 205 | // uid for each connection 206 | id: string 207 | data: Context 208 | }>): this 209 | 210 | 211 | ## drain 212 | 213 | Callback function for the server is ready to accept more data. 214 | 215 | Type: 216 | 217 | typescript 218 | drain( 219 | ws: ServerWebSocket<{ 220 | // uid for each connection 221 | id: string 222 | data: Context 223 | }>, 224 | code: number, 225 | reason: string 226 | ): this 227 | 228 | 229 | ## parse 230 | 231 | Parse middleware to parse the request before upgrading the HTTP connection to WebSocket. 232 | 233 | ## beforeHandle 234 | 235 | Before Handle middleware which execute before upgrading the HTTP connection to WebSocket. 236 | 237 | Ideal place for validation. 238 | 239 | ## transform 240 | 241 | Transform middleware which execute before validation. 242 | 243 | ## transformMessage 244 | 245 | Like transform, but execute before validation of WebSocket message 246 | 247 | ## header 248 | 249 | Additional headers to add before upgrade connection to WebSocket. 250 |


/docs/plugins/bearer.md:

1 | --- 2 | title: Bearer Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Bearer Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia for retrieving Bearer token as specified in RFC6750. Start by installing the plugin with "bun add @elysiajs/bearer". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia for retrieving Bearer token as specified in RFC6750. Start by installing the plugin with "bun add @elysiajs/bearer". 15 | --- 16 | 17 | # Bearer Plugin 18 | Plugin for elysia for retrieving the Bearer token. 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/bearer 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { bearer } from '@elysiajs/bearer' 29 | 30 | const app = new Elysia() 31 | .use(bearer()) 32 | .get('/sign', ({ bearer }) => bearer, { 33 | beforeHandle({ bearer, set }) { 34 | if (!bearer) { 35 | set.status = 400 36 | set.headers[ 37 | 'WWW-Authenticate' 38 | ] = `Bearer realm='sign', error="invalid_request"` 39 | 40 | return 'Unauthorized' 41 | } 42 | } 43 | }) 44 | .listen(3000) 45 | 46 | 47 | This plugin is for retrieving a Bearer token specified in RFC6750. 48 | 49 | This plugin DOES NOT handle authentication validation for your server. Instead, the plugin leaves the decision to developers to apply logic for handling validation check themselves. 50 |


/docs/plugins/cors.md:

1 | --- 2 | title: CORS Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: CORS Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for customizing Cross-Origin Resource Sharing behavior. Start by installing the plugin with "bun add @elysiajs/cors". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for customizing Cross-Origin Resource Sharing behavior. Start by installing the plugin with "bun add @elysiajs/cors". 15 | --- 16 | 17 | # CORS Plugin 18 | This plugin adds support for customizing Cross-Origin Resource Sharing behavior. 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/cors 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { cors } from '@elysiajs/cors' 29 | 30 | new Elysia() 31 | .use(cors()) 32 | .listen(3000) 33 | 34 | 35 | This will set Elysia to accept requests from any origin. 36 | 37 | ## Config 38 | Below is a config which is accepted by the plugin 39 | 40 | ### origin 41 | @default true 42 | 43 | Indicates whether the response can be shared with the requesting code from the given origins. 44 | 45 | Value can be one of the following: 46 | - string - Name of origin which will directly assign to Access-Control-Allow-Origin header. 47 | - boolean - If set to true, Access-Control-Allow-Origin will be set to * (any origins) 48 | - RegExp - Pattern to match request's URL, allowed if matched. 49 | - Function - Custom logic to allow resource sharing, allow if true is returned. 50 | - Expected to have the type of: 51 | typescript 52 | cors(context: Context) => boolean | void 53 | 54 | - Array<string | RegExp | Function> - iterate through all cases above in order, allowed if any of the values are true. 55 | 56 | --- 57 | ### methods 58 | @default * 59 | 60 | Allowed methods for cross-origin requests. 61 | 62 | Assign Access-Control-Allow-Methods header. 63 | 64 | Value can be one of the following: 65 | - undefined | null | '' - Ignore all methods. 66 | - * - Allows all methods. 67 | - string - Expects either a single method or a comma-delimited string 68 | - (eg: 'GET, PUT, POST') 69 | - string[] - Allow multiple HTTP methods. 70 | - eg: ['GET', 'PUT', 'POST'] 71 | 72 | --- 73 | ### allowedHeaders 74 | @default * 75 | 76 | Allowed headers for an incoming request. 77 | 78 | Assign Access-Control-Allow-Headers header. 79 | 80 | Value can be one of the following: 81 | - string - Expects either a single header or a comma-delimited string 82 | - eg: 'Content-Type, Authorization'. 83 | - string[] - Allow multiple HTTP headers. 84 | - eg: ['Content-Type', 'Authorization'] 85 | 86 | --- 87 | ### exposeHeaders 88 | @default * 89 | 90 | Response CORS with specified headers. 91 | 92 | Assign Access-Control-Expose-Headers header. 93 | 94 | Value can be one of the following: 95 | - string - Expects either a single header or a comma-delimited string. 96 | - eg: 'Content-Type, X-Powered-By'. 97 | - string[] - Allow multiple HTTP headers. 98 | - eg: ['Content-Type', 'X-Powered-By'] 99 | 100 | --- 101 | ### credentials 102 | @default true 103 | 104 | 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 is include. 105 | 106 | When a request's credentials mode Request.credentials is include, browsers will only expose the response to the frontend JavaScript code if the Access-Control-Allow-Credentials value is true. 107 | 108 | Credentials are cookies, authorization headers, or TLS client certificates. 109 | 110 | Assign Access-Control-Allow-Credentials header. 111 | 112 | --- 113 | ### maxAge 114 | @default 5 115 | 116 | Indicates how long the results of a preflight request (that is the information contained in the Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can be cached. 117 | 118 | Assign Access-Control-Max-Age header. 119 | 120 | --- 121 | ### preflight 122 | 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. 123 | 124 | Response with OPTIONS request with 3 HTTP request headers: 125 | - Access-Control-Request-Method 126 | - Access-Control-Request-Headers 127 | - Origin 128 | 129 | This config indicates if the server should respond to preflight requests. 130 | 131 | ## Pattern 132 | Below you can find the common patterns to use the plugin. 133 | 134 | ## Allow CORS by top-level domain 135 | 136 | typescript 137 | import { Elysia } from 'elysia' 138 | import { cors } from '@elysiajs/cors' 139 | 140 | const app = new Elysia() 141 | .use(cors({ 142 | origin: /.*\.saltyaom\.com$/ 143 | })) 144 | .get('/', () => 'Hi') 145 | .listen(3000) 146 | 147 | 148 | This will allow requests from top-level domains with saltyaom.com 149 |


/docs/plugins/cron.md:

1 | --- 2 | title: Cron Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Cron Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for running cronjob in Elysia server. Start by installing the plugin with "bun add @elysiajs/cron". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for customizing Cross-Origin Resource Sharing behavior. Start by installing the plugin with "bun add @elysiajs/cors". 15 | --- 16 | 17 | # Cron Plugin 18 | 19 | This plugin adds support for running cronjob in the Elysia server. 20 | 21 | Install with: 22 | 23 | bash 24 | bun add @elysiajs/cron 25 | 26 | 27 | Then use it: 28 | 29 | typescript 30 | import { Elysia } from 'elysia' 31 | import { cron } from '@elysiajs/cron' 32 | 33 | new Elysia() 34 | .use( 35 | cron({ 36 | name: 'heartbeat', 37 | pattern: '*/10 * * * * *', 38 | run() { 39 | console.log('Heartbeat') 40 | } 41 | }) 42 | ) 43 | .listen(3000) 44 | 45 | 46 | The above code will log heartbeat every 10 seconds. 47 | 48 | ## cron 49 | 50 | Create a cronjob for the Elysia server. 51 | 52 | type: 53 | 54 | 55 | cron(config: CronConfig, callback: (Instance['store']) => void): this 56 | 57 | 58 | CronConfig accepts the parameters specified below: 59 | 60 | ### name 61 | 62 | Job name to register to store. 63 | 64 | This will register the cron instance to store with a specified name, which can be used to reference in later processes eg. stop the job. 65 | 66 | ### pattern 67 | 68 | Time to run the job as specified by cron syntax specified as below: 69 | 70 | 71 | ┌────────────── second (optional) 72 | │ ┌──────────── minute 73 | │ │ ┌────────── hour 74 | │ │ │ ┌──────── day of the month 75 | │ │ │ │ ┌────── month 76 | │ │ │ │ │ ┌──── day of week 77 | │ │ │ │ │ │ 78 | * * * * * * 79 | 80 | 81 | This can be generated by tools like Crontab Guru 82 | 83 | --- 84 | 85 | This plugin extends the cron method to Elysia using cronner. 86 | 87 | Below are the configs accepted by cronner. 88 | 89 | ### timezone 90 | 91 | Time zone in Europe/Stockholm format 92 | 93 | ### startAt 94 | 95 | Schedule start time for the job 96 | 97 | ### stopAt 98 | 99 | Schedule stop time for the job 100 | 101 | ### maxRuns 102 | 103 | Maximum number of executions 104 | 105 | ### catch 106 | 107 | Continue execution even if an unhandled error is thrown by a triggered function. 108 | 109 | ### interval 110 | 111 | The minimum interval between executions, in seconds. 112 | 113 | ## Pattern 114 | 115 | Below you can find the common patterns to use the plugin. 116 | 117 | ## Stop cronjob 118 | 119 | You can stop cronjob manually by accessing the cronjob name registered to store. 120 | 121 | typescript 122 | import { Elysia } from 'elysia' 123 | import { cron } from '@elysiajs/cron' 124 | 125 | const app = new Elysia() 126 | .use( 127 | cron({ 128 | name: 'heartbeat', 129 | pattern: '*/1 * * * * *', 130 | run() { 131 | console.log("Heartbeat") 132 | } 133 | }) 134 | ) 135 | .get('/stop', ({ store: { cron: { heartbeat } } }) => { 136 | heartbeat.stop() 137 | 138 | return 'Stop heartbeat' 139 | }) 140 | .listen(3000) 141 | 142 | 143 | ## Predefined patterns 144 | 145 | You can use predefined patterns from @elysiajs/cron/schedule 146 | typescript 147 | import { Elysia } from 'elysia' 148 | import { cron, Patterns } from '@elysiajs/cron' 149 | 150 | const app = new Elysia() 151 | .use( 152 | cron({ 153 | name: 'heartbeat', 154 | pattern: Patterns.everySecond(), 155 | run() { 156 | console.log("Heartbeat") 157 | } 158 | }) 159 | ) 160 | .get('/stop', ({ store: { cron: { heartbeat } } }) => { 161 | heartbeat.stop() 162 | 163 | return 'Stop heartbeat' 164 | }) 165 | .listen(3000) 166 | 167 | 168 | 169 | ### Functions 170 | 171 | Function | Description 172 | ------------- | ------------- 173 | .everySeconds(2) | Run the task every 2 seconds 174 | .everyMinutes(5) | Run the task every 5 minutes 175 | .everyHours(3) | Run the task every 3 hours 176 | .everyHoursAt(3, 15) | Run the task every 3 hours at 15 minutes 177 | .everyDayAt('04:19') | Run the task every day at 04:19 178 | .everyWeekOn(Patterns.MONDAY, '19:30') | Run the task every Monday at 19:30 179 | .everyWeekdayAt('17:00') | Run the task every day from Monday to Friday at 17:00 180 | .everyWeekendAt('11:00') | Run the task on Saturday and Sunday at 11:00 181 | 182 | ### Function aliases to constants 183 | 184 | Function | Constant 185 | ------------- | ------------- 186 | .everySecond() | EVERY_SECOND 187 | .everyMinute() | EVERY_MINUTE 188 | .hourly() | EVERY_HOUR 189 | .daily() | EVERY_DAY_AT_MIDNIGHT 190 | .everyWeekday() | EVERY_WEEKDAY 191 | .everyWeekend() | EVERY_WEEKEND 192 | .weekly() | EVERY_WEEK 193 | .monthly() | EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT 194 | .everyQuarter() | EVERY_QUARTER 195 | .yearly() | EVERY_YEAR 196 | 197 | ### Constants 198 | 199 | Constant | Pattern 200 | ------------- | ------------- 201 | .EVERY_SECOND | * * * * * * 202 | .EVERY_5_SECONDS | */5 * * * * * 203 | .EVERY_10_SECONDS | */10 * * * * * 204 | .EVERY_30_SECONDS | */30 * * * * * 205 | .EVERY_MINUTE | */1 * * * * 206 | .EVERY_5_MINUTES | 0 */5 * * * * 207 | .EVERY_10_MINUTES | 0 */10 * * * * 208 | .EVERY_30_MINUTES | 0 */30 * * * * 209 | .EVERY_HOUR | 0 0-23/1 * * * 210 | .EVERY_2_HOURS | 0 0-23/2 * * * 211 | .EVERY_3_HOURS | 0 0-23/3 * * * 212 | .EVERY_4_HOURS | 0 0-23/4 * * * 213 | .EVERY_5_HOURS | 0 0-23/5 * * * 214 | .EVERY_6_HOURS | 0 0-23/6 * * * 215 | .EVERY_7_HOURS | 0 0-23/7 * * * 216 | .EVERY_8_HOURS | 0 0-23/8 * * * 217 | .EVERY_9_HOURS | 0 0-23/9 * * * 218 | .EVERY_10_HOURS | 0 0-23/10 * * * 219 | .EVERY_11_HOURS | 0 0-23/11 * * * 220 | .EVERY_12_HOURS | 0 0-23/12 * * * 221 | .EVERY_DAY_AT_1AM | 0 01 * * * 222 | .EVERY_DAY_AT_2AM | 0 02 * * * 223 | .EVERY_DAY_AT_3AM | 0 03 * * * 224 | .EVERY_DAY_AT_4AM | 0 04 * * * 225 | .EVERY_DAY_AT_5AM | 0 05 * * * 226 | .EVERY_DAY_AT_6AM | 0 06 * * * 227 | .EVERY_DAY_AT_7AM | 0 07 * * * 228 | .EVERY_DAY_AT_8AM | 0 08 * * * 229 | .EVERY_DAY_AT_9AM | 0 09 * * * 230 | .EVERY_DAY_AT_10AM | 0 10 * * * 231 | .EVERY_DAY_AT_11AM | 0 11 * * * 232 | .EVERY_DAY_AT_NOON | 0 12 * * * 233 | .EVERY_DAY_AT_1PM | 0 13 * * * 234 | .EVERY_DAY_AT_2PM | 0 14 * * * 235 | .EVERY_DAY_AT_3PM | 0 15 * * * 236 | .EVERY_DAY_AT_4PM | 0 16 * * * 237 | .EVERY_DAY_AT_5PM | 0 17 * * * 238 | .EVERY_DAY_AT_6PM | 0 18 * * * 239 | .EVERY_DAY_AT_7PM | 0 19 * * * 240 | .EVERY_DAY_AT_8PM | 0 20 * * * 241 | .EVERY_DAY_AT_9PM | 0 21 * * * 242 | .EVERY_DAY_AT_10PM | 0 22 * * * 243 | .EVERY_DAY_AT_11PM | 0 23 * * * 244 | .EVERY_DAY_AT_MIDNIGHT | 0 0 * * * 245 | .EVERY_WEEK | 0 0 * * 0 246 | .EVERY_WEEKDAY | 0 0 * * 1-5 247 | .EVERY_WEEKEND | 0 0 * * 6,0 248 | .EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT | 0 0 1 * * 249 | .EVERY_1ST_DAY_OF_MONTH_AT_NOON | 0 12 1 * * 250 | .EVERY_2ND_HOUR | 0 */2 * * * 251 | .EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM | 0 1-23/2 * * * 252 | .EVERY_2ND_MONTH | 0 0 1 */2 * 253 | .EVERY_QUARTER | 0 0 1 */3 * 254 | .EVERY_6_MONTHS | 0 0 1 */6 * 255 | .EVERY_YEAR | 0 0 1 1 * 256 | .EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM | 0 */30 9-17 * * * 257 | .EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM | 0 */30 9-18 * * * 258 | .EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM | 0 */30 10-19 * * * 259 |


/docs/plugins/graphql-apollo.md:

1 | --- 2 | title: Apollo GraphQL Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Apollo GraphQL Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for using GraphQL Apollo on the Elysia server. Start by installing the plugin with "bun add graphql @elysiajs/apollo @apollo/server". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for using GraphQL Apollo on the Elysia server. Start by installing the plugin with "bun add graphql @elysiajs/apollo @apollo/server". 15 | --- 16 | 17 | # GraphQL Apollo Plugin 18 | Plugin for elysia for using GraphQL Apollo. 19 | 20 | Install with: 21 | bash 22 | bun add graphql @elysiajs/apollo @apollo/server 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { apollo, gql } from '@elysiajs/apollo' 29 | 30 | const app = new Elysia() 31 | .use( 32 | apollo({ 33 | typeDefs: gql` 34 | type Book { 35 | title: String 36 | author: String 37 | } 38 | 39 | type Query { 40 | books: [Book] 41 | } 42 | `, 43 | resolvers: { 44 | Query: { 45 | books: () => { 46 | return [ 47 | { 48 | title: 'Elysia', 49 | author: 'saltyAom' 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | }) 56 | ) 57 | .listen(3000) 58 | 59 | 60 | Accessing /graphql should show Apollo GraphQL playground work with. 61 | 62 | ## Context 63 | Because Elysia is based on Web Standard Request and Response which is different from Node's HttpRequest and HttpResponse that Express uses, results in req, res being undefined in context. 64 | 65 | Because of this, Elysia replaces both with context like route parameters. 66 | typescript 67 | const app = new Elysia() 68 | .use( 69 | apollo({ 70 | typeDefs, 71 | resolvers, 72 | context: async ({ request }) => { 73 | const authorization = request.headers.get('Authorization') 74 | 75 | return { 76 | authorization 77 | } 78 | } 79 | }) 80 | ) 81 | .listen(3000) 82 | 83 | 84 | 85 | ## Config 86 | This plugin extends Apollo's ServerRegistration (which is ApolloServer's' constructor parameter). 87 | 88 | Below are the extended parameters for configuring Apollo Server with Elysia. 89 | ### path 90 | @default "/graphql" 91 | 92 | Path to expose Apollo Server. 93 | 94 | ### enablePlayground 95 | @default process.env.ENV !== 'production' 96 | 97 | Determine whether should Apollo should provide Apollo Playground. 98 |


/docs/plugins/graphql-yoga.md:

1 | --- 2 | title: GraphQL Yoga Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: GraphQL Yoga Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for using GraphQL Yoga on the Elysia server. Start by installing the plugin with "bun add graphql graphql-yoga @elysiajs/graphql-yoga". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for using GraphQL Yoga on the Elysia server. Start by installing the plugin with "bun add graphql graphql-yoga @elysiajs/graphql-yoga". 15 | --- 16 | 17 | # GraphQL Yoga Plugin 18 | This plugin integrates GraphQL yoga with Elysia 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/graphql-yoga 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { yoga } from '@elysiajs/graphql-yoga' 29 | 30 | const app = new Elysia() 31 | .use( 32 | yoga({ 33 | typeDefs: /* GraphQL */` 34 | type Query { 35 | hi: String 36 | } 37 | `, 38 | resolvers: { 39 | Query: { 40 | hi: () => 'Hello from Elysia' 41 | } 42 | } 43 | }) 44 | ) 45 | .listen(3000) 46 | 47 | 48 | Accessing /graphql in the browser (GET request) would show you a GraphiQL instance for the GraphQL-enabled Elysia server. 49 | 50 | optional: you can install a custom version of optional peer dependencies as well: 51 | bash 52 | bun add graphql graphql-yoga 53 | 54 | 55 | ## Resolver 56 | Elysia uses Mobius to infer type from typeDefs field automatically, allowing you to get full type-safety and auto-complete when typing resolver types. 57 | 58 | ## Context 59 | You can add custom context to the resolver function by adding context 60 | ts 61 | import { Elysia } from 'elysia' 62 | import { yoga } from '@elysiajs/graphql-yoga' 63 | 64 | const app = new Elysia() 65 | .use( 66 | yoga({ 67 | typeDefs: /* GraphQL */` 68 | type Query { 69 | hi: String 70 | } 71 | `, 72 | context: { 73 | name: 'Mobius' 74 | }, 75 | // If context is a function on this doesn't present 76 | // for some reason it won't infer context type 77 | useContext(_) {}, 78 | resolvers: { 79 | Query: { 80 | hi: async (parent, args, context) => context.name 81 | } 82 | } 83 | }) 84 | ) 85 | .listen(3000) 86 | 87 | 88 | ## Config 89 | This plugin extends GraphQL Yoga's createYoga options, please refer to the GraphQL Yoga documentation with inlining schema config to root. 90 | 91 | Below is a config which is accepted by the plugin 92 | 93 | ### path 94 | @default /graphql 95 | 96 | Endpoint to expose GraphQL handler 97 |


/docs/plugins/html.md:

1 | --- 2 | title: HTML Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: HTML Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds shortcut support for returning HTML in the Elysia server. Start by installing the plugin with "bun add @elysiajs/html". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds shortcut support for returning HTML in the Elysia server. Start by installing the plugin with "bun add @elysiajs/html". 15 | --- 16 | 17 | # HTML Plugin 18 | 19 | Allows you to use JSX and HTML with proper headers and support. 20 | 21 | Install with: 22 | 23 | bash 24 | bun add @elysiajs/html 25 | 26 | 27 | Then use it: 28 | 29 | tsx 30 | import { Elysia } from 'elysia' 31 | import { html, Html } from '@elysiajs/html' 32 | 33 | new Elysia() 34 | .use(html()) 35 | .get( 36 | '/html', 37 | () => ` 38 | <html lang='en'> 39 | <head> 40 | <title>Hello World</title> 41 | </head> 42 | <body> 43 | <h1>Hello World</h1> 44 | </body> 45 | </html>` 46 | ) 47 | .get('/jsx', () => ( 48 | <html lang='en'> 49 | <head> 50 | <title>Hello World</title> 51 | </head> 52 | <body> 53 | <h1>Hello World</h1> 54 | </body> 55 | </html> 56 | )) 57 | .listen(3000) 58 | 59 | 60 | This plugin will automatically add Content-Type: text/html; charset=utf8 header to the response, add <!doctype html>, and convert it into a Response object. 61 | 62 | ## JSX 63 | Elysia HTML is based on @kitajs/html allowing us to define JSX to string in compile time to achieve high performance. 64 | 65 | Name your file that needs to use JSX to end with affix "x": 66 | - .js -> .jsx 67 | - .ts -> .tsx 68 | 69 | To register the TypeScript type, please append the following to tsconfig.json: 70 | jsonc 71 | // tsconfig.json 72 | { 73 | "compilerOptions": { 74 | "jsx": "react", 75 | "jsxFactory": "Html.createElement", 76 | "jsxFragmentFactory": "Html.Fragment" 77 | } 78 | } 79 | 80 | 81 | That's it, now you can use JSX as your template engine: 82 | tsx 83 | import { Elysia } from 'elysia' 84 | import { html, Html } from '@elysiajs/html' // [!code ++] 85 | 86 | new Elysia() 87 | .use(html()) // [!code ++] 88 | .get('/', () => ( 89 | <html lang="en"> 90 | <head> 91 | <title>Hello World</title> 92 | </head> 93 | <body> 94 | <h1>Hello World</h1> 95 | </body> 96 | </html> 97 | )) 98 | .listen(3000) 99 | 100 | 101 | If the error Cannot find name 'Html'. Did you mean 'html'? occurs, this import must be added to the JSX template: 102 | tsx 103 | import { Html } from '@elysiajs/html' 104 | 105 | 106 | It is important that it is written in uppercase. 107 | 108 | ## XSS 109 | Elysia HTML is based use of the Kita HTML plugin to detect possible XSS attacks in compile time. 110 | 111 | You can use a dedicated safe attribute to sanitize user value to prevent XSS vulnerability. 112 | tsx 113 | import { Elysia, t } from 'elysia' 114 | import { html, Html } from '@elysiajs/html' 115 | 116 | new Elysia() 117 | .use(html()) 118 | .post('/', ({ body }) => ( 119 | <html lang="en"> 120 | <head> 121 | <title>Hello World</title> 122 | </head> 123 | <body> 124 | <h1 safe>{body}</h1> 125 | </body> 126 | </html> 127 | ), { 128 | body: t.String() 129 | }) 130 | .listen(3000) 131 | 132 | 133 | However, when are building a large-scale app, it's best to have a type reminder to detect possible XSS vulnerabilities in your codebase. 134 | 135 | To add a type-safe reminder, please install: 136 | sh 137 | bun add @kitajs/ts-html-plugin 138 | 139 | 140 | Then appends the following tsconfig.json 141 | jsonc 142 | // tsconfig.json 143 | { 144 | "compilerOptions": { 145 | "jsx": "react", 146 | "jsxFactory": "Html.createElement", 147 | "jsxFragmentFactory": "Html.Fragment", 148 | "plugins": [{ "name": "@kitajs/ts-html-plugin" }] 149 | } 150 | } 151 | 152 | 153 | ## Options 154 | 155 | ### contentType 156 | 157 | - Type: string 158 | - Default: 'text/html; charset=utf8' 159 | 160 | The content-type of the response. 161 | 162 | ### autoDetect 163 | 164 | - Type: boolean 165 | - Default: true 166 | 167 | Whether to automatically detect HTML content and set the content-type. 168 | 169 | ### autoDoctype 170 | 171 | - Type: boolean | 'full' 172 | - Default: true 173 | 174 | Whether to automatically add <!doctype html> to a response starting with <html>, if not found. 175 | 176 | Use full to also automatically add doctypes on responses returned without this plugin 177 | 178 | ts 179 | // without the plugin 180 | app.get('/', () => '<html></html>') 181 | 182 | // With the plugin 183 | app.get('/', ({ html }) => html('<html></html>')) 184 | 185 | 186 | ### isHtml 187 | 188 | - Type: (value: string) => boolean 189 | - Default: isHtml (exported function) 190 | 191 | The function is used to detect if a string is a html or not. Default implementation if length is greater than 7, starts with < and ends with >. 192 | 193 | Keep in mind there's no real way to validate HTML, so the default implementation is a best guess. 194 |


/docs/plugins/jwt.md:

1 | --- 2 | title: JWT Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: JWT Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for using JWT (JSON Web Token) in Elysia server. Start by installing the plugin with "bun add @elysiajs/jwt". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for using JWT (JSON Web Token) in Elysia server. Start by installing the plugin with "bun add @elysiajs/jwt". 15 | --- 16 | 17 | # JWT Plugin 18 | This plugin adds support for using JWT in Elysia handler 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/jwt 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { jwt } from '@elysiajs/jwt' 29 | 30 | const app = new Elysia() 31 | .use( 32 | jwt({ 33 | name: 'jwt', 34 | secret: 'Fischl von Luftschloss Narfidort' 35 | }) 36 | ) 37 | .get('/sign/:name', async ({ jwt, cookie: { auth }, params }) => { 38 | auth.set({ 39 | value: await jwt.sign(params), 40 | httpOnly: true, 41 | maxAge: 7 * 86400, 42 | path: '/profile', 43 | }) 44 | 45 | return `Sign in as ${auth.value}` 46 | }) 47 | .get('/profile', async ({ jwt, set, cookie: { auth } }) => { 48 | const profile = await jwt.verify(auth.value) 49 | 50 | if (!profile) { 51 | set.status = 401 52 | return 'Unauthorized' 53 | } 54 | 55 | return `Hello ${profile.name}` 56 | }) 57 | .listen(3000) 58 | 59 | 60 | ## Config 61 | This plugin extends config from jose. 62 | 63 | Below is a config that is accepted by the plugin. 64 | 65 | ### name 66 | Name to register jwt function as. 67 | 68 | For example, jwt function will be registered with a custom name. 69 | typescript 70 | app 71 | .use( 72 | jwt({ 73 | name: 'myJWTNamespace', 74 | secret: process.env.JWT_SECRETS! 75 | }) 76 | ) 77 | .get('/sign/:name', ({ myJWTNamespace, params }) => { 78 | return myJWTNamespace.sign(params) 79 | }) 80 | 81 | 82 | Because some might need to use multiple jwt with different configs in a single server, explicitly registering the JWT function with a different name is needed. 83 | 84 | ### secret 85 | The private key to sign JWT payload with. 86 | 87 | ### schema 88 | Type strict validation for JWT payload. 89 | 90 | --- 91 | Below is a config that extends from cookie 92 | 93 | ### alg 94 | @default HS256 95 | 96 | Signing Algorithm to sign JWT payload with. 97 | 98 | Possible properties for jose are: 99 | HS256 100 | HS384 101 | HS512 102 | PS256 103 | PS384 104 | PS512 105 | RS256 106 | RS384 107 | RS512 108 | ES256 109 | ES256K 110 | ES384 111 | ES512 112 | EdDSA 113 | 114 | ### iss 115 | The issuer claim identifies the principal that issued the JWT as per RFC7519 116 | 117 | TLDR; is usually (the domain) name of the signer. 118 | 119 | ### sub 120 | The subject claim identifies the principal that is the subject of the JWT. 121 | 122 | The claims in a JWT are normally statements about the subject as per RFC7519 123 | 124 | ### aud 125 | The audience claim identifies the recipients that the JWT is intended for. 126 | 127 | Each principal intended to process the JWT MUST identify itself with a value in the audience claim as per RFC7519 128 | 129 | ### jti 130 | JWT ID claim provides a unique identifier for the JWT as per RFC7519 131 | 132 | ### nbf 133 | The "not before" claim identifies the time before which the JWT must not be accepted for processing as per RFC7519 134 | 135 | ### exp 136 | The expiration time claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing as per RFC7519 137 | 138 | ### iat 139 | The "issued at" claim identifies the time at which the JWT was issued.
140 | 141 | This claim can be used to determine the age of the JWT as per RFC7519 142 |
143 | ### b64 144 | This JWS Extension Header Parameter modifies the JWS Payload representation and the JWS Signing input computation as per RFC7797. 145 | 146 | ### kid 147 | A hint indicating which key was used to secure the JWS. 148 | 149 | This parameter allows originators to explicitly signal a change of key to recipients as per RFC7515 150 | 151 | ### x5t 152 | (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 corresponding to the key used to digitally sign the JWS as per RFC7515 153 | 154 | ### x5c 155 | (X.509 certificate chain) header parameter contains the X.509 public key certificate or certificate chain RFC5280 corresponding to the key used to digitally sign the JWS as per RFC7515 156 | 157 | ### x5u 158 | (X.509 URL) header parameter is a URI 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 159 | 160 | ### jwk 161 | The "jku" (JWK Set URL) Header Parameter is a URI [RFC3986] that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used to digitally sign the JWS. 162 | 163 | The keys MUST be encoded as a JWK Set [JWK] as per RFC7515 164 | 165 | ### typ 166 | The typ (type) Header Parameter is used by JWS applications to declare the media type [IANA.MediaTypes] of this complete JWS. 167 | 168 | 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 169 | 170 | ### ctr 171 | Content-Type parameter is used by JWS applications to declare the media type [IANA.MediaTypes] of the secured content (the payload). 172 | 173 | 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 174 | 175 | ## Handler 176 | Below are the value added to the handler. 177 | 178 | ### jwt.sign 179 | A dynamic object of collection related to use with JWT registered by the JWT plugin. 180 | 181 | Type: 182 | typescript 183 | sign: (payload: JWTPayloadSpec): Promise<string> 184 | 185 | 186 | JWTPayloadSpec accepts the same value as JWT config 187 | 188 | ### jwt.verify 189 | Verify payload with the provided JWT config 190 | 191 | Type: 192 | typescript 193 | verify(payload: string) => Promise<JWTPayloadSpec | false> 194 | 195 | 196 | JWTPayloadSpec accepts the same value as JWT config 197 | 198 | ## Pattern 199 | Below you can find the common patterns to use the plugin. 200 | 201 | ## Set JWT expiration date 202 | By default, the config is passed to setCookie and inherits its value. 203 | 204 | typescript 205 | const app = new Elysia() 206 | .use( 207 | jwt({ 208 | name: 'jwt', 209 | secret: 'kunikuzushi', 210 | exp: '7d' 211 | }) 212 | ) 213 | .get('/sign/:name', async ({ jwt, params }) => jwt.sign(params)) 214 | 215 | 216 | This will sign JWT with an expiration date of the next 7 days. 217 |


/docs/plugins/opentelemetry.md:

1 | --- 2 | title: OpenTelemetry Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: OpenTelemetry Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for OpenTelemetry. Start by installing the plugin with "bun add @elysiajs/opentelemetry". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for OpenTelemetry. Start by installing the plugin with "bun add @elysiajs/opentelemetry". 15 | --- 16 | 17 | # OpenTelemetry 18 | 19 | To start using OpenTelemetry, install @elysiajs/opentelemetry and apply plugin to any instance. 20 | 21 | typescript 22 | import { Elysia } from 'elysia' 23 | import { opentelemetry } from '@elysiajs/opentelemetry' 24 | 25 | import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' 26 | import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' 27 | 28 | new Elysia() 29 | .use( 30 | opentelemetry({ 31 | spanProcessors: [ 32 | new BatchSpanProcessor( 33 | new OTLPTraceExporter() 34 | ) 35 | ] 36 | }) 37 | ) 38 | 39 | 40 | jaeger showing collected trace automatically 41 | 42 | Elysia OpenTelemetry is will collect span of any library compatible OpenTelemetry standard, and will apply parent and child span automatically. 43 | 44 | ## Usage 45 | See opentelemetry for usage and utilities 46 | 47 | ## Config 48 | This plugin extends OpenTelemetry SDK parameters options. 49 | 50 | Below is a config which is accepted by the plugin 51 | 52 | ### autoDetectResources - boolean 53 | Detect resources automatically from the environment using the default resource detectors. 54 | 55 | default: true 56 | 57 | ### contextManager - ContextManager 58 | Use a custom context manager. 59 | 60 | default: AsyncHooksContextManager 61 | 62 | ### textMapPropagator - TextMapPropagator 63 | Use a custom propagator. 64 | 65 | default: CompositePropagator using W3C Trace Context and Baggage 66 | 67 | ### metricReader - MetricReader 68 | Add a MetricReader that will be passed to the MeterProvider. 69 | 70 | ### views - View[] 71 | A list of views to be passed to the MeterProvider. 72 | 73 | Accepts an array of View-instances. This parameter can be used to configure explicit bucket sizes of histogram metrics. 74 | 75 | ### instrumentations - (Instrumentation | Instrumentation[])[] 76 | Configure instrumentations. 77 | 78 | By default getNodeAutoInstrumentations is enabled, if you want to enable them you can use either metapackage or configure each instrumentation individually. 79 | 80 | default: getNodeAutoInstrumentations() 81 | 82 | ### resource - IResource 83 | Configure a resource. 84 | 85 | Resources may also be detected by using the autoDetectResources method of the SDK. 86 | 87 | ### resourceDetectors - Array<Detector | DetectorSync> 88 | 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. 89 | 90 | If resourceDetectors was not set, you can also use the environment variable OTEL_NODE_RESOURCE_DETECTORS to enable only certain detectors, or completely disable them: 91 | 92 | - env 93 | - host 94 | - os 95 | - process 96 | - serviceinstance (experimental) 97 | - all - enable all resource detectors above 98 | - none - disable resource detection 99 | 100 | For example, to enable only the env, host detectors: 101 | 102 | bash 103 | export OTEL_NODE_RESOURCE_DETECTORS="env,host" 104 | 105 | 106 | ### sampler - Sampler 107 | Configure a custom sampler. By default, all traces will be sampled. 108 | 109 | ### serviceName - string 110 | Namespace to be identify as. 111 | 112 | ### spanProcessors - SpanProcessor[] 113 | An array of span processors to register to the tracer provider. 114 | 115 | ### traceExporter - SpanExporter 116 | Configure a trace exporter. If an exporter is configured, it will be used with a BatchSpanProcessor. 117 | 118 | If an exporter OR span processor is not configured programmatically, this package will auto setup the default otlp exporter with http/protobuf protocol with a BatchSpanProcessor. 119 | 120 | ### spanLimits - SpanLimits 121 | Configure tracing parameters. These are the same trace parameters used to configure a tracer. 122 |


/docs/plugins/server-timing.md:

1 | --- 2 | title: Server Timing Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Server Timing Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia for performance audit via Server Timing API. Start by installing the plugin with "bun add @elysiajs/server-timing". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia for performance audit via Server Timing API. Start by installing the plugin with "bun add @elysiajs/server-timing". 15 | --- 16 | 17 | # Server Timing Plugin 18 | This plugin adds support for auditing performance bottlenecks with Server Timing API 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/server-timing 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { serverTiming } from '@elysiajs/server-timing' 29 | 30 | new Elysia() 31 | .use(serverTiming()) 32 | .get('/', () => 'hello') 33 | .listen(3000) 34 | 35 | 36 | Server Timing then will append header 'Server-Timing' with log duration, function name, and detail for each life-cycle function. 37 | 38 | To inspect, open browser developer tools > Network > [Request made through Elysia server] > Timing. 39 | 40 | Developer tools showing Server Timing screenshot 41 | 42 | Now you can effortlessly audit the performance bottleneck of your server. 43 | 44 | ## Config 45 | Below is a config which is accepted by the plugin 46 | 47 | ### enabled 48 | @default NODE_ENV !== 'production' 49 | 50 | Determine whether or not Server Timing should be enabled 51 | 52 | ### allow 53 | @default undefined 54 | 55 | A condition whether server timing should be log 56 | 57 | ### trace 58 | @default undefined 59 | 60 | Allow Server Timing to log specified life-cycle events: 61 | 62 | Trace accepts objects of the following: 63 | - request: capture duration from request 64 | - parse: capture duration from parse 65 | - transform: capture duration from transform 66 | - beforeHandle: capture duration from beforeHandle 67 | - handle: capture duration from the handle 68 | - afterHandle: capture duration from afterHandle 69 | - total: capture total duration from start to finish 70 | 71 | ## Pattern 72 | Below you can find the common patterns to use the plugin. 73 | 74 | - Allow Condition 75 | 76 | ## Allow Condition 77 | You may disable Server Timing on specific routes via allow property 78 | 79 | ts 80 | import { Elysia } from 'elysia' 81 | import { serverTiming } from '@elysiajs/server-timing' 82 | 83 | new Elysia() 84 | .use( 85 | serverTiming({ 86 | allow: ({ request }) => { 87 | return new URL(request.url).pathname !== '/no-trace' 88 | } 89 | }) 90 | ) 91 | 92 |


/docs/plugins/static.md:

1 | --- 2 | title: Static Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Static Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for serving static files/folders for Elysia Server. Start by installing the plugin with "bun add @elysiajs/static". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for serving static files/folders for Elysia Server. Start by installing the plugin with "bun add @elysiajs/static". 15 | --- 16 | 17 | # Static Plugin 18 | This plugin can serve static files/folders for Elysia Server 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/static 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { Elysia } from 'elysia' 28 | import { staticPlugin } from '@elysiajs/static' 29 | 30 | new Elysia() 31 | .use(staticPlugin()) 32 | .listen(3000) 33 | 34 | 35 | By default, the static plugin default folder is public, and registered with /public prefix. 36 | 37 | Suppose your project structure is: 38 | 39 | | - src 40 | | - index.ts 41 | | - public 42 | | - takodachi.png 43 | | - nested 44 | | - takodachi.png 45 | 46 | 47 | The available path will become: 48 | - /public/takodachi.png 49 | - /public/nested/takodachi.png 50 | 51 | ## Config 52 | Below is a config which is accepted by the plugin 53 | 54 | ### assets 55 | @default "public" 56 | 57 | Path to the folder to expose as static 58 | 59 | ### prefix 60 | @default "/public" 61 | 62 | Path prefix to register public files 63 | 64 | ### ignorePatterns 65 | @default [] 66 | 67 | List of files to ignore from serving as static files 68 | 69 | ### staticLimit 70 | @default 1024 71 | 72 | By default, the static plugin will register paths to the Router with a static name, if the limits are exceeded, paths will be lazily added to the Router to reduce memory usage. 73 | Tradeoff memory with performance. 74 | 75 | ### alwaysStatic 76 | @default false 77 | 78 | If set to true, static files path will be registered to Router skipping the staticLimits. 79 | 80 | ### headers 81 | @default {} 82 | 83 | Set response headers of files 84 | 85 | ### indexHTML 86 | @default false 87 | 88 | If set to true, the index.html file from the static directory will be served for any request that is matching neither a route nor any existing static file. 89 | 90 | ## Pattern 91 | Below you can find the common patterns to use the plugin. 92 | 93 | - Single File 94 | 95 | ## Single file 96 | Suppose you want to return just a single file, you can use file instead of using the static plugin 97 | typescript 98 | import { Elysia, file } from 'elysia' 99 | 100 | new Elysia() 101 | .get('/file', () => file('public/takodachi.png')) 102 | 103 |


/docs/plugins/stream.md:

1 | --- 2 | title: Stream Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Stream Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for streaming response and Server-Sent Events, eg. OpenAI integration. Start by installing the plugin with "bun add @elysiajs/stream". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for streaming response and Server-Sent Events, eg. OpenAI integration. Start by installing the plugin with "bun add @elysiajs/stream". 15 | --- 16 | 17 | # Stream Plugin 18 | 19 | ::: warning 20 | This plugin is in maintenance mode and will not receive new features. We recommend using the Generator Stream instead 21 | ::: 22 | 23 | This plugin adds support for streaming response or sending Server-Sent Event back to the client. 24 | 25 | Install with: 26 | bash 27 | bun add @elysiajs/stream 28 | 29 | 30 | Then use it: 31 | typescript 32 | import { Elysia } from 'elysia' 33 | import { Stream } from '@elysiajs/stream' 34 | 35 | new Elysia() 36 | .get('/', () => new Stream(async (stream) => { 37 | stream.send('hello') 38 | 39 | await stream.wait(1000) 40 | stream.send('world') 41 | 42 | stream.close() 43 | })) 44 | .listen(3000) 45 | 46 | 47 | By default, Stream will return Response with content-type of text/event-stream; charset=utf8. 48 | 49 | ## Constructor 50 | Below is the constructor parameter accepted by Stream: 51 | 1. Stream: 52 | - Automatic: Automatically stream response from a provided value 53 | - Iterable 54 | - AsyncIterable 55 | - ReadableStream 56 | - Response 57 | - Manual: Callback of (stream: this) => unknown or undefined 58 | 2. Options: StreamOptions 59 | - event: A string identifying the type of event described 60 | - retry: The reconnection time in milliseconds 61 | 62 | ## Method 63 | Below is the method provided by Stream: 64 | 65 | ### send 66 | Enqueue data to stream to send back to the client 67 | 68 | ### close 69 | Close the stream 70 | 71 | ### wait 72 | Return a promise that resolves in the provided value in ms 73 | 74 | ### value 75 | Inner value of the ReadableStream 76 | 77 | ## Pattern 78 | Below you can find the common patterns to use the plugin. 79 | - OpenAI 80 | - Fetch Stream 81 | - Server Sent Event 82 | 83 | ## OpenAI 84 | Automatic mode is triggered when the parameter is either Iterable or AsyncIterable streaming the response back to the client automatically. 85 | 86 | Below is an example of integrating ChatGPT into Elysia. 87 | 88 | ts 89 | new Elysia() 90 | .get( 91 | '/ai', 92 | ({ query: { prompt } }) => 93 | new Stream( 94 | openai.chat.completions.create({ 95 | model: 'gpt-3.5-turbo', 96 | stream: true, 97 | messages: [{ 98 | role: 'user', 99 | content: prompt 100 | }] 101 | }) 102 | ) 103 | ) 104 | 105 | 106 | By default openai chatGPT completion returns AsyncIterable so you should be able to wrap the OpenAI in Stream. 107 | 108 | ## Fetch Stream 109 | You can pass a fetch from an endpoint that returns the stream to proxy a stream. 110 | 111 | This is useful for those endpoints that use AI text generation since you can proxy it directly, eg. Cloudflare AI. 112 | ts 113 | const model = '@cf/meta/llama-2-7b-chat-int8' 114 | const endpoint = `https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/ai/run/${model}` 115 | 116 | new Elysia() 117 | .get('/ai', ({ query: { prompt } }) => 118 | fetch(endpoint, { 119 | method: 'POST', 120 | headers: { 121 | authorization: `Bearer ${API_TOKEN}`, 122 | 'content-type': 'application/json' 123 | }, 124 | body: JSON.stringify({ 125 | messages: [ 126 | { role: 'system', content: 'You are a friendly assistant' }, 127 | { role: 'user', content: prompt } 128 | ] 129 | }) 130 | }) 131 | ) 132 | 133 | 134 | ## Server Sent Event 135 | Manual mode is triggered when the parameter is either callback or undefined, allowing you to control the stream. 136 | 137 | ### callback-based 138 | Below is an example of creating a Server-Sent Event endpoint using a constructor callback 139 | 140 | ts 141 | new Elysia() 142 | .get('/source', () => 143 | new Stream((stream) => { 144 | const interval = setInterval(() => { 145 | stream.send('hello world') 146 | }, 500) 147 | 148 | setTimeout(() => { 149 | clearInterval(interval) 150 | stream.close() 151 | }, 3000) 152 | }) 153 | ) 154 | 155 | 156 | ### value-based 157 | Below is an example of creating a Server-Sent Event endpoint using a value-based 158 | 159 | ts 160 | new Elysia() 161 | .get('/source', () => { 162 | const stream = new Stream() 163 | 164 | const interval = setInterval(() => { 165 | stream.send('hello world') 166 | }, 500) 167 | 168 | setTimeout(() => { 169 | clearInterval(interval) 170 | stream.close() 171 | }, 3000) 172 | 173 | return stream 174 | }) 175 | 176 | 177 | Both callback-based and value-based streams work in the same way but with different syntax for your preference. 178 |


/docs/plugins/swagger.md:

1 | --- 2 | title: Swagger Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Swagger Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for generating Swagger API documentation for Elysia Server. Start by installing the plugin with "bun add @elysiajs/swagger". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for generating Swagger API documentation for Elysia Server. Start by installing the plugin with "bun add @elysiajs/swagger". 15 | --- 16 | 17 | # Swagger Plugin 18 | 19 | This plugin generates a Swagger endpoint for an Elysia server 20 | 21 | Install with: 22 | 23 | bash 24 | bun add @elysiajs/swagger 25 | 26 | 27 | Then use it: 28 | 29 | typescript 30 | import { Elysia } from 'elysia' 31 | import { swagger } from '@elysiajs/swagger' 32 | 33 | new Elysia() 34 | .use(swagger()) 35 | .get('/', () => 'hi') 36 | .post('/hello', () => 'world') 37 | .listen(3000) 38 | 39 | 40 | Accessing /swagger would show you a Scalar UI with the generated endpoint documentation from the Elysia server. You can also access the raw OpenAPI spec at /swagger/json. 41 | 42 | ## Config 43 | 44 | Below is a config which is accepted by the plugin 45 | 46 | ### provider 47 | 48 | @default scalar 49 | 50 | UI Provider for documentation. Default to Scalar. 51 | 52 | ### scalar 53 | 54 | Configuration for customizing Scalar. 55 | 56 | Please refer to the Scalar config 57 | 58 | ### swagger 59 | 60 | Configuration for customizing Swagger. 61 | 62 | Please refer to the Swagger specification. 63 | 64 | ### excludeStaticFile 65 | 66 | @default true 67 | 68 | Determine if Swagger should exclude static files. 69 | 70 | ### path 71 | 72 | @default /swagger 73 | 74 | Endpoint to expose Swagger 75 | 76 | ### exclude 77 | 78 | Paths to exclude from Swagger documentation. 79 | 80 | Value can be one of the following: 81 | 82 | - string 83 | - RegExp 84 | - Array<string | RegExp> 85 | 86 | ## Pattern 87 | 88 | Below you can find the common patterns to use the plugin. 89 | 90 | ## Change Swagger Endpoint 91 | 92 | You can change the swagger endpoint by setting path in the plugin config. 93 | 94 | typescript 95 | import { Elysia } from 'elysia' 96 | import { swagger } from '@elysiajs/swagger' 97 | 98 | new Elysia() 99 | .use( 100 | swagger({ 101 | path: '/v2/swagger' 102 | }) 103 | ) 104 | .listen(3000) 105 | 106 | 107 | ## Customize Swagger info 108 | 109 | typescript 110 | import { Elysia } from 'elysia' 111 | import { swagger } from '@elysiajs/swagger' 112 | 113 | new Elysia() 114 | .use( 115 | swagger({ 116 | documentation: { 117 | info: { 118 | title: 'Elysia Documentation', 119 | version: '1.0.0' 120 | } 121 | } 122 | }) 123 | ) 124 | .listen(3000) 125 | 126 | 127 | ## Using Tags 128 | 129 | Elysia can separate the endpoints into groups by using the Swaggers tag system 130 | 131 | Firstly define the available tags in the swagger config object 132 | 133 | typescript 134 | app.use( 135 | swagger({ 136 | documentation: { 137 | tags: [ 138 | { name: 'App', description: 'General endpoints' }, 139 | { name: 'Auth', description: 'Authentication endpoints' } 140 | ] 141 | } 142 | }) 143 | ) 144 | 145 | 146 | Then use the details property of the endpoint configuration section to assign that endpoint to the group 147 | 148 | typescript 149 | app.get('/', () => 'Hello Elysia', { 150 | detail: { 151 | tags: ['App'] 152 | } 153 | }) 154 | 155 | app.group('/auth', (app) => 156 | app.post( 157 | '/sign-up', 158 | async ({ body }) => 159 | db.user.create({ 160 | data: body, 161 | select: { 162 | id: true, 163 | username: true 164 | } 165 | }), 166 | { 167 | detail: { 168 | tags: ['Auth'] 169 | } 170 | } 171 | ) 172 | ) 173 | 174 | 175 | Which will produce a swagger page like the following 176 | image 177 | 178 | ## Security Configuration 179 | 180 | To secure your API endpoints, you can define security schemes in the Swagger configuration. The example below demonstrates how to use Bearer Authentication (JWT) to protect your endpoints: 181 | 182 | typescript 183 | app.use( 184 | swagger({ 185 | documentation: { 186 | components: { 187 | securitySchemes: { 188 | bearerAuth: { 189 | type: 'http', 190 | scheme: 'bearer', 191 | bearerFormat: 'JWT' 192 | } 193 | } 194 | } 195 | } 196 | }) 197 | ) 198 | 199 | export const addressController = new Elysia({ 200 | prefix: '/address', 201 | detail: { 202 | tags: ['Address'], 203 | security: [ 204 | { 205 | bearerAuth: [] 206 | } 207 | ] 208 | } 209 | }) 210 | 211 | 212 | This configuration ensures that all endpoints under the /address prefix require a valid JWT token for access. 213 |


/docs/plugins/trpc.md:

1 | --- 2 | title: tRPC Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: tRPC Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for using tRPC on Bun with Elysia Server. Start by installing the plugin with "bun add @elysiajs/trpc". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for using tRPC on Bun with Elysia Server. Start by installing the plugin with "bun add @elysiajs/trpc". 15 | --- 16 | 17 | # tRPC Plugin 18 | This plugin adds support for using tRPC 19 | 20 | Install with: 21 | bash 22 | bun add @elysiajs/trpc @trpc/server @elysiajs/websocket 23 | 24 | 25 | Then use it: 26 | typescript 27 | import { compile as c, trpc } from "@elysiajs/trpc"; 28 | import { initTRPC } from "@trpc/server"; 29 | import { Elysia, t as T } from "elysia"; 30 | 31 | const t = initTRPC.create(); 32 | const p = t.procedure; 33 | 34 | const router = t.router({ 35 | greet: p 36 | 37 | // 💡 Using Zod 38 | //.input(z.string()) 39 | // 💡 Using Elysia's T 40 | .input(c(T.String())) 41 | .query(({ input }) => input), 42 | }); 43 | 44 | export type Router = typeof router; 45 | 46 | const app = new Elysia().use(trpc(router)).listen(3000); 47 | 48 | 49 | ## trpc 50 | Accept the tRPC router and register to Elysia's handler. 51 | 52 | type: 53 | 54 | trpc(router: Router, option?: { 55 | endpoint?: string 56 | }): this 57 | 58 | 59 | Router is the TRPC Router instance. 60 | 61 | ### endpoint 62 | The path to the exposed TRPC endpoint. 63 |


/docs/quick-start.md:

1 | --- 2 | title: Quick Start - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Quick Start - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia is a library built for Bun and the only prerequisite. To start, bootstrap a new project with "bun create elysia hi-elysia" and start the development server with "bun dev". This is all it needs to do a quick start or get started with ElysiaJS. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia is a library built for Bun and the only prerequisite. To start, bootstrap a new project with "bun create elysia hi-elysia" and start the development server with "bun dev". This is all it needs to do a quick start or get started with ElysiaJS. 15 | --- 16 | 17 | <script setup> 18 | import Card from '../components/nearl/card.vue' 19 | import Deck from '../components/nearl/card-deck.vue' 20 | import Tab from '../components/fern/tab.vue' 21 | </script> 22 | 23 | # Quick Start 24 | 25 | Elysia is a TypeScript backend framework with multiple runtime support but optimized for Bun. 26 | 27 | However, you can use Elysia with other runtimes like Node.js. 28 | 29 | <Tab 30 | id="quickstart" 31 | :names="['Bun', 'Node.js', 'Web Standard']" 32 | :tabs="['bun', 'node', 'web-standard']" 33 | > 34 | 35 | 36 | 37 | Elysia is optimized for Bun which is a JavaScript runtime that aims to be a drop-in replacement for Node.js. 38 | 39 | You can install Bun with the command below: 40 | 41 | ::: code-group 42 | 43 | bash [MacOS/Linux] 44 | curl -fsSL https://bun.sh/install | bash 45 | 46 | 47 | bash [Windows] 48 | powershell -c "irm bun.sh/install.ps1 | iex" 49 | 50 | 51 | ::: 52 | 53 | <Tab 54 | id="quickstart" 55 | :names="['Auto Installation', 'Manual Installation']" 56 | :tabs="['auto', 'manual']" 57 | > 58 | 59 | 60 | 61 | We recommend starting a new Elysia server using bun create elysia, which sets up everything automatically. 62 | 63 | bash 64 | bun create elysia app 65 | 66 | 67 | Once done, you should see the folder name app in your directory. 68 | 69 | bash 70 | cd app 71 | 72 | 73 | Start a development server by: 74 | 75 | bash 76 | bun dev 77 | 78 | 79 | Navigate to localhost:3000 should greet you with "Hello Elysia". 80 | 81 | ::: tip 82 | Elysia ships you with dev command to automatically reload your server on file change. 83 | ::: 84 | 85 | 86 | 87 | 88 | 89 | To manually create a new Elysia app, install Elysia as a package: 90 | 91 | typescript 92 | bun add elysia 93 | bun add -d @types/bun 94 | 95 | 96 | This will install Elysia and Bun type definitions. 97 | 98 | Create a new file src/index.ts and add the following code: 99 | 100 | typescript 101 | import { Elysia } from 'elysia' 102 | 103 | const app = new Elysia() 104 | .get('/', () => 'Hello Elysia') 105 | .listen(3000) 106 | 107 | console.log( 108 | `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 109 | ) 110 | 111 | 112 | Open your package.json file and add the following scripts: 113 | 114 | json 115 | { 116 | "scripts": { 117 | "dev": "bun --watch src/index.ts", 118 | "build": "bun build src/index.ts --target bun --outdir ./dist", 119 | "start": "NODE_ENV=production bun dist/index.js", 120 | "test": "bun test" 121 | } 122 | } 123 | 124 | 125 | These scripts refer to the different stages of developing an application: 126 | 127 | - dev - Start Elysia in development mode with auto-reload on code change. 128 | - build - Build the application for production usage. 129 | - start - Start an Elysia production server. 130 | 131 | If you are using TypeScript, make sure to create, and update tsconfig.json to include compilerOptions.strict to true: 132 | 133 | json 134 | { 135 | "compilerOptions": { 136 | "strict": true 137 | } 138 | } 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Node.js is a JavaScript runtime for server-side applications, the most popular runtime for JavaScript which Elysia supports. 149 | 150 | You can install Bun with the command below: 151 | 152 | ::: code-group 153 | 154 | bash [MacOS] 155 | brew install node 156 | 157 | 158 | bash [Windows] 159 | choco install nodejs 160 | 161 | 162 | bash [apt (Linux)] 163 | sudo apt install nodejs 164 | 165 | 166 | bash [pacman (Arch)] 167 | pacman -S nodejs npm 168 | 169 | 170 | ::: 171 | 172 | ## Setup 173 | 174 | We recommended using TypeScript for your Node.js project. 175 | 176 | <Tab 177 | id="language" 178 | :names="['TypeScript', 'JavaScript']" 179 | :tabs="['ts', 'js']" 180 | > 181 | 182 | 183 | 184 | To create a new Elysia app with TypeScript, we recommended install Elysia with tsx: 185 | 186 | ::: code-group 187 | 188 | bash [pnpm] 189 | pnpm add elysia @elysiajs/node && \ 190 | pnpm add -d tsx @types/node typescript 191 | 192 | 193 | bash [npm] 194 | npm install elysia @elysiajs/node && \ 195 | npm install --save-dev tsx @types/node typescript 196 | 197 | 198 | bash [yarn] 199 | yarn add elysia && \ 200 | yarn add -d tsx @types/node typescript 201 | 202 | 203 | ::: 204 | 205 | This will install Elysia, TypeScript, and tsx. 206 | 207 | tsx is a CLI that transpiles TypeScript to JavaScript with hot-reload and several more feature you expected from a modern development environment. 208 | 209 | Create a new file src/index.ts and add the following code: 210 | 211 | typescript 212 | import { Elysia } from 'elysia' 213 | import { node } from '@elysiajs/node' 214 | 215 | const app = new Elysia({ adapter: node() }) 216 | .get('/', () => 'Hello Elysia') 217 | .listen(3000, ({ hostname, port }) => { 218 | console.log( 219 | `🦊 Elysia is running at ${hostname}:${port}` 220 | ) 221 | }) 222 | 223 | 224 | Open your package.json file and add the following scripts: 225 | 226 | json 227 | { 228 | "scripts": { 229 | "dev": "tsx watch src/index.ts", 230 | "build": "tsc src/index.ts --outDir dist", 231 | "start": "NODE_ENV=production node dist/index.js" 232 | } 233 | } 234 | 235 | 236 | These scripts refer to the different stages of developing an application: 237 | 238 | - dev - Start Elysia in development mode with auto-reload on code change. 239 | - build - Build the application for production usage. 240 | - start - Start an Elysia production server. 241 | 242 | Make sure to create tsconfig.json 243 | 244 | bash 245 | npx tsc init 246 | 247 | 248 | Don't forget to update tsconfig.json to include compilerOptions.strict to true: 249 | json 250 | { 251 | "compilerOptions": { 252 | "strict": true 253 | } 254 | } 255 | 256 | 257 | 258 | 259 | 260 | 261 | ::: warning 262 | Using Elysia with TypeScript will miss out on some features auto-completion, advanced type checking and end-to-end type safety which is the core feature of Elysia. 263 | ::: 264 | 265 | To create a new Elysia app with JavaScript, starts by installing Elysia: 266 | 267 | ::: code-group 268 | 269 | bash [pnpm] 270 | pnpm add elysia @elysiajs/node 271 | 272 | 273 | bash [npm] 274 | npm install elysia @elysiajs/node 275 | 276 | 277 | bash [yarn] 278 | yarn add elysia @elysiajs/node 279 | 280 | 281 | ::: 282 | 283 | This will install Elysia, TypeScript, and tsx. 284 | 285 | tsx is a CLI that transpiles TypeScript to JavaScript with hot-reload and several more feature you expected from a modern development environment. 286 | 287 | Create a new file src/index.ts and add the following code: 288 | 289 | javascript 290 | import { Elysia } from 'elysia' 291 | import { node } from '@elysiajs/node' 292 | 293 | const app = new Elysia({ adapter: node() }) 294 | .get('/', () => 'Hello Elysia') 295 | .listen(3000, ({ hostname, port }) => { 296 | console.log( 297 | `🦊 Elysia is running at ${hostname}:${port}` 298 | ) 299 | }) 300 | 301 | 302 | Open your package.json file and add the following scripts: 303 | 304 | json 305 | { 306 | "type", "module", 307 | "scripts": { 308 | "dev": "node src/index.ts", 309 | "start": "NODE_ENV=production node src/index.js" 310 | } 311 | } 312 | 313 | 314 | These scripts refer to the different stages of developing an application: 315 | 316 | - dev - Start Elysia in development mode with auto-reload on code change. 317 | - start - Start an Elysia production server. 318 | 319 | Make sure to create tsconfig.json 320 | 321 | bash 322 | npx tsc init 323 | 324 | 325 | Don't forget to update tsconfig.json to include compilerOptions.strict to true: 326 | json 327 | { 328 | "compilerOptions": { 329 | "strict": true 330 | } 331 | } 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | Elysia is a WinterCG compliance library, which means if a framework or runtime supports Web Standard Request/Response, it can run Elysia. 343 | 344 | First, install Elysia with the command below: 345 | 346 | ::: code-group 347 | 348 | bash [bun] 349 | bun install elysia 350 | 351 | 352 | bash [pnpm] 353 | pnpm install elysia 354 | 355 | 356 | bash [npm] 357 | npm install elysia 358 | 359 | 360 | bash [yarn] 361 | yarn add elysia 362 | 363 | 364 | ::: 365 | 366 | Next, select a runtime that supports Web Standard Request/Response. 367 | 368 | We have a few recommendations: 369 | 370 | 371 | 372 | Elysia as Next.js API routes. 373 | 374 | 375 | Elysia as Expo App Router API. 376 | 377 | 378 | Elysia as Astro API routes. 379 | 380 | 381 | Elysia as SvelteKit API routes. 382 | 383 | 384 | 385 | ### Not in the list? 386 | If you are using a custom runtime, you may access app.fetch to handle the request and response manually. 387 | 388 | typescript 389 | import { Elysia } from 'elysia' 390 | 391 | const app = new Elysia() 392 | .get('/', () => 'Hello Elysia') 393 | .listen(3000) 394 | 395 | export default app.fetch 396 | 397 | console.log( 398 | `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 399 | ) 400 | 401 | 402 | 403 | 404 | 405 | 406 | ## Next Steps 407 | 408 | We recommend checking out the either one of the following: 409 | 410 | 411 | 412 | The core concept of Elysia and how to use it. 413 | 414 | 415 | A step-by-step guide walkthrough Elysia's features. 416 | 417 | 418 | 419 | If you have any questions, feel free to ask in our Discord community. 420 |


/docs/recipe/better-auth.md:

1 | --- 2 | title: Better Auth - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Better Auth - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: We may use @better-auth/cli to generate auth schema and migrate our database as well. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: We may use @better-auth/cli to generate auth schema and migrate our database as well. 15 | --- 16 | 17 | # Better Auth 18 | Better Auth is 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. 19 | 20 | Better Auth has a cli tool to generate auth schema and migrate our database as well. It currently has 3 database adapters: 21 | 22 | - Prisma 23 | - Drizzle 24 | - Mongoose 25 | 26 | ## Better Auth CLI 27 | Better Auth has a cli tool to generate auth schema with the following core tables in our database: user, session, account, and verification. More information about the core schema can be found in Better Auth Core Schema. 28 | 29 | To read more on configuring your database, please refer to Better Auth Database. 30 | 31 | To read more on how to use the cli, please refer to Better Auth CLI. 32 | 33 | ## Installation 34 | To install Better Auth, run the following command: 35 | 36 | bash 37 | bun add better-auth 38 | 39 | 40 | Make sure to set your environment variables for better auth secret BETTER_AUTH_SECRET= and other enviroment variables such as Github and Google client id and secret. 41 | 42 | In your project inside the src folder, create a libs/auth or utils/auth folder, and create a auth.ts file inside it and copy the following code: 43 | 44 | ## Better Auth Instance 45 | 46 | ts 47 | import { betterAuth } from "better-auth"; 48 | import { drizzleAdapter } from "better-auth/adapters/drizzle"; 49 | import db from "../../database"; 50 | import { account, session, user, verification } from "../../database/schema"; 51 | export const auth = betterAuth({ 52 | database: drizzleAdapter(db, { // We're using Drizzle as our database 53 | provider: "pg", 54 | /* 55 | * Map your schema into a better-auth schema 56 | */ 57 | schema: { 58 | user, 59 | session, 60 | verification, 61 | account, 62 | }, 63 | }), 64 | socialProviders: { 65 | /* 66 | * We're using Google and Github as our social provider, 67 | * make sure you have set your environment variables 68 | */ 69 | emailAndPassword: { 70 | enabled: true // If you want to use email and password auth 71 | }, 72 | github: { 73 | clientId: process.env.GITHUB_CLIENT_ID!, 74 | clientSecret: process.env.GITHUB_CLIENT_SECRET!, 75 | }, 76 | google: { 77 | clientId: process.env.GOOGLE_CLIENT_ID!, 78 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!, 79 | }, 80 | }, 81 | }); 82 | 83 | 84 | 85 | Now just run to generate an auth schema with the necessary tables. 86 | bash 87 | bunx @better-auth/cli generate --config ./src/libs/auth/auth.ts 88 | 89 | Additionally you can use the --output option to specify the output directory for the generated files. We can then use the drizzle migrate command to migrate our database drizzle-kit migrate. 90 | 91 | ## Better Auth View 92 | 93 | We need to setup a view to handle contexts for better auth. Create a file inside src/utils/auth-view.ts or src/libs/auth/auth-view.ts and copy the following code: 94 | 95 | ts 96 | import { Context } from "elysia"; 97 | import { auth } from "./auth"; 98 | 99 | const betterAuthView = (context: Context) => { 100 | const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"] 101 | if(BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) { 102 | console.log(context.request) 103 | return auth.handler(context.request); 104 | } 105 | else { 106 | context.error(405) 107 | } 108 | } 109 | 110 | export default betterAuthView; 111 | 112 | 113 | ## Better Auth Middleware 114 | 115 | We can setup a simple middleware to handle better auth. Create a file inside src/middlewares/auth-middleware.ts and copy the following code: 116 | 117 | ts 118 | import { Session, User } from "better-auth/types"; 119 | import { auth } from "../../utils/auth/auth"; 120 | import { Context } from "elysia"; 121 | 122 | export const userMiddleware = async (c: Context) => { 123 | const session = await auth.api.getSession({ headers: c.request.headers }); 124 | 125 | if (!session) { 126 | c.set.status = 401; 127 | return { success: 'error', message: "Unauthorized Access: Token is missing" }; 128 | } 129 | 130 | return { 131 | user: session.user, 132 | session: session.session 133 | } 134 | } 135 | 136 | export const userInfo = (user: User | null, session: Session | null) => { 137 | return { 138 | user: user, 139 | session: session 140 | } 141 | } 142 | 143 | 144 | ## Attaching Better Auth Into Our Elysia App 145 | 146 | Inside our index.ts file, we can attach the auth view so that it listens to our auth routes and add the following code: 147 | 148 | ts 149 | const app = new Elysia() 150 | .use(cors()).use(swagger()).all("/api/auth/*", betterAuthView); 151 | 152 | app.listen(process.env.BACKEND_PORT || 8000); 153 | 154 | console.log( 155 | `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 156 | ); 157 | 158 | 159 | Our Auth Should now be working as expected! We can then just access our auth routes from our frontend as such: 160 | 161 | ts 162 | import { createAuthClient } from "better-auth/client" 163 | export const authClient = createAuthClient({ 164 | baseURL: process.env.BETTER_AUTH_URL! 165 | }) 166 | 167 | export const signinGoogle = async () => { 168 | const data = await authClient.signIn.social({ 169 | provider: "google", 170 | }); 171 | 172 | return data; 173 | }; 174 | 175 | 176 | For a detailed client side guide do check out Better Auth Frontend


/docs/recipe/openapi.md:

1 | --- 2 | title: OpenAPI - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: OpenAPI - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Elysia has first-class support and follows OpenAPI schema by default. Allowing any Elysia server to generate a Swagger page and serve as documentation automatically by using just 1 line of the Elysia Swagger plugin. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: Elysia has first-class support and follows OpenAPI schema by default. Allowing any Elysia server to generate a Swagger page and serve as documentation automatically by using just 1 line of the Elysia Swagger plugin. 15 | --- 16 | 17 | # OpenAPI 18 | Elysia has first-class support and follows OpenAPI schema by default. 19 | 20 | Elysia can automatically generate an API documentation page by providing a Swagger plugin. 21 | 22 | To generate the Swagger page, install the plugin: 23 | bash 24 | bun add @elysiajs/swagger 25 | 26 | 27 | And register the plugin to the server: 28 | typescript 29 | import { Elysia } from 'elysia' 30 | import { swagger } from '@elysiajs/swagger' 31 | 32 | const app = new Elysia() 33 | .use(swagger()) 34 | 35 | 36 | By default, Elysia use OpenAPI V3 schema and Scalar UI by default 37 | 38 | For Swagger plugin configuration, see the Swagger plugin page. 39 | 40 | ## Route definitions 41 | We add route information by providing a schema type. 42 | 43 | However, sometime defining a type only isn't clear what the route might work. You can use schema.detail fields to explictly define what the route is all about. 44 | 45 | typescript 46 | import { Elysia, t } from 'elysia' 47 | import { swagger } from '@elysiajs/swagger' 48 | 49 | new Elysia() 50 | .use(swagger()) 51 | .post('/sign-in', ({ body }) => body, { 52 | body: t.Object( 53 | { 54 | username: t.String(), 55 | password: t.String({ 56 | minLength: 8, 57 | description: 'User password (at least 8 characters)' // [!code ++] 58 | }) 59 | }, 60 | { // [!code ++] 61 | description: 'Expected an username and password' // [!code ++] 62 | } // [!code ++] 63 | ), 64 | detail: { // [!code ++] 65 | summary: 'Sign in the user', // [!code ++] 66 | tags: ['authentication'] // [!code ++] 67 | } // [!code ++] 68 | }) 69 | 70 | 71 | The detail fields follows an OpenAPI V3 definition with auto-completion and type-safety by default. 72 | 73 | Detail is then passed to Swagger to put the description to Swagger route. 74 | 75 | ### detail 76 | detail extends the OpenAPI Operation Object 77 | 78 | The detail field is an object that can be use to describe information about the route for API documentation. 79 | 80 | Which may contains the following fields: 81 | 82 | ### tags 83 | An array of tags for the operation. Tags can be used for logical grouping of operations by resources or any other qualifier. 84 | 85 | ### summary 86 | A short summary of what the operation does. 87 | 88 | ### description 89 | A verbose explanation of the operation behavior. 90 | 91 | ### externalDocs 92 | Additional external documentation for this operation. 93 | 94 | ### operationId 95 | A unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. 96 | 97 | ### deprecated 98 | Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is false. 99 | 100 | ### security 101 | A declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement ({}) can be included in the array. 102 | 103 | ## Hide 104 | You can hide the route from the Swagger page by setting detail.hide to true 105 | 106 | typescript 107 | import { Elysia, t } from 'elysia' 108 | import { swagger } from '@elysiajs/swagger' 109 | 110 | new Elysia() 111 | .use(swagger()) 112 | .post('/sign-in', ({ body }) => body, { 113 | body: t.Object( 114 | { 115 | username: t.String(), 116 | password: t.String() 117 | }, 118 | { 119 | description: 'Expected an username and password' 120 | } 121 | ), 122 | detail: { // [!code ++] 123 | hide: true // [!code ++] 124 | } // [!code ++] 125 | }) 126 | 127 | 128 | ## Tags group 129 | Elysia may accept tags to add an entire instance or group of routes to a specific tag. 130 | 131 | typescript 132 | import { Elysia, t } from 'elysia' 133 | 134 | new Elysia({ 135 | tags: ['user'] 136 | }) 137 | .get('/user', 'user') 138 | .get('/admin', 'admin') 139 | 140 | 141 | ## Guard 142 | Alternatively, Elysia may accept guards to add an entire instance or group of routes to a specific guard. 143 | 144 | typescript 145 | import { Elysia, t } from 'elysia' 146 | 147 | new Elysia() 148 | .guard({ 149 | detail: { 150 | description: 'Require user to be logged in' 151 | } 152 | }) 153 | .get('/user', 'user') 154 | .get('/admin', 'admin') 155 | 156 |


/docs/recipe/opentelemetry.md:

1 | --- 2 | title: OpenTelemetry Plugin - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: OpenTelemetry Plugin - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: Plugin for Elysia that adds support for OpenTelemetry. Start by installing the plugin with "bun add @elysiajs/opentelemetry". 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: Plugin for Elysia that adds support for OpenTelemetry. Start by installing the plugin with "bun add @elysiajs/opentelemetry". 15 | --- 16 | 17 | # OpenTelemetry 18 | 19 | To start using OpenTelemetry, install @elysiajs/opentelemetry and apply plugin to any instance. 20 | 21 | typescript 22 | import { Elysia } from 'elysia' 23 | import { opentelemetry } from '@elysiajs/opentelemetry' 24 | 25 | import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' 26 | import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' 27 | 28 | new Elysia() 29 | .use( 30 | opentelemetry({ 31 | spanProcessors: [ 32 | new BatchSpanProcessor( 33 | new OTLPTraceExporter() 34 | ) 35 | ] 36 | }) 37 | ) 38 | 39 | 40 | jaeger showing collected trace automatically 41 | 42 | Elysia OpenTelemetry is will collect span of any library compatible OpenTelemetry standard, and will apply parent and child span automatically. 43 | 44 | In the code above, we apply Prisma to trace how long each query took. 45 | 46 | By applying OpenTelemetry, Elysia will then: 47 | - collect telemetry data 48 | - Grouping relevant lifecycle together 49 | - Measure how long each function took 50 | - Instrument HTTP request and response 51 | - Collect error and exception 52 | 53 | You may export telemetry data to Jaeger, Zipkin, New Relic, Axiom or any other OpenTelemetry compatible backend. 54 | 55 | axiom showing collected trace from OpenTelemetry 56 | 57 | Here's an example of exporting telemetry to Axiom 58 | typescript 59 | import { Elysia } from 'elysia' 60 | import { opentelemetry } from '@elysiajs/opentelemetry' 61 | 62 | import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node' 63 | import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' 64 | 65 | new Elysia() 66 | .use( 67 | opentelemetry({ 68 | spanProcessors: [ 69 | new BatchSpanProcessor( 70 | new OTLPTraceExporter({ 71 | url: 'https://api.axiom.co/v1/traces', // [!code ++] 72 | headers: { // [!code ++] 73 | Authorization: `Bearer ${Bun.env.AXIOM_TOKEN}`, // [!code ++] 74 | 'X-Axiom-Dataset': Bun.env.AXIOM_DATASET // [!code ++] 75 | } // [!code ++] 76 | }) 77 | ) 78 | ] 79 | }) 80 | ) 81 | 82 | 83 | ## OpenTelemetry SDK 84 | Elysia OpenTelemetry is for applying OpenTelemetry to Elysia server only. 85 | 86 | You may use OpenTelemetry SDK normally, and the span is run under Elysia's request span, it will be automatically appear in Elysia trace. 87 | 88 | However, we also provide a getTracer, and record utility to collect span from any part of your application. 89 | 90 | typescript 91 | import { Elysia } from 'elysia' 92 | import { record } from '@elysiajs/opentelemetry' 93 | 94 | export const plugin = new Elysia() 95 | .get('', () => { 96 | return record('database.query', () => { 97 | return db.query('SELECT * FROM users') 98 | }) 99 | }) 100 | 101 | 102 | ## Record utility 103 | record is an equivalent to OpenTelemetry's startActiveSpan but it will handle auto-closing and capture exception automatically. 104 | 105 | You may think of record as a label for your code that will be shown in trace. 106 | 107 | ### Prepare your codebase for observability 108 | Elysia OpenTelemetry will group lifecycle and read the function name of each hook as the name of the span. 109 | 110 | It's a good time to name your function. 111 | 112 | If your hook handler is an arrow function, you may refactor it to named function to understand the trace better, otherwise your trace span will be named as anonymous. 113 | 114 | typescript 115 | const bad = new Elysia() 116 | // ⚠️ span name will be anonymous 117 | .derive(async ({ cookie: { session } }) => { 118 | return { 119 | user: await getProfile(session) 120 | } 121 | }) 122 | 123 | const good = new Elysia() 124 | // ✅ span name will be getProfile 125 | .derive(async function getProfile({ cookie: { session } }) { 126 | return { 127 | user: await getProfile(session) 128 | } 129 | }) 130 | 131 | 132 | ## getCurrentSpan 133 | getCurrentSpan is a utility to get the current span of the current request when you are outside of the handler. 134 | 135 | typescript 136 | import { getCurrentSpan } from '@elysiajs/opentelemetry' 137 | 138 | function utility() { 139 | const span = getCurrentSpan() 140 | span.setAttributes({ 141 | 'custom.attribute': 'value' 142 | }) 143 | } 144 | 145 | 146 | This works outside of the handler by retriving current span from AsyncLocalStorage 147 | 148 | ## setAttribute 149 | setAttribute is a utility to set attribute to the current span. 150 | 151 | typescript 152 | import { setAttribute } from '@elysiajs/opentelemetry' 153 | 154 | function utility() { 155 | setAttribute('custom.attribute', 'value') 156 | } 157 | 158 | 159 | This is a syntax sugar for getCurrentSpan().setAttributes 160 | 161 | ## Configuration 162 | See opentelemetry plugin for configuration option and definition. 163 |


/docs/recipe/react-email.md:

1 | --- 2 | title: React Email - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: React Email - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: As Elysia is using Bun as runtime environment, we may directly use React Email and import JSX directly to our base to send emails. 11 | 12 | - - meta 13 | - name: 'og:description' 14 | content: As Elysia is using Bun as runtime environment, we may directly use React Email and import JSX directly to our code to send emails. 15 | --- 16 | 17 | # React Email 18 | React Email is a library that allows you to use React components to create emails. 19 | 20 | As Elysia is using Bun as runtime environment, we can directly write a React Email component and import the JSX directly to our code to send emails. 21 | 22 | ## Installation 23 | To install React Email, run the following command: 24 | 25 | bash 26 | bun add -d react-email 27 | bun add @react-email/components react react-dom 28 | 29 | 30 | Then add this script to package.json: 31 | json 32 | { 33 | "scripts": { 34 | "email": "email dev --dir src/emails" 35 | } 36 | } 37 | 38 | 39 | We recommend adding email templates into the src/emails directory as we can directly import the JSX files. 40 | 41 | ### TypeScript 42 | If you are using TypeScript, you may need to add the following to your tsconfig.json: 43 | 44 | json 45 | { 46 | "compilerOptions": { 47 | "jsx": "react" 48 | } 49 | } 50 | 51 | 52 | ## Your first email 53 | Create file src/emails/otp.tsx with the following code: 54 | 55 | tsx 56 | import * as React from 'react' 57 | import { Tailwind, Section, Text } from '@react-email/components' 58 | 59 | export default function OTPEmail({ otp }: { otp: number }) { 60 | return ( 61 | <Tailwind> 62 | <Section className="flex justify-center items-center w-full min-h-screen font-sans"> 63 | <Section className="flex flex-col items-center w-76 rounded-2xl px-6 py-1 bg-gray-50"> 64 | <Text className="text-xs font-medium text-violet-500"> 65 | Verify your Email Address 66 | </Text> 67 | <Text className="text-gray-500 my-0"> 68 | Use the following code to verify your email address 69 | </Text> 70 | <Text className="text-5xl font-bold pt-2">{otp}</Text> 71 | <Text className="text-gray-400 font-light text-xs pb-4"> 72 | This code is valid for 10 minutes 73 | </Text> 74 | <Text className="text-gray-600 text-xs"> 75 | Thank you joining us 76 | </Text> 77 | </Section> 78 | </Section> 79 | </Tailwind> 80 | ) 81 | } 82 | 83 | OTPEmail.PreviewProps = { 84 | otp: 123456 85 | } 86 | 87 | 88 | You may notice that we are using @react-email/components to create the email template. 89 | 90 | This library provides a set of components including styling with Tailwind that are compatible with email clients like Gmail, Outlook, etc. 91 | 92 | We also added a PreviewProps to the OTPEmail function. This is only apply when previewing the email on our playground. 93 | 94 | ## Preview your email 95 | To preview your email, run the following command: 96 | 97 | bash 98 | bun email 99 | 100 | 101 | This will open a browser window with the preview of your email. 102 | 103 | React Email playground showing an OTP email we have just written 104 | 105 | ## Sending email 106 | To send an email, we can use react-dom/server to render the the email then submit using a preferred provider: 107 | 108 | ::: code-group 109 | 110 | tsx [Nodemailer] 111 | import { Elysia, t } from 'elysia' 112 | 113 | import * as React from 'react' 114 | import { renderToStaticMarkup } from 'react-dom/server' 115 | 116 | import OTPEmail from './emails/otp' 117 | 118 | import nodemailer from 'nodemailer' // [!code ++] 119 | 120 | const transporter = nodemailer.createTransport({ // [!code ++] 121 | host: 'smtp.gehenna.sh', // [!code ++] 122 | port: 465, // [!code ++] 123 | auth: { // [!code ++] 124 | user: 'makoto', // [!code ++] 125 | pass: '12345678' // [!code ++] 126 | } // [!code ++] 127 | }) // [!code ++] 128 | 129 | new Elysia() 130 | .get('/otp', ({ body }) => { 131 | // Random between 100,000 and 999,999 132 | const otp = ~~(Math.random() * (900_000 - 1)) + 100_000 133 | 134 | const html = renderToStaticMarkup(<OTPEmail otp={otp} />) 135 | 136 | await transporter.sendMail({ // [!code ++] 137 | from: '[email protected]', // [!code ++] 138 | to: body, // [!code ++] 139 | subject: 'Verify your email address', // [!code ++] 140 | html, // [!code ++] 141 | }) // [!code ++] 142 | 143 | return { success: true } 144 | }, { 145 | body: t.String({ format: 'email' }) 146 | }) 147 | .listen(3000) 148 | 149 | 150 | tsx [Resend] 151 | import { Elysia, t } from 'elysia' 152 | 153 | import OTPEmail from './emails/otp' 154 | 155 | import Resend from 'resend' // [!code ++] 156 | 157 | const resend = new Resend('re_123456789') // [!code ++] 158 | 159 | new Elysia() 160 | .get('/otp', ({ body }) => { 161 | // Random between 100,000 and 999,999 162 | const otp = ~~(Math.random() * (900_000 - 1)) + 100_000 163 | 164 | await resend.emails.send({ // [!code ++] 165 | from: '[email protected]', // [!code ++] 166 | to: body, // [!code ++] 167 | subject: 'Verify your email address', // [!code ++] 168 | html: <OTPEmail otp={otp} />, // [!code ++] 169 | }) // [!code ++] 170 | 171 | return { success: true } 172 | }, { 173 | body: t.String({ format: 'email' }) 174 | }) 175 | .listen(3000) 176 | 177 | 178 | tsx [AWS SES] 179 | import { Elysia, t } from 'elysia' 180 | 181 | import * as React from 'react' 182 | import { renderToStaticMarkup } from 'react-dom/server' 183 | 184 | import OTPEmail from './emails/otp' 185 | 186 | import { type SendEmailCommandInput, SES } from '@aws-sdk/client-ses' // [!code ++] 187 | import { fromEnv } from '@aws-sdk/credential-providers' // [!code ++] 188 | 189 | const ses = new SES({ // [!code ++] 190 | credentials: // [!code ++] 191 | process.env.NODE_ENV === 'production' ? fromEnv() : undefined // [!code ++] 192 | }) // [!code ++] 193 | 194 | new Elysia() 195 | .get('/otp', ({ body }) => { 196 | // Random between 100,000 and 999,999 197 | const otp = ~~(Math.random() * (900_000 - 1)) + 100_000 198 | 199 | const html = renderToStaticMarkup(<OTPEmail otp={otp} />) 200 | 201 | await ses.sendEmail({ // [!code ++] 202 | Source: '[email protected]', // [!code ++] 203 | Destination: { // [!code ++] 204 | ToAddresses: [body] // [!code ++] 205 | }, // [!code ++] 206 | Message: { // [!code ++] 207 | Body: { // [!code ++] 208 | Html: { // [!code ++] 209 | Charset: 'UTF-8', // [!code ++] 210 | Data: html // [!code ++] 211 | } // [!code ++] 212 | }, // [!code ++] 213 | Subject: { // [!code ++] 214 | Charset: 'UTF-8', // [!code ++] 215 | Data: 'Verify your email address' // [!code ++] 216 | } // [!code ++] 217 | } // [!code ++] 218 | } satisfies SendEmailCommandInput) // [!code ++] 219 | 220 | return { success: true } 221 | }, { 222 | body: t.String({ format: 'email' }) 223 | }) 224 | .listen(3000) 225 | 226 | 227 | tsx [Sendgrid] 228 | import { Elysia, t } from 'elysia' 229 | 230 | import OTPEmail from './emails/otp' 231 | 232 | import sendgrid from "@sendgrid/mail" // [!code ++] 233 | 234 | sendgrid.setApiKey(process.env.SENDGRID_API_KEY) // [!code ++] 235 | 236 | new Elysia() 237 | .get('/otp', ({ body }) => { 238 | // Random between 100,000 and 999,999 239 | const otp = ~~(Math.random() * (900_000 - 1)) + 100_000 240 | 241 | const html = renderToStaticMarkup(<OTPEmail otp={otp} />) 242 | 243 | await sendgrid.send({ // [!code ++] 244 | from: '[email protected]', // [!code ++] 245 | to: body, // [!code ++] 246 | subject: 'Verify your email address', // [!code ++] 247 | html // [!code ++] 248 | }) // [!code ++] 249 | 250 | return { success: true } 251 | }, { 252 | body: t.String({ format: 'email' }) 253 | }) 254 | .listen(3000) 255 | 256 | 257 | ::: 258 | 259 | ::: tip 260 | Notice that we can directly import the email component out of the box thanks to Bun 261 | ::: 262 | 263 | You may see all of the available integration with React Email in the React Email Integration, and learn more about React Email in React Email documentation 264 |


/docs/table-of-content.md:

1 | --- 2 | title: Table of Content - ElysiaJS 3 | head: 4 | - - meta 5 | - property: 'og:title' 6 | content: Table of Content - ElysiaJS 7 | 8 | - - meta 9 | - name: 'description' 10 | content: There's no correct or organized way to learn Elysia, however, we recommended completing the essential chapter first as the chapter briefly covers most of Elysia's features and foundation before jumping to other topics that interest you. Once you've completed the essential chapter, you may jump to any topic that interests you. However, we recommended following the order of the chapter as it may reference to previous chapter. 11 | 12 | - - meta 13 | - property: 'og:description' 14 | content: There's no correct or organized way to learn Elysia, however, we recommended completing the essential chapter first as the chapter briefly covers most of Elysia's features and foundation before jumping to other topics that interest you. Once you've completed the essential chapter, you may jump to any topic that interests you. However, we recommended following the order of the chapter as it may reference to previous chapter. 15 | --- 16 | 17 | <script setup> 18 | import Card from '../components/nearl/card.vue' 19 | import Deck from '../components/nearl/card-deck.vue' 20 | </script> 21 | 22 | # Table of Content 23 | There's no correct way to learn Elysia, but we recommended completing the essential chapter first as the chapter briefly covers most of Elysia's features and foundation before jumping to other topics that interest you. 24 | 25 | 26 | 27 | A quick overview of Elysia 28 | 29 | 30 | Understand how to structure your code 31 | 32 | 33 | Enforce data type and create a unified type 34 | 35 | 36 | Intercept events and customize behaviors 37 | 38 | 39 | Understand how to separate Elysia, and scope 40 | 41 | 42 | End-to-end type safety client for Elysia 43 | 44 | 45 | 46 | --- 47 | 48 | Once you've completed the essential chapter, you may jump to any topic that interests you. We have organized a recommended chapter in order as it may reference to previous chapter. 49 | 50 | ### Prerequisite Knowledge 51 | Although Elysia's documentation is designed to be beginner-friendly, we need to establish a baseline so that the docs can stay focused on Elysia's functionality. We will provide links to relevant documentation whenever we introduce a new concept. 52 | 53 | To get the most out of our documentation, it's recommended that you have a basic understanding of Node.js and basic HTTP. 54 |

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment