localhost
GET
Elysia has a every important concepts that you need to understand to use.
This page covers most concepts that you should know before getting started.
Elysia lifecycle methods are encapsulated to its own instance only.
Which means if you create a new instance, it will not share the lifecycle methods with others.
import { Elysia } from 'elysia'
const profile = new Elysia()
.onBeforeHandle(({ cookie }) => {
throwIfNotSignIn(cookie)
})
.get('/profile', () => 'Hi there!')
const app = new Elysia()
.use(profile)
// ⚠️ This will NOT have sign in check
.patch('/rename', ({ body }) => updateProfile(body))In this example, the isSignIn check will only apply to profile but not app.
GET
Try changing the path in the URL bar to /rename and see the result
Elysia isolate lifecycle by default unless explicitly stated. This is similar to export in JavaScript, where you need to export the function to make it available outside the module.
To "export" the lifecycle to other instances, you must add specify the scope.
import { Elysia } from 'elysia'
const profile = new Elysia()
.onBeforeHandle(
{ as: 'global' },
({ cookie }) => {
throwIfNotSignIn(cookie)
}
)
.get('/profile', () => 'Hi there!')
const app = new Elysia()
.use(profile)
// This has sign in check
.patch('/rename', ({ body }) => updateProfile(body))GET
Casting lifecycle to "global" will export lifecycle to every instance.
Learn more about this in scope.
Elysia code should ALWAYS use method chaining.
This is important to ensure type safety.
import { Elysia } from 'elysia'
new Elysia()
.state('build', 1)
// Store is strictly typed
.get('/', ({ store: { build } }) => build)
.listen(3000)In the code above, state returns a new ElysiaInstance type, adding a typed build property.
As Elysia type system is complex, every method in Elysia returns a new type reference.
Without using method chaining, Elysia doesn't save these new types, leading to no type inference.
import { Elysia } from 'elysia'
const app = new Elysia()
app.state('build', 1)
app.get('/', ({ store: { build } }) => build)Property 'build' does not exist on type '{}'.
app.listen(3000)We recommend to always use method chaining to provide an accurate type inference.
Elysia by design, is compose of multiple mini Elysia apps which can run independently like a microservice that communicate with each other.
Each Elysia instance is independent and can run as a standalone server.
When an instance need to use another instance's service, you must explicitly declare the dependency.
import { Elysia } from 'elysia'
const auth = new Elysia()
.decorate('Auth', Auth)
.model(Auth.models)
const main = new Elysia()
// ❌ 'auth' is missing
.get('/', ({ Auth }) => Auth.getProfile())Property 'Auth' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server<unknown> | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] :...'. // auth is required to use Auth's service
.use(auth)
.get('/profile', ({ Auth }) => Auth.getProfile())
This is similar to Dependency Injection where each instance must declare its dependencies.
This approach force you to be explicit about dependencies allowing better tracking, modularity.
By default, each plugin will be re-executed every time applying to another instance.
To prevent this, Elysia can deduplicate lifecycle with an unique identifier using name and optional seed property.
import { Elysia } from 'elysia'
// `name` is an unique identifier
const ip = new Elysia({ name: 'ip' })
.derive(
{ as: 'global' },
({ server, request }) => ({
ip: server?.requestIP(request)
})
)
.get('/ip', ({ ip }) => ip)
const router1 = new Elysia()
.use(ip)
.get('/ip-1', ({ ip }) => ip)
const router2 = new Elysia()
.use(ip)
.get('/ip-2', ({ ip }) => ip)
const server = new Elysia()
.use(router1)
.use(router2)Adding the name and optional seed to the instance will make it a unique identifier prevent it from being called multiple times.
Learn more about this in plugin deduplication.
There are some case that global dependency make more sense than an explicit one.
Global plugin example:
Example use cases:
In case like this, it make more sense to create it as global dependency instead of applying it to every instance.
However, if your dependency doesn't fit into these categories, it's recommended to use explicit dependency instead.
Explicit dependency example:
Example use cases:
The order of Elysia's life-cycle code is very important.
Because event will only apply to routes after it is registered.
If you put the onError before plugin, plugin will not inherit the onError event.
import { Elysia } from 'elysia'
new Elysia()
.onBeforeHandle(() => {
console.log('1')
})
.get('/', () => 'hi')
.onBeforeHandle(() => {
console.log('2')
})
.listen(3000)Console should log the following:
1Notice that it doesn't log 2, because the event is registered after the route so it is not applied to the route.
Learn more about this in order of code.
Elysia has a complex type system that allows you to infer types from the instance.
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/', ({ body }) => body, {
body: t.Object({
name: t.String()
})
})You should always use an inline function to provide an accurate type inference.
If you need to apply a separate function, eg. MVC's controller pattern, it's recommended to destructure properties from inline function to prevent unnecessary type inference as follows:
import { Elysia, t } from 'elysia'
abstract class Controller {
static greet({ name }: { name: string }) {
return 'hello ' + name
}
}
const app = new Elysia()
.post('/', ({ body }) => Controller.greet(body), {
body: t.Object({
name: t.String()
})
})See Best practice: MVC Controller.
We can get a type definitions of every Elysia/TypeBox's type by accessing static property as follows:
import { t } from 'elysia'
const MyType = t.Object({
hello: t.Literal('Elysia')
})
type MyType = typeof MyType.static
This allows Elysia to infer and provide type automatically, reducing the need to declare duplicate schema
A single Elysia/TypeBox schema can be used for:
This allows us to make a schema as a single source of truth.