--- 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 @elysiajs/apollo @apollo/server ``` Then use it: ```typescript import { Elysia } from 'elysia' import { apollo, gql } from '@elysiajs/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 Apollo GraphQL playground 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 should 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 twoslash 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 '@elysiajs/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 has excellent support for OpenAPI with schemas that can be used for data validation, type inference, and OpenAPI annotation from a single source of truth. Elysia also supports OpenAPI schema generation with **1 line directly from types**, allowing you to have complete and accurate API documentation without any manual annotation. ```typescript import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysiajs/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) ``` ## 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 twoslash import { Elysia, t } from 'elysia' import { openapi, fromTypes } from '@elysiajs/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 ``` 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 '@elysiajs/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. ## Platform Agnostic Elysia was designed for Bun, but is **not limited to Bun**. Being [WinterTC compliant](https://wintertc.org/) allows you to deploy Elysia servers on Cloudflare Workers, Vercel Edge Functions, and most other runtimes that support Web Standard Requests. ## Our Community If you have questions or get stuck with Elysia, feel free to ask our community on GitHub Discussions, Discord, or Twitter. *** 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 @elysiajs/bearer ``` Then use it: ```typescript twoslash import { Elysia } from 'elysia' import { bearer } from '@elysiajs/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 logic for handling validation check 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] // Controller handle HTTP related eg. routing, request validation 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 session.value = response.token return response }, { body: AuthModel.signInBody, response: { 200: AuthModel.signInResponse, 400: AuthModel.signInInvalid } } ) ``` ```typescript [auth/service.ts] // Service handle business logic, decoupled from Elysia controller import { status } from 'elysia' import type { AuthModel } from './model' // If the class doesn't need to store a property, // you may use `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 } from 'elysia' export namespace AuthModel { // Define a DTO for Elysia validation export const signInBody = t.Object({ username: t.String(), password: t.String(), }) // Define it as TypeScript type export type signInBody = typeof signInBody.static // Repeat for other models export const signInResponse = t.Object({ username: t.String(), token: t.String(), }) export type signInResponse = typeof signInResponse.static export const signInInvalid = t.Literal('Invalid username or password') export type signInInvalid = typeof signInInvalid.static } ``` ::: Each file has its own responsibility as follows: * **Controller**: Handle HTTP routing, request validation, and cookie. * **Service**: Handle business logic, decoupled from Elysia controller if possible. * **Model**: Define 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. ## Controller > 1 Elysia instance = 1 controller Elysia does a lot to ensure type integrity, if you pass an entire `Context` type to a controller, these might be the problems: 1. Elysia type is complex and heavily depends on plugin and multiple level of chaining. 2. Hard to type, Elysia type could change at anytime, especially with decorators, and store 3. Type casting may lead to a loss of type integrity or an inability to ensure consistency between types and runtime code. 4. This makes it more challenging for [Sucrose](/blog/elysia-10#sucrose) *(Elysia's "kind of" compiler)* to statically analyze your code ### ❌ Don't: Create a separate controller Don't create a separate controller, use Elysia itself as a controller instead: ```typescript import { Elysia, t, type Context } from 'elysia' abstract class Controller { static root(context: Context) { return Service.doStuff(context.stuff) } } // ❌ Don't new Elysia() .get('/', Controller.hi) ``` By passing an entire `Controller.method` to Elysia is an equivalent of having 2 controllers passing data back and forth. It's against the design of framework and MVC pattern itself. ### ✅ Do: Use Elysia as a controller Instead treat an Elysia instance as a controller itself instead. ```typescript import { Elysia } from 'elysia' import { Service } from './service' new Elysia() .get('/', ({ stuff }) => { Service.doStuff(stuff) }) ``` Otherwise, if you really want to separate the controller, you may create a controller class that is not tied with HTTP request at all. ```typescript import { Elysia } from 'elysia' abstract class Controller { static doStuff(stuff: string) { return Service.doStuff(stuff) } } new Elysia() .get('/', ({ stuff }) => Controller.doStuff(stuff)) ``` ### Testing 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 Service is a set of utility/helper functions decoupled as a 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 ### ✅ Do: Abstract away non-request dependent service We recommend abstracting a service class/function 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 may use `abstract class` and `static` instead to avoid allocating class instance. ### ✅ Do: 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) 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 request-dependent properties, such as `requestIP`, `requestTime`, or `session`. Overusing decorators may tie 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 } }) ``` ### ❌ Don't: Pass entire `Context` to a service **Context is a highly dynamic type** that can be inferred from Elysia instance. Do not pass an entire `Context` to a service, instead use object destructuring to extract what you need and pass it to the service. ```typescript import type { Context } from 'elysia' class AuthService { constructor() {} // ❌ Don't do this isSignIn({ status, cookie: { session } }: Context) { if (session.value) return status(401) } } ``` As Elysia type is complex, and heavily depends on plugin and multiple level of chaining, it can be challenging to manually type as it's highly dynamic. ### ⚠️ Infers Context from Elysia instance In case of **absolute necessity**, you may infer the `Context` type from the Elysia instance itself: ```typescript import { Elysia, type InferContext } from 'elysia' const setup = new Elysia() .state('a', 'a') .decorate('b', 'b') class AuthService { constructor() {} // ✅ Do isSignIn({ status, cookie: { session } }: InferContext) { if (session.value) return status(401) } } ``` However we recommend to avoid this if possible, and use [Elysia as a service](#✅-do-use-elysia-as-a-controller) instead. You may find more about [InferContext](/essential/handler#infercontext) in [Essential: Handler](/essential/handler). ## Model Model or [DTO (Data Transfer Object)](https://en.wikipedia.org/wiki/Data_transfer_object) is handle by [Elysia.t (Validation)](/essential/validation.html#elysia-type). Elysia has a validation system built-in which can infers type from your code and validate it at runtime. ### ❌ 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 } ``` ### ✅ Do: Use Elysia's validation system Instead of declaring a class or interface, use Elysia's validation system to define a model: ```typescript twoslash // ✅ Do import { Elysia, t } from 'elysia' const customBody = t.Object({ username: t.String(), password: t.String() }) // Optional if you want to get the type of the model // Usually if we didn't use the type, as it's already inferred by Elysia type CustomBody = typeof customBody.static // ^? export { customBody } ``` 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 customBody = t.Object({ username: t.String(), password: t.String() }) // ---cut--- // ✅ Do new Elysia() .post('/login', ({ body }) => { // ^? return body }, { body: customBody }) ``` ### ❌ Don't: Declare type separate from the model Do not declare a type separate from the model, instead use `typeof` with `.static` property to get the type of the model. ```typescript // ❌ Don't import { Elysia, t } from 'elysia' const customBody = t.Object({ username: t.String(), password: t.String() }) type CustomBody = { username: string password: string } // ✅ Do const customBody = t.Object({ username: t.String(), password: t.String() }) type CustomBody = typeof customBody.static ``` ### 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({ 'auth.sign': customBody }) const models = AuthModel.models const UserController = new Elysia({ prefix: '/auth' }) .use(AuthModel) .post('/sign-in', async ({ body, cookie: { session } }) => { // ^? return true }, { body: 'auth.sign' }) ``` This approach provide several benefits: 1. Allow us to name a model and provide auto-completion. 2. Modify schema for later usage, or perform a [remap](/essential/handler.html#remap). 3. Show up as "models" in OpenAPI compliance client, eg. OpenAPI. 4. Improve TypeScript inference speed as model type will be cached during registration. ## Reuse a plugin It's ok to reuse plugins multiple time to provide type inference. Elysia handle plugin deduplication automatically by default, and the performance is negligible. To create a unique plugin, you may provide a **name** or optional **seed** to an Elysia instance. ```typescript import { Elysia } from 'elysia' const plugin = new Elysia({ name: 'my-plugin' }) .decorate("type", "plugin") const app = new Elysia() .use(plugin) .use(plugin) .use(plugin) .use(plugin) .listen(3000) ``` This allows Elysia to improve performance by reusing the registered plugins instead of processing the plugin over and over again. --- --- 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 Better Auth instance, we can mount to Elysia via [mount](/patterns/mount.html). We need to mount the handler to 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 with `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 with `http://localhost:3000/auth/api/auth`. But the URL looks redundant, we can customize the `/api/auth` prefix to something else in Better Auth instance. ```ts import { betterAuth } from 'better-auth' import { openAPI } from 'better-auth/plugins' import { passkey } from 'better-auth/plugins/passkey' import { Pool } from 'pg' export const auth = betterAuth({ basePath: '/api' // [!code ++] }) ``` Then we can access Better Auth with `http://localhost:3000/auth/api`. Unfortunately, we can't set `basePath` of a Better Auth instance to be empty or `/`. ## OpenAPI Better Auth support `openapi` with `better-auth/plugins`. However if we are using [@elysiajs/openapi](/plugins/openapi), you might want to extract the documentation from Better Auth instance. We may do that with the following code: ```ts import { openAPI } from 'better-auth/plugins' 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 use `@elysiajs/openapi`. ```ts import { Elysia } from 'elysia' import { openapi } from '@elysiajs/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 `@elysiajs/cors`. ```ts import { Elysia } from 'elysia' import { cors } from '@elysiajs/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 pass to 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` object in all of your routes. --- --- url: 'https://elysiajs.com/integrations/cheat-sheet.md' --- # Cheat Sheet Here are a quick overview for a 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 route 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 parameter 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 return in as 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 sub routes 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 of 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 twoslash 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 an Elysia event 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 of sub routes 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 variable 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 a response 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) ``` ## Web Socket Create a realtime connection using Web Socket 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 '@elysiajs/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 of 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 body 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 '@elysiajs/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/migrate.md' --- # Comparison with Other Frameworks Elysia is designed to be intuitive and easy to use, especially for those familiar with other web frameworks. If you have used other popular frameworks like Express, Fastify, or Hono, you will find Elysia right at home with just a few differences. --- --- 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 twoslash import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1', normalize: true }) ``` ## adapter ###### Since 1.1.11 Runtime adapter for using Elysia in different environments. Default to appropriate adapter based on the environment. ```ts import { Elysia, t } from 'elysia' import { BunAdapter } from 'elysia/adapter/bun' new Elysia({ adapter: BunAdapter }) ``` ## 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 twoslash 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 twoslash import { Elysia } from 'elysia' new Elysia({ detail: { hide: true, tags: ['elysia'] } }) ``` ## encodeSchema Handle custom `t.Transform` schema with custom `Encode` before returning the response to client. This allows us to create custom encode function 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 a name of an instance which is used for debugging and [Plugin Deduplication](/essential/plugin.html#plugin-deduplication) ```ts twoslash import { Elysia } from 'elysia' new Elysia({ name: 'service.thing' }) ``` ## nativeStaticResponse ###### Since 1.1.11 Use an optimized function for handling inline value for each respective runtime. ```ts twoslash 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 field into a specified schema. ```ts twoslash 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 twoslash 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 twoslash import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1' }) ``` When prefix is defined, all routes will be prefixed with the given value. #### Example ```ts twoslash import { Elysia, t } from 'elysia' new Elysia({ prefix: '/v1' }).get('/name', 'elysia') // Path is /v1/name ``` ## sanitize A function or an array of function that calls and intercepts on every `t.String` while validation. Allowing us to read and transform a string into a new value. ```ts import { Elysia, t } from 'elysia' new Elysia({ sanitize: (value) => Bun.escapeHTML(value) }) ``` ## seed Define a value which will be used to generate checksum of an instance, used for [Plugin Deduplication](/essential/plugin.html#plugin-deduplication) ```ts twoslash 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 should Elysia handle path 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 twoslash 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 twoslash 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 30 seconds idleTimeout: 30 } }) ``` By default the idle timeout is 10 seconds (on Bun). *** ## 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 `10` (10 seconds) By default, Bun set idle timeout to 10 seconds, which means that if a request is not completed within 10 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 override 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 an tags for OpenAPI schema for all routes of an instance similar to [detail](#detail) ```ts twoslash 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 cookie 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 cookie to prevent tampering by: 1. Provide cookie secret to 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 cookie, 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. \