--- url: 'https://elysiajs.com/plugins/graphql-apollo.md' --- # GraphQL Apollo Plugin Plugin for [elysia](https://github.com/elysiajs/elysia) for using GraphQL Apollo. Install with: ```bash bun add graphql @elysia/apollo @apollo/server ``` Then use it: ```typescript import { Elysia } from 'elysia' import { apollo, gql } from '@elysia/apollo' const app = new Elysia() .use( apollo({ typeDefs: gql` type Book { title: String author: String } type Query { books: [Book] } `, resolvers: { Query: { books: () => { return [ { title: 'Elysia', author: 'saltyAom' } ] } } } }) ) .listen(3000) ``` Accessing `/graphql` should show the Apollo GraphQL playground to work with. ## Context 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. Because of this, Elysia replaces both with `context` like route parameters. ```typescript const app = new Elysia() .use( apollo({ typeDefs, resolvers, context: async ({ request }) => { const authorization = request.headers.get('Authorization') return { authorization } } }) ) .listen(3000) ``` ## Config This plugin extends Apollo's [ServerRegistration](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#options) (which is `ApolloServer`'s' constructor parameter). Below are the extended parameters for configuring Apollo Server with Elysia. ### path @default `"/graphql"` Path to expose Apollo Server. ### enablePlayground @default `process.env.ENV !== 'production'` Determine whether Apollo should provide Apollo Playground. --- --- url: 'https://elysiajs.com/at-glance.md' --- # At a glance Elysia is an ergonomic web framework for building backend servers with Bun. Designed with simplicity and type-safety in mind, Elysia offers a familiar API with extensive support for TypeScript and is optimized for Bun. Here's a simple hello world in Elysia. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', 'Hello Elysia') .get('/user/:id', ({ params: { id }}) => id) .post('/form', ({ body }) => body) .listen(3000) ``` Navigate to [localhost:3000](http://localhost:3000/) and you should see 'Hello Elysia' as the result. ::: tip Hover over the code snippet to see the type definition. In the mock browser, click on the path highlighted in blue to change paths and preview the response. Elysia can run in the browser, and the results you see are actually executed using Elysia. ::: ## Performance Building on Bun and extensive optimization like static code analysis allows Elysia to generate optimized code on the fly. Elysia can outperform most web frameworks available today\[1], and even match the performance of Golang and Rust frameworks\[2]. | Framework | Runtime | Average | Plain Text | Dynamic Parameters | JSON Body | | ------------- | ------- | ----------- | ---------- | ------------------ | ---------- | | bun | bun | 262,660.433 | 326,375.76 | 237,083.18 | 224,522.36 | | elysia | bun | 255,574.717 | 313,073.64 | 241,891.57 | 211,758.94 | | hyper-express | node | 234,395.837 | 311,775.43 | 249,675 | 141,737.08 | | hono | bun | 203,937.883 | 239,229.82 | 201,663.43 | 170,920.4 | | h3 | node | 96,515.027 | 114,971.87 | 87,935.94 | 86,637.27 | | oak | deno | 46,569.853 | 55,174.24 | 48,260.36 | 36,274.96 | | fastify | bun | 65,897.043 | 92,856.71 | 81,604.66 | 23,229.76 | | fastify | node | 60,322.413 | 71,150.57 | 62,060.26 | 47,756.41 | | koa | node | 39,594.14 | 46,219.64 | 40,961.72 | 31,601.06 | | express | bun | 29,715.537 | 39,455.46 | 34,700.85 | 14,990.3 | | express | node | 15,913.153 | 17,736.92 | 17,128.7 | 12,873.84 | ## TypeScript Elysia is designed to help you write less TypeScript. Elysia's Type System is fine-tuned to infer types from your code automatically, without needing to write explicit TypeScript, while providing type-safety at both runtime and compile time for the most ergonomic developer experience. Take a look at this example: ```typescript twoslash import { Elysia } from 'elysia' new Elysia() .get('/user/:id', ({ params: { id } }) => id) // ^? .listen(3000) ``` 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. Elysia's goal is to help you write less TypeScript and focus more on business logic. Let the framework handle the complex types. TypeScript is not required to use Elysia, but it's recommended. ## Type Integrity To take it a step further, Elysia provides **Elysia.t**, a schema builder to validate types and values at both runtime and compile time, creating a single source of truth for your data types. Let's modify the previous code to accept only a number value instead of a string. ```typescript twoslash import { Elysia, t } from 'elysia' new Elysia() .get('/user/:id', ({ params: { id } }) => id, { // ^? params: t.Object({ id: t.Number() }) }) .listen(3000) ``` This code ensures that our path parameter **id** will always be a number at both runtime and compile time (type-level). ::: tip Hover over "id" in the above code snippet to see a type definition. ::: With Elysia's schema builder, we can ensure type safety like a strongly typed language with a single source of truth. ## Standard Schema Elysia supports [Standard Schema](https://github.com/standard-schema/standard-schema), allowing you to use your favorite validation library: * Zod * Valibot * ArkType * Effect Schema * Yup * Joi * [and more](https://github.com/standard-schema/standard-schema) ```typescript twoslash import { Elysia } from 'elysia' import { z } from 'zod' import * as v from 'valibot' new Elysia() .get('/id/:id', ({ params: { id }, query: { name } }) => id, { // ^? params: z.object({ id: z.coerce.number() }), query: v.object({ name: v.literal('Lilith') }) }) .listen(3000) ``` Elysia will infer the types from the schema automatically, allowing you to use your favorite validation library while still maintaining type safety. ## OpenAPI Elysia adopts many standards by default, like OpenAPI, WinterTC compliance, and Standard Schema. Allowing you to integrate with most of the industry standard tools or at least easily integrate with tools you are familiar with. For instance, because Elysia adopts OpenAPI by default, generating API documentation is as easy as adding a one-liner: ```typescript import { Elysia, t } from 'elysia' import { openapi } from '@elysia/openapi' new Elysia() .use(openapi()) // [!code ++] .get('/user/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) .listen(3000) ``` With the OpenAPI plugin, you can seamlessly generate an API documentation page without additional code or specific configuration and share it with your team effortlessly. ## OpenAPI from types Elysia also supports OpenAPI schema generation with **1 line directly from types**. This is a **unique feature** of Elysia, allowing you to have complete and accurate API documentation directly from your code without any manual annotation. ```typescript import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use(openapi({ references: fromTypes() // [!code ++] })) .get('/user/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) .listen(3000) ``` This is equivalent to **FastAPI**'s automatic OpenAPI generation from types but in TypeScript. ## End-to-end Type Safety With Elysia, type safety is not limited to server-side. With Elysia, you can synchronize your types with your frontend team automatically, similar to tRPC, using Elysia's client library, "Eden". ```typescript import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysia/openapi' export const app = new Elysia() .use(openapi({ references: fromTypes() })) .get('/user/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) .listen(3000) export type App = typeof app // [!code ++] ``` And on your client-side: ```typescript twoslash // @filename: server.ts import { Elysia, t } from 'elysia' const app = new Elysia() .get('/user/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) .listen(3000) export type App = typeof app // @filename: client.ts // ---cut--- // client.ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') // Get data from /user/617 const { data } = await app.user({ id: 617 }).get() // ^? console.log(data) ``` 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. Elysia is not only about helping you create a confident backend but for all that is beautiful in this world. ## Type Soundness Most frameworks with end-to-end type safety usually assume only a happy part, leaving error handling and edge cases out of the type system. However, Elysia can infers all of the possible outcomes of your API, from lifecycle events/macro from any part of your codebase. ::: code-group ```typescript twoslash [client.ts] // @filename: server.ts import { Elysia, t } from 'elysia' const plugin = new Elysia() .macro({ auth: { cookie: t.Object({ session: t.String() }), beforeHandle({ cookie: { session }, status }) { if(session.value !== 'valid') return status(401) } } }) const app = new Elysia() .use(plugin) .get('/user/:id', ({ params: { id }, status }) => { if(Math.random() > 0.1) return status(420) return id }, { auth: true, params: t.Object({ id: t.Number() }) }) .listen(3000) export type App = typeof app // @filename: client.ts // ---cut--- // client.ts import { treaty } from '@elysia/eden' import type { App } from './server' const app = treaty('localhost:3000') // Get data from /user/617 const { data, error } = await app.user({ id: 617 }).get() // ^? console.log(data) ``` ```typescript [server.ts] import { Elysia, t } from 'elysia' const plugin = new Elysia() .macro({ auth: { cookie: t.Object({ session: t.String() }), beforeHandle({ cookie: { session }, status }) { if(session.value !== 'valid') return status(401) } } }) const app = new Elysia() .use(plugin) .get('/user/:id', ({ params: { id }, status }) => { if(Math.random() > 0.1) return status(420) return id }, { auth: true, params: t.Object({ id: t.Number() }) }) .listen(3000) export type App = typeof app ``` ::: This is one of the **unfair advantages** that Elysia has from years of investment in type system. ## Platform Agnostic Elysia is optimized for Bun with native feature but **not limited to Bun**. Being [WinterTC compliant](https://wintertc.org/) allows you to deploy Elysia servers on: * Bun * [Node.js](/integrations/node) * [Deno](/integrations/deno) * [Cloudflare Worker](/integrations/cloudflare-worker) * [Vercel](/integrations/vercel) * [Expo](/integrations/expo) via API routes * [Nextjs](/integrations/nextjs) via API routes * [Astro](/integrations/astro) via API routes and several more! Checkout `integration` section on sidebar for more support runtime. ## Our Community We want to create a friendly and welcoming community for everyone with bright, cute, friendly and playful design including our hand drawn anime girl mascot, *"Elysia chan"*. We believe that technology should be friendly and approachable instead of being serious all the time, to brings joy to people's lives. But even that, **we take Elysia very seriously** to make sure it's reliable and production ready high-performance framework that can be trusted for your next project. Elysia is **used in production by many companies and projects worldwide**, including [X](https://x.com/shlomiatar/status/1822381556142362734), [Bank for Agriculture and Agricultural Co-operatives](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13924470), [Cluely](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-14420139), [CS.Money](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13913513), [Abacate Pay](https://github.com/elysiajs/elysia/discussions/1312#discussioncomment-13922081) and used by [over 10,000 (open source) projects on GitHub.](https://github.com/elysiajs/elysia/network/dependents) and has been actively developed and maintained since 2022 with many regular contributors from our community. Elysia is a reliable choice and production ready for building your next backend server. Here's on of our community resources to get you started: *** 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](https://github.com/SaltyAom/bun-http-framework-benchmark/tree/c7e26fe3f1bfee7ffbd721dbade10ad72a0a14ab#results). 2\. Based on [TechEmpower Benchmark round 22](https://www.techempower.com/benchmarks/#section=data-r22\&hw=ph\&test=composite). --- --- url: 'https://elysiajs.com/plugins/bearer.md' --- # Bearer Plugin Plugin for [elysia](https://github.com/elysiajs/elysia) for retrieving the Bearer token. Install with: ```bash bun add @elysia/bearer ``` Then use it: ```typescript import { Elysia } from 'elysia' import { bearer } from '@elysia/bearer' const app = new Elysia() .use(bearer()) .get('/sign', ({ bearer }) => bearer, { beforeHandle({ bearer, set, status }) { if (!bearer) { set.headers[ 'WWW-Authenticate' ] = `Bearer realm='sign', error="invalid_request"` return status(400, 'Unauthorized') } } }) .listen(3000) ``` This plugin is for retrieving a Bearer token specified in [RFC6750](https://www.rfc-editor.org/rfc/rfc6750#section-2). This plugin DOES NOT handle authentication validation for your server. Instead, the plugin leaves the decision to developers to apply the logic for handling validation checks themselves. --- --- url: 'https://elysiajs.com/essential/best-practice.md' --- # Best Practice Elysia is a pattern-agnostic framework, leaving the decision of which coding patterns to use up to you and your team. However, there are several concerns when trying to adapt an MVC pattern [(Model-View-Controller)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) with Elysia, and we found it hard to decouple and handle types. This page is a guide on how to follow Elysia structure best practices combined with the MVC pattern, but it can be adapted to any coding pattern you prefer. ## Folder Structure Elysia is unopinionated about folder structure, leaving you to **decide** how to organize your code yourself. However, **if you don't have a specific structure in mind**, we recommend a feature-based folder structure where each feature has its own folder containing controllers, services, and models. ``` | src | modules | auth | index.ts (Elysia controller) | service.ts (service) | model.ts (model) | user | index.ts (Elysia controller) | service.ts (service) | model.ts (model) | utils | a | index.ts | b | index.ts ``` This structure allows you to easily find and manage your code and keep related code together. Here's an example code of how to distribute your code into a feature-based folder structure: ::: code-group ```typescript [auth/index.ts] import { Elysia } from 'elysia' import { Auth } from './service' import { AuthModel } from './model' export const auth = new Elysia({ prefix: '/auth' }) .get( '/sign-in', async ({ body, cookie: { session } }) => { const response = await Auth.signIn(body) // Set session cookie // (Elysia cookie is proxy, it can never be null/undefined) session!.value = response.token return response }, { body: AuthModel.signInBody, // response is optional, use to enforce return type response: { 200: AuthModel.signInResponse, 400: AuthModel.signInInvalid } } ) ``` ```typescript [auth/service.ts] // Service handles business logic, decoupled from Elysia controller import { status } from 'elysia' import type { AuthModel } from './model' // If a class doesn't need to store a property, // you can use an `abstract class` to avoid class allocation export abstract class Auth { static async signIn({ username, password }: AuthModel['signInBody']) { const user = await sql` SELECT password FROM users WHERE username = ${username} LIMIT 1` if (!await Bun.password.verify(password, user.password)) // You can throw an HTTP error directly throw status( 400, 'Invalid username or password' satisfies AuthModel['signInInvalid'] ) return { username, token: await generateAndSaveTokenToDB(user.id) } } } ``` ```typescript [auth/model.ts] // Model define the data structure and validation for the request and response import { t, type UnwrapSchema } from 'elysia' export const AuthModel = { signInBody: t.Object({ username: t.String(), password: t.String(), }), signInResponse: t.Object({ username: t.String(), token: t.String(), }), signInInvalid: t.Literal('Invalid username or password') } as const // Optional, cast all model to TypeScript type export type AuthModel = { [k in keyof typeof AuthModel]: UnwrapSchema } ``` ::: Each file has its own responsibility: * **Controller**: Handles HTTP routing, request validation, and cookies. * **Service**: Handles business logic, decoupled from the Elysia controller if possible. * **Model**: Defines the data structure and validation for the request and response. Feel free to adapt this structure to your needs and use any coding pattern you prefer. ::: note You may get a warning when using `cookie.name` as it might be `undefined` depending on your TypeScript configuration. Elysia cookie can never be `undefined` because it's a Proxy object. `cookie` is always defined, only its value (via cookie.value) can be undefined. This can be fixed by using a \[cookie schema] or disable [strictNullChecks](https://www.typescriptlang.org/tsconfig/#strictNullChecks) in `tsconfig.json` ::: ## Controller Due to the type soundness of Elysia, it's not recommended to use a traditional controller class that is tightly coupled with Elysia's `Context` because: 1. **Elysia types are complex** and heavily depend on plugins and multiple levels of chaining. 2. **Hard to type**; Elysia types could change at any time, especially with decorators and store. 3. **Loss of type integrity** and inconsistency between types and runtime code. We recommend one of the following approaches to implement a controller in Elysia. 1. Use Elysia instance as a controller itself 2. Create a controller that is not tied with HTTP request or Elysia. *** ### 1. Elysia instance as a controller > 1 Elysia instance = 1 controller Treat an Elysia instance as a controller, and define your routes directly on the Elysia instance. ```typescript // ✅ Do import { Elysia } from 'elysia' import { Service } from './service' new Elysia() .get('/', ({ stuff }) => { Service.doStuff(stuff) }) ``` This approach allows Elysia to infer the `Context` type automatically, ensuring type integrity and consistency between types and runtime code. ```typescript // ❌ Don't import { Elysia, t, type Context } from 'elysia' abstract class Controller { static root(context: Context) { return Service.doStuff(context.stuff) } } new Elysia() .get('/', Controller.root) ``` This approach makes it hard to type `Context` properly, and may lead to loss of type integrity. ### 2. Controller without HTTP request If you want to create a controller class, we recommend creating a class that is not tied to HTTP request or Elysia at all. This approach allows you to decouple the controller from Elysia, making it easier to test, reuse, and even swap a framework while still following the MVC pattern. ```typescript import { Elysia } from 'elysia' abstract class Controller { static doStuff(stuff: string) { return Service.doStuff(stuff) } } new Elysia() .get('/', ({ stuff }) => Controller.doStuff(stuff)) ``` Tying the controller to the Elysia Context may lead to: 1. Loss of type integrity 2. Making it harder to test and reuse 3. Vendor lock-in We recommend keeping the controller decoupled from Elysia as much as possible. ### ❌ Don't: Pass entire `Context` to a controller **Context is a highly dynamic type** that can be inferred from Elysia instance. Do not pass an entire `Context` to a controller, instead use object destructuring to extract what you need and pass it to the controller. ```typescript import type { Context } from 'elysia' abstract class Controller { constructor() {} // ❌ Don't do this static root(context: Context) { return Service.doStuff(context.stuff) } } ``` This approach makes it hard to type `Context` properly, and may lead to loss of type integrity. ### Testing If you're using Elysia as a controller, you can test your controller using `handle` to directly call a function (and it's lifecycle) ```typescript import { Elysia } from 'elysia' import { Service } from './service' import { describe, it, expect } from 'bun:test' const app = new Elysia() .get('/', ({ stuff }) => { Service.doStuff(stuff) return 'ok' }) describe('Controller', () => { it('should work', async () => { const response = await app .handle(new Request('http://localhost/')) .then((x) => x.text()) expect(response).toBe('ok') }) }) ``` You may find more information about testing in [Unit Test](/patterns/unit-test.html). ## Service A service is a set of utility/helper functions decoupled as business logic to use in a module/controller, in our case, an Elysia instance. Any technical logic that can be decoupled from controller may live inside a **Service**. There are 2 types of service in Elysia: 1. Non-request dependent service 2. Request dependent service ### 1. Abstract away Non-request dependent service We recommend abstracting service classes/functions away from Elysia. If the service or function isn't tied to an HTTP request or doesn't access a `Context`, it's recommended to implement it as a static class or function. ```typescript import { Elysia, t } from 'elysia' abstract class Service { static fibo(number: number): number { if(number < 2) return number return Service.fibo(number - 1) + Service.fibo(number - 2) } } new Elysia() .get('/fibo', ({ body }) => { return Service.fibo(body) }, { body: t.Numeric() }) ``` If your service doesn't need to store a property, you can use an `abstract class` and `static` methods to avoid allocating a class instance. ### 2. Request dependent service as Elysia instance **If the service is a request-dependent service** or needs to process HTTP requests, we recommend abstracting it as an Elysia instance to ensure type integrity and inference: ```typescript import { Elysia } from 'elysia' // ✅ Do const AuthService = new Elysia({ name: 'Auth.Service' }) .macro({ isSignIn: { resolve({ cookie, status }) { if (!cookie.session.value) return status(401, 'Unauthorized') return { session: cookie.session.value, } } } }) const UserController = new Elysia() .use(AuthService) .get('/profile', ({ Auth: { user } }) => user, { isSignIn: true }) ``` ::: tip Elysia handles [plugin deduplication](/essential/plugin.html#plugin-deduplication) by default, so you don't have to worry about performance, as it will be a singleton if you specify a **"name"** property ::: ### ✅ Do: Decorate only request dependent property It's recommended to `decorate` only for request-dependent properties, such as `requestIP`, `requestTime`, or `session`. Overusing decorators ties your code to Elysia, making it harder to test and reuse. ```typescript import { Elysia } from 'elysia' new Elysia() .decorate('requestIP', ({ request }) => request.headers.get('x-forwarded-for') || request.ip) .decorate('requestTime', () => Date.now()) .decorate('session', ({ cookie }) => cookie.session.value) .get('/', ({ requestIP, requestTime, session }) => { return { requestIP, requestTime, session } }) ``` ## Model Models or [DTOs (Data Transfer Objects)](https://en.wikipedia.org/wiki/Data_transfer_object) are handled by [Elysia.t (Validation)](/essential/validation.html#elysia-type). Elysia has a built-in validation system that can infer types from your code and validate them at runtime. ### ✅ Do: Use Elysia's validation system Elysia's strength is prioritizing a single source of truth for both types and runtime validation. Instead of declaring an interface, reuse validation's model instead: ```typescript twoslash // ✅ Do import { Elysia, t, type UnwrapSchema } from 'elysia' export const models = { customBody: t.Object({ username: t.String(), password: t.String() }) } // Optional if you want to extract the type from the model type CustomBody = UnwrapSchema // ^? // Or make the entire object as type type Models = { [k in keyof typeof models]: UnwrapSchema } // ❌ Don't: declare model and type separately interface ICustomBody { username: string password: string } ``` We can get type of model by using `typeof` with `.static` property from the model. Then you can use the `CustomBody` type to infer the type of the request body. ```typescript twoslash import { Elysia, t } from 'elysia' const models = { customBody: t.Object({ username: t.String(), password: t.String() }) } // ---cut--- // ✅ Do new Elysia() .post('/login', ({ body }) => { // ^? return body }, { body: models.customBody }) ``` ### ❌ Don't: Declare a class instance as a model Do not declare a class instance as a model: ```typescript // ❌ Don't class CustomBody { username: string password: string constructor(username: string, password: string) { this.username = username this.password = password } } // ❌ Don't interface ICustomBody { username: string password: string } ``` ### Group You can group multiple models into a single object to make it more organized. ```typescript import { Elysia, t } from 'elysia' export const AuthModel = { sign: t.Object({ username: t.String(), password: t.String() }) } const models = AuthModel.models ``` ### Model Injection Though this is optional, if you are strictly following MVC pattern, you may want to inject like a service into a controller. We recommended using [Elysia reference model](/essential/validation#reference-model) Using Elysia's model reference ```typescript twoslash import { Elysia, t } from 'elysia' const customBody = t.Object({ username: t.String(), password: t.String() }) const AuthModel = new Elysia() .model({ sign: customBody }) const models = AuthModel.models const UserController = new Elysia({ prefix: '/auth' }) .use(AuthModel) .prefix('model', 'auth.') .post('/sign-in', async ({ body, cookie: { session } }) => { // ^? return true }, { body: 'auth.Sign' }) ``` This approach provides several benefits: 1. Allows you to name a model and provide auto-completion. 2. Modifies schemas for later usage, or performs a [remap](/essential/handler.html#remap). 3. Shows up as "models" in OpenAPI-compliant clients, eg. OpenAPI. 4. Improves TypeScript inference speed as model types will be cached during registration. --- --- url: 'https://elysiajs.com/integrations/better-auth.md' --- # Better Auth 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. We recommend going through the [Better Auth basic setup](https://www.better-auth.com/docs/installation) before going through this page. Our basic setup will look like this: ```ts [auth.ts] import { betterAuth } from 'better-auth' import { Pool } from 'pg' export const auth = betterAuth({ database: new Pool() }) ``` ## Handler After setting up the Better Auth instance, we can mount it to Elysia via [mount](/patterns/mount.html). We need to mount the handler to an Elysia endpoint. ```ts [index.ts] import { Elysia } from 'elysia' import { auth } from './auth' const app = new Elysia() .mount(auth.handler) // [!code ++] .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Then we can access Better Auth at `http://localhost:3000/api/auth`. ### Custom endpoint We recommend setting a prefix path when using [mount](/patterns/mount.html). ```ts [index.ts] import { Elysia } from 'elysia' const app = new Elysia() .mount('/auth', auth.handler) // [!code ++] .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` Then we can access Better Auth at `http://localhost:3000/auth/api/auth`. But the URL looks redundant, so we can customize the `/api/auth` prefix to something else in the Better Auth instance. ```ts import { betterAuth } from 'better-auth' import { Pool } from 'pg' export const auth = betterAuth({ basePath: '/api' // [!code ++] }) ``` Then we can access Better Auth at `http://localhost:3000/auth/api`. Unfortunately, we can't set the `basePath` of a Better Auth instance to be empty or `/`. ## OpenAPI Better Auth supports `openapi` with `better-auth/plugins`. However, if we are using [@elysia/openapi](/plugins/openapi), you might want to extract the documentation from the Better Auth instance. First, we need to add the `openAPI` plugin to our Better Auth instance: ```ts [auth.ts] import { betterAuth } from 'better-auth' import { openAPI } from 'better-auth/plugins' // [!code ++] import { Pool } from 'pg' export const auth = betterAuth({ database: new Pool(), plugins: [openAPI()] // [!code ++] }) ``` ::: tip The `openAPI()` plugin is required for `auth.api.generateOpenAPISchema` to be available. Without it, you will get a type error: `Property 'generateOpenAPISchema' does not exist`. ::: Then we may extract the OpenAPI schema with the following code: ```ts [auth.ts] let _schema: ReturnType const getSchema = async () => (_schema ??= auth.api.generateOpenAPISchema()) export const OpenAPI = { getPaths: (prefix = '/auth/api') => getSchema().then(({ paths }) => { const reference: typeof paths = Object.create(null) for (const path of Object.keys(paths)) { const key = prefix + path reference[key] = paths[path] for (const method of Object.keys(paths[path])) { const operation = (reference[key] as any)[method] operation.tags = ['Better Auth'] } } return reference }) as Promise, components: getSchema().then(({ components }) => components) as Promise } as const ``` Then in our Elysia instance that uses `@elysia/openapi`. ```ts import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' import { OpenAPI } from './auth' const app = new Elysia().use( openapi({ documentation: { components: await OpenAPI.components, paths: await OpenAPI.getPaths() } }) ) ``` ## CORS To configure CORS, you can use the `cors` plugin from `@elysia/cors`. ```ts import { Elysia } from 'elysia' import { cors } from '@elysia/cors' import { auth } from './auth' const app = new Elysia() .use( cors({ origin: 'http://localhost:3001', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], credentials: true, allowedHeaders: ['Content-Type', 'Authorization'] }) ) .mount(auth.handler) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` ## Macro You can use [macro](https://elysiajs.com/patterns/macro.html#macro) with [resolve](https://elysiajs.com/essential/handler.html#resolve) to provide session and user information before passing to the view. ```ts import { Elysia } from 'elysia' import { auth } from './auth' // user middleware (compute user and session and pass to routes) const betterAuth = new Elysia({ name: 'better-auth' }) .mount(auth.handler) .macro({ auth: { async resolve({ status, request: { headers } }) { const session = await auth.api.getSession({ headers }) if (!session) return status(401) return { user: session.user, session: session.session } } } }) const app = new Elysia() .use(betterAuth) .get('/user', ({ user }) => user, { auth: true }) .listen(3000) console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ``` This will allow you to access the `user` and `session` objects in all of your routes. --- --- url: 'https://elysiajs.com/integrations/cheat-sheet.md' --- # Cheat Sheet Here is a quick overview of common Elysia patterns ## Hello World A simple hello world ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'Hello World') .listen(3000) ``` ## Custom HTTP Method Define routes using custom HTTP methods/verbs See [Route](/essential/route.html#custom-method) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/hi', () => 'Hi') .post('/hi', () => 'From Post') .put('/hi', () => 'From Put') .route('M-SEARCH', '/hi', () => 'Custom Method') .listen(3000) ``` ## Path Parameter Using dynamic path parameters See [Path](/essential/route.html#path-type) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/id/:id', ({ params: { id } }) => id) .get('/rest/*', () => 'Rest') .listen(3000) ``` ## Return JSON Elysia converts response to JSON automatically See [Handler](/essential/handler.html) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/json', () => { return { hello: 'Elysia' } }) .listen(3000) ``` ## Return a file A file can be returned as a formdata response The response must be a 1-level deep object ```typescript import { Elysia, file } from 'elysia' new Elysia() .get('/json', () => { return { hello: 'Elysia', image: file('public/cat.jpg') } }) .listen(3000) ``` ## Header and status Set a custom header and a status code See [Handler](/essential/handler.html) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ set, status }) => { set.headers['x-powered-by'] = 'Elysia' return status(418, "I'm a teapot") }) .listen(3000) ``` ## Group Define a prefix once for subroutes See [Group](/essential/route.html#group) ```typescript import { Elysia } from 'elysia' new Elysia() .get("/", () => "Hi") .group("/auth", app => { return app .get("/", () => "Hi") .post("/sign-in", ({ body }) => body) .put("/sign-up", ({ body }) => body) }) .listen(3000) ``` ## Schema Enforce a data type on a route See [Validation](/essential/validation) ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/mirror', ({ body: { username } }) => username, { body: t.Object({ username: t.String(), password: t.String() }) }) .listen(3000) ``` ## File upload See [Validation#file](/essential/validation#file) ```typescript import { Elysia, t } from 'elysia' new Elysia() .post('/body', ({ body }) => body, { body: t.Object({ file: t.File({ format: 'image/*' }), multipleFiles: t.Files() }) }) .listen(3000) ``` ## Lifecycle Hook Intercept Elysia events in order See [Lifecycle](/essential/life-cycle.html) ```typescript import { Elysia, t } from 'elysia' new Elysia() .onRequest(() => { console.log('On request') }) .on('beforeHandle', () => { console.log('Before handle') }) .post('/mirror', ({ body }) => body, { body: t.Object({ username: t.String(), password: t.String() }), afterHandle: () => { console.log("After handle") } }) .listen(3000) ``` ## Guard Enforce a data type on subroutes See [Scope](/essential/plugin.html#scope) ```typescript twoslash // @errors: 2345 import { Elysia, t } from 'elysia' new Elysia() .guard({ response: t.String() }, (app) => app .get('/', () => 'Hi') // Invalid: will throws error, and TypeScript will report error .get('/invalid', () => 1) ) .listen(3000) ``` ## Custom context Add custom variables to route context See [Context](/essential/handler.html#context) ```typescript import { Elysia } from 'elysia' new Elysia() .state('version', 1) .decorate('getDate', () => Date.now()) .get('/version', ({ getDate, store: { version } }) => `${version} ${getDate()}`) .listen(3000) ``` ## Redirect Redirect responses See [Handler](/essential/handler.html#redirect) ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', () => 'hi') .get('/redirect', ({ redirect }) => { return redirect('/') }) .listen(3000) ``` ## Plugin Create a separate instance See [Plugin](/essential/plugin) ```typescript import { Elysia } from 'elysia' const plugin = new Elysia() .state('plugin-version', 1) .get('/hi', () => 'hi') new Elysia() .use(plugin) .get('/version', ({ store }) => store['plugin-version']) .listen(3000) ``` ## WebSocket Create a realtime connection using WebSocket See [Web Socket](/patterns/websocket) ```typescript import { Elysia } from 'elysia' new Elysia() .ws('/ping', { message(ws, message) { ws.send('hello ' + message) } }) .listen(3000) ``` ## OpenAPI documentation Create interactive documentation using Scalar (or optionally Swagger) See [openapi](/plugins/openapi.html) ```typescript import { Elysia } from 'elysia' import { openapi } from '@elysia/openapi' const app = new Elysia() .use(openapi()) .listen(3000) console.log(`View documentation at "${app.server!.url}openapi" in your browser`); ``` ## Unit Test Write a unit test for your Elysia app See [Unit Test](/patterns/unit-test) ```typescript // test/index.test.ts import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia' describe('Elysia', () => { it('return a response', async () => { const app = new Elysia().get('/', () => 'hi') const response = await app .handle(new Request('http://localhost/')) .then((res) => res.text()) expect(response).toBe('hi') }) }) ``` ## Custom body parser Create custom logic for parsing bodies See [Parse](/essential/life-cycle.html#parse) ```typescript import { Elysia } from 'elysia' new Elysia() .onParse(({ request, contentType }) => { if (contentType === 'application/custom-type') return request.text() }) ``` ## GraphQL Create a custom GraphQL server using GraphQL Yoga or Apollo See [GraphQL Yoga](/plugins/graphql-yoga) ```typescript import { Elysia } from 'elysia' import { yoga } from '@elysia/graphql-yoga' const app = new Elysia() .use( yoga({ typeDefs: /* GraphQL */` type Query { hi: String } `, resolvers: { Query: { hi: () => 'Hello from Elysia' } } }) ) .listen(3000) ``` --- --- url: 'https://elysiajs.com/patterns/configuration.md' --- # Config Elysia comes with a configurable behavior, allowing us to customize various aspects of its functionality. We can define a configuration by using a constructor. ```ts import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1', normalize: true }) ``` ## adapter ###### Since 1.1.11 Runtime adapter for using Elysia in different environments. Defaults to appropriate adapter based on the environment. ```ts import { Elysia, t } from 'elysia' import { BunAdapter } from 'elysia/adapter/bun' new Elysia({ adapter: BunAdapter }) ``` ## allowUnsafeValidationDetails ###### Since 1.4.13 Whether Elysia should include unsafe validation details in the error response on production. ```ts import { Elysia, t } from 'elysia' new Elysia({ allowUnsafeValidationDetails: true }) ``` By default, Elysia will omit all validation detail on production. This is done to prevent leaking sensitive information about the validation schema, such as field names and expected types, which could be exploited by an attacker. Ideally, this should only be enabled on public APIs as it may leak sensitive information about the server implementation. #### Options - @default `false` * `true` - Include unsafe validation details in the error response on production * `false` - Exclude unsafe validation details in the error response on production ## aot ###### Since 0.4.0 Ahead of Time compilation. Elysia has a built-in JIT *"compiler"* that can [optimize performance](/blog/elysia-04.html#ahead-of-time-complie). ```ts import { Elysia } from 'elysia' new Elysia({ aot: true }) ``` Disable Ahead of Time compilation #### Options - @default `false` * `true` - Precompile every route before starting the server * `false` - Disable JIT entirely. Faster startup time without cost of performance ## detail Define an OpenAPI schema for all routes of an instance. This schema will be used to generate OpenAPI documentation for all routes of an instance. ```ts import { Elysia } from 'elysia' new Elysia({ detail: { hide: true, tags: ['elysia'] } }) ``` ## encodeSchema Handle custom `t.Transform` schemas with custom `Encode` before returning the response to client. This allows us to create custom encode functions for your data before sending response to the client. ```ts import { Elysia, t } from 'elysia' new Elysia({ encodeSchema: true }) ``` #### Options - @default `true` * `true` - Run `Encode` before sending the response to client * `false` - Skip `Encode` entirely ## name Define the name of an instance which is used for debugging and [Plugin Deduplication](/essential/plugin.html#plugin-deduplication) ```ts import { Elysia } from 'elysia' new Elysia({ name: 'service.thing' }) ``` ## nativeStaticResponse ###### Since 1.1.11 Use optimized functions for handling inline values for each respective runtime. ```ts import { Elysia } from 'elysia' new Elysia({ nativeStaticResponse: true }) ``` #### Example If enabled on Bun, Elysia will insert inline value into `Bun.serve.static` improving performance for static value. ```ts import { Elysia } from 'elysia' // This new Elysia({ nativeStaticResponse: true }).get('/version', 1) // is an equivalent to Bun.serve({ static: { '/version': new Response(1) } }) ``` ## normalize ###### Since 1.1.0 Whether Elysia should coerce fields into a specified schema. ```ts import { Elysia, t } from 'elysia' new Elysia({ normalize: true }) ``` When unknown properties that are not specified in schema are found on either input and output, how should Elysia handle the field? Options - @default `true` * `true`: Elysia will coerce fields into a specified schema using [exact mirror](/blog/elysia-13.html#exact-mirror) * `typebox`: Elysia will coerce fields into a specified schema using [TypeBox's Value.Clean](https://github.com/sinclairzx81/typebox) * `false`: Elysia will raise an error if a request or response contains fields that are not explicitly allowed in the schema of the respective handler. ## precompile ###### Since 1.0.0 Whether Elysia should [precompile all routes](/blog/elysia-10.html#improved-startup-time) ahead of time before starting the server. ```ts import { Elysia } from 'elysia' new Elysia({ precompile: true }) ``` Options - @default `false` * `true`: Run JIT on all routes before starting the server * `false`: Dynamically compile routes on demand It's recommended to leave it as `false`. ## prefix Define a prefix for all routes of an instance ```ts import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1' }) ``` When prefix is defined, all routes will be prefixed with the given value. #### Example ```ts import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1' }).get('/name', 'elysia') // Path is /v1/name ``` ## sanitize A function or an array of functions that calls and intercepts on every `t.String` while validation. Allowing us to read and transform strings into new values. ```ts import { Elysia, t } from 'elysia' new Elysia({ sanitize: (value) => Bun.escapeHTML(value) }) ``` ## seed Define a value that will be used to generate checksum of an instance, used for [Plugin Deduplication](/essential/plugin.html#plugin-deduplication) ```ts import { Elysia } from 'elysia' new Elysia({ seed: { value: 'service.thing' } }) ``` The value could be any type not limited to string, number, or object. ## strictPath Whether Elysia should handle paths strictly. According to [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.3), a path should be strictly equal to the path defined in the route. ```ts import { Elysia, t } from 'elysia' new Elysia({ strictPath: true }) ``` #### Options - @default `false` * `true` - Follows [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.3) for path matching strictly * `false` - Tolerate suffix '/' or vice-versa. #### Example ```ts import { Elysia, t } from 'elysia' // Path can be either /name or /name/ new Elysia({ strictPath: false }).get('/name', 'elysia') // Path can be only /name new Elysia({ strictPath: true }).get('/name', 'elysia') ``` ## serve Customize HTTP server behavior. Bun serve configuration. ```ts import { Elysia } from 'elysia' new Elysia({ serve: { hostname: 'elysiajs.com', tls: { cert: Bun.file('cert.pem'), key: Bun.file('key.pem') } }, }) ``` This configuration extends [Bun Serve API](https://bun.sh/docs/api/http) and [Bun TLS](https://bun.sh/docs/api/http#tls) ### Example: Max body size We can set the maximum body size by setting [`serve.maxRequestBodySize`](#serve-maxrequestbodysize) in the `serve` configuration. ```ts import { Elysia } from 'elysia' new Elysia({ serve: { maxRequestBodySize: 1024 * 1024 * 256 // 256MB } }) ``` By default the maximum request body size is 128MB (1024 \* 1024 \* 128). Define body size limit. ```ts import { Elysia } from 'elysia' new Elysia({ serve: { // Maximum message size (in bytes) maxPayloadLength: 64 * 1024, } }) ``` ### Example: HTTPS / TLS We can enable TLS (known as successor of SSL) by passing in a value for key and cert; both are required to enable TLS. ```ts import { Elysia, file } from 'elysia' new Elysia({ serve: { tls: { cert: file('cert.pem'), key: file('key.pem') } } }) ``` ### Example: Increase timeout We can increase the idle timeout by setting [`serve.idleTimeout`](#serve-idletimeout) in the `serve` configuration. ```ts import { Elysia } from 'elysia' new Elysia({ serve: { // Increase idle timeout to 60 seconds idleTimeout: 60 } }) ``` By default the idle timeout is 30 seconds. *** ## serve HTTP server configuration. Elysia extends Bun configuration which supports TLS out of the box, powered by BoringSSL. See [serve.tls](#serve-tls) for available configuration. ### serve.hostname @default `0.0.0.0` Set the hostname which the server listens on ### serve.id Uniquely identify a server instance with an ID This string will be used to hot reload the server without interrupting pending requests or websockets. If not provided, a value will be generated. To disable hot reloading, set this value to `null`. ### serve.idleTimeout @default `30` (30 seconds) By default, Elysia sets idle timeout to 30 seconds, which means that if a request is not completed within 30 seconds, it will be aborted. ### serve.maxRequestBodySize @default `1024 * 1024 * 128` (128MB) Set the maximum size of a request body (in bytes) ### serve.port @default `3000` Port to listen on ### serve.rejectUnauthorized @default `NODE_TLS_REJECT_UNAUTHORIZED` environment variable If set to `false`, any certificate is accepted. ### serve.reusePort @default `true` If the `SO_REUSEPORT` flag should be set This allows multiple processes to bind to the same port, which is useful for load balancing This configuration is overridden and turns on by default by Elysia ### serve.unix If set, the HTTP server will listen on a unix socket instead of a port. (Cannot be used with hostname+port) ### serve.tls We can enable TLS (known as successor of SSL) by passing in a value for key and cert; both are required to enable TLS. ```ts import { Elysia, file } from 'elysia' new Elysia({ serve: { tls: { cert: file('cert.pem'), key: file('key.pem') } } }) ``` Elysia extends Bun configuration which supports TLS out of the box, powered by BoringSSL. ### serve.tls.ca Optionally override the trusted CA certificates. Default is to trust the well-known CAs curated by Mozilla. Mozilla's CAs are completely replaced when CAs are explicitly specified using this option. ### serve.tls.cert Cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided private key, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, see ca). When providing multiple cert chains, they do not have to be in the same order as their private keys in key. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail. ### serve.tls.dhParamsFile File path to a .pem file custom Diffie Helman parameters ### serve.tls.key Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form . The object form can only occur in an array. **object.passphrase** is optional. Encrypted keys will be decrypted with **object.passphrase** if provided, or **options.passphrase** if it is not. ### serve.tls.lowMemoryMode @default `false` This sets `OPENSSL_RELEASE_BUFFERS` to 1. It reduces overall performance but saves some memory. ### serve.tls.passphrase Shared passphrase for a single private key and/or a PFX. ### serve.tls.requestCert @default `false` If set to `true`, the server will request a client certificate. ### serve.tls.secureOptions Optionally affect the OpenSSL protocol behavior, which is not usually necessary. This should be used carefully if at all! Value is a numeric bitmask of the SSL\_OP\_\* options from OpenSSL Options ### serve.tls.serverName Explicitly set a server name ## tags Define tags for OpenAPI schema for all routes of an instance similar to [detail](#detail) ```ts import { Elysia } from 'elysia' new Elysia({ tags: ['elysia'] }) ``` ## systemRouter Use runtime/framework provided router if possible. On Bun, Elysia will use [Bun.serve.routes](https://bun.sh/docs/api/http#routing) and fallback to Elysia's own router. ## websocket Override websocket configuration Recommended to leave this as default as Elysia will generate suitable configuration for handling WebSocket automatically This configuration extends [Bun's WebSocket API](https://bun.sh/docs/api/websockets) #### Example ```ts import { Elysia } from 'elysia' new Elysia({ websocket: { // enable compression and decompression perMessageDeflate: true } }) ``` *** --- --- url: 'https://elysiajs.com/tutorial/patterns/cookie.md' --- # Cookie You interact with cookies by using cookie from context. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { const total = +visit.value ?? 0 visit.value++ return `You have visited ${visit.value} times` }) .listen(3000) ``` Cookie is a reactive object. Once modified, it will be reflected in response. ## Value Elysia will then try to coerce it into its respective value when a type annotation if provided. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.value ??= 0 visit.value.total++ return `You have visited ${visit.value.total} times` }, { cookie: t.Object({ visit: t.Optional( t.Object({ total: t.Number() }) ) }) }) .listen(3000) ``` We can use cookie schema to validate and parse cookie. ## Attribute We can get/set cookie attribute by its respective property name. Otherwise, use `.set()` to bulk set attribute. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.value ??= 0 visit.value++ visit.httpOnly = true visit.path = '/' visit.set({ sameSite: 'lax', secure: true, maxAge: 60 * 60 * 24 * 7 }) return `You have visited ${visit.value} times` }) .listen(3000) ``` See Cookie Attribute. ## Remove We can remove cookie by calling `.remove()` method. ```typescript import { Elysia } from 'elysia' new Elysia() .get('/', ({ cookie: { visit } }) => { visit.remove() return `Cookie removed` }) .listen(3000) ``` ## Cookie Signature Elysia can sign cookies to prevent tampering by: 1. Provide a cookie secret to the Elysia constructor. 2. Use `t.Cookie` to provide secret for each cookie. ```typescript import { Elysia } from 'elysia' new Elysia({ cookie: { secret: 'Fischl von Luftschloss Narfidort', } }) .get('/', ({ cookie: { visit } }) => { visit.value ??= 0 visit.value++ return `You have visited ${visit.value} times` }, { cookie: t.Cookie({ visit: t.Optional(t.Number()) }, { secrets: 'Fischl von Luftschloss Narfidort', sign: ['visit'] }) }) .listen(3000) ``` If multiple secrets are provided, Elysia will use the first secret to sign cookies, and try to verify with the rest. See Cookie Signature, Cookie Rotation. ## Assignment Let's create a simple counter that tracks how many times you have visited the site. \