localhost
POST
By default, hook and schema will apply to current instance only.
Elysia has an encapsulation scope for to prevent unintentional side effects.
Scope type is to specify the scope of hook whether is should be encapsulated or global.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.derive(() => {
return { hi: 'ok' }
})
.get('/child', ({ hi }) => hi)
const main = new Elysia()
.use(plugin)
// ⚠️ Hi is missing
.get('/parent', ({ hi }) => hi)Property 'hi' does not exist on type '{ body: unknown; query: Record<string, string | string[] | undefined>; params: never; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<string | undefined>>; ... 8 more ...; error: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 58 more ... | 511 ? { ...; }[Cod...'.
From the above code, we can see that hi
is missing from the parent instance because the scope is local by default if not specified, and will not apply to parent.
To apply the hook to the parent instance, we can use the as
to specify scope of the hook.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.derive({ as: 'scoped' }, () => {
return { hi: 'ok' }
})
.get('/child', ({ hi }) => hi)
const main = new Elysia()
.use(plugin)
// ✅ Hi is now available
.get('/parent', ({ hi }) => hi)
Elysia has 3 levels of scope as the following: Scope type are as the following:
Let's review what each scope type does by using the following example:
import { Elysia } from 'elysia'
// ? Value base on table value provided below
const type = 'local'
const child = new Elysia()
.get('/child', () => 'hi')
const current = new Elysia()
.onBeforeHandle({ as: type }, () => {
console.log('hi')
})
.use(child)
.get('/current', () => 'hi')
const parent = new Elysia()
.use(current)
.get('/parent', () => 'hi')
const main = new Elysia()
.use(parent)
.get('/main', () => 'hi')
By changing the type
value, the result should be as follows:
type | child | current | parent | main |
---|---|---|---|---|
'local' | ✅ | ✅ | ❌ | ❌ |
'scoped' | ✅ | ✅ | ✅ | ❌ |
'global' | ✅ | ✅ | ✅ | ✅ |
Guard allows us to apply hook and schema into multiple routes all at once.
import { Elysia, t } from 'elysia'
new Elysia()
.guard(
{
body: t.Object({
username: t.String(),
password: t.String()
})
},
(app) =>
app
.post('/sign-up', ({ body }) => signUp(body))
.post('/sign-in', ({ body }) => signIn(body), {
beforeHandle: isUserExists
})
)
.get('/', () => 'hi')
.listen(3000)
This code applies validation for body
to both '/sign-in' and '/sign-up' instead of inlining the schema one by one but applies not to '/'.
We can summarize the route validation as the following:
Path | Has validation |
---|---|
/sign-up | ✅ |
/sign-in | ✅ |
/ | ❌ |
Guard accepts the same parameter as inline hook, the only difference is that you can apply hook to multiple routes in the scope.
This means that the code above is translated into:
import { Elysia, t } from 'elysia'
new Elysia()
.post('/sign-up', ({ body }) => signUp(body), {
body: t.Object({
username: t.String(),
password: t.String()
})
})
.post('/sign-in', ({ body }) => body, {
beforeHandle: isUserExists,
body: t.Object({
username: t.String(),
password: t.String()
})
})
.get('/', () => 'hi')
.listen(3000)
We can use a group with prefixes by providing 3 parameters to the group.
With the same API as guard apply to the 2nd parameter, instead of nesting group and guard together.
Consider the following example:
import { Elysia, t } from 'elysia'
new Elysia()
.group('/v1', (app) =>
app.guard(
{
body: t.Literal('Rikuhachima Aru')
},
(app) => app.post('/student', ({ body }) => body)
)
)
.listen(3000)
From nested groupped guard, we may merge group and guard together by providing guard scope to 2nd parameter of group:
import { Elysia, t } from 'elysia'
new Elysia()
.group(
'/v1',
(app) => app.guard(
{
body: t.Literal('Rikuhachima Aru')
},
(app) => app.post('/student', ({ body }) => body)
)
)
.listen(3000)
Which results in the follows syntax:
import { Elysia, t } from 'elysia'
new Elysia()
.group(
'/v1',
{
body: t.Literal('Rikuhachima Aru')
},
(app) => app.post('/student', ({ body }) => body)
)
.listen(3000)
POST
To apply hook to parent may use one of the following:
inline as
apply only to a single hookguard as
apply to all hook in a guardinstance as
apply to all hook in an instanceEvery event listener will accept as
parameter to specify the scope of the hook.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.derive({ as: 'scoped' }, () => {
return { hi: 'ok' }
})
.get('/child', ({ hi }) => hi)
const main = new Elysia()
.use(plugin)
// ✅ Hi is now available
.get('/parent', ({ hi }) => hi)
However, this method is apply to only a single hook, and may not be suitable for multiple hooks.
Every event listener will accept as
parameter to specify the scope of the hook.
import { Elysia, t } from 'elysia'
const plugin = new Elysia()
.guard({
as: 'scoped',
response: t.String(),
beforeHandle() {
console.log('ok')
}
})
.get('/child', () => 'ok')
const main = new Elysia()
.use(plugin)
.get('/parent', () => 'hello')
Guard alllowing us to apply schema
and hook
to multiple routes all at once while specifying the scope.
However, it doesn't support derive
and resolve
method.
as
will read all hooks and schema scope of the current instance, modify.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.derive(() => {
return { hi: 'ok' }
})
.get('/child', ({ hi }) => hi)
.as('plugin')
const main = new Elysia()
.use(plugin)
// ✅ Hi is now available
.get('/parent', ({ hi }) => hi)
Sometimes we want to reapply plugin to parent instance as well but as it's limited by scoped
mechanism, it's limited to 1 parent only.
To apply to the parent instance, we need to "lift the scope up to the parent instance, and as
is the perfect method to do so.
Which means if you have local
scope, and want to apply it to the parent instance, you can use as('plugin')
to lift it up.
import { Elysia, t } from 'elysia'
const plugin = new Elysia()
.guard({
response: t.String()
})
.onBeforeHandle(() => { console.log('called') })
.get('/ok', () => 'ok')
.get('/not-ok', () => 1)Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<MergeSchema<UnwrapRoute<InputSchema<never>, {}>, MergeSchema<{ body: unknown; headers: unknown; query: unknown; params: unknown; cookie: unknown; response: { 200: string; }; }, MergeSchema<{}, {}>>>, { ...; } & { ...; }, "/not-ok">'.
Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string | string[] | undefined>; params: never; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<string | undefined>>; ... 8 more ...; error: <const Code extends "OK" | 200, const T extends Code extends 200 ? { ...; }[Code] : Code extends "Con...'.
Type 'number' is not assignable to type 'Response | MaybePromise<string | { 200: string; } | { _type: Record<200, string>; [ELYSIA_RESPONSE]: 200; }>'. .as('plugin')
const instance = new Elysia()
.use(plugin)
.get('/no-ok-parent', () => 2)Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<MergeSchema<UnwrapRoute<InputSchema<never>, {}>, MergeSchema<{ body: unknown; headers: unknown; query: unknown; params: unknown; cookie: unknown; response: { 200: string; }; }, MergeSchema<{}, {}>>>, { ...; } & { ...; }, "/no-ok-parent">'.
Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string | string[] | undefined>; params: never; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<string | undefined>>; ... 8 more ...; error: <const Code extends "OK" | 200, const T extends Code extends 200 ? { ...; }[Code] : Code extends "Con...'.
Type 'number' is not assignable to type 'Response | MaybePromise<string | { 200: string; } | { _type: Record<200, string>; [ELYSIA_RESPONSE]: 200; }>'. .as('plugin')
const parent = new Elysia()
.use(instance)
// This now error because `scoped` is lifted up to parent
.get('/ok', () => 3)Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<MergeSchema<UnwrapRoute<InputSchema<never>, {}>, MergeSchema<{ body: unknown; headers: unknown; query: unknown; params: unknown; cookie: unknown; response: { 200: string; }; }, MergeSchema<{}, {}>>>, { ...; } & { ...; }, "/ok">'.
Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string | string[] | undefined>; params: never; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<string | undefined>>; ... 8 more ...; error: <const Code extends "OK" | 200, const T extends Code extends 200 ? { ...; }[Code] : Code extends "Con...'.
Type 'number' is not assignable to type 'Response | MaybePromise<string | { 200: string; } | { _type: Record<200, string>; [ELYSIA_RESPONSE]: 200; }>'.
By default plugin will only apply hook to itself and descendants only.
If the hook is registered in a plugin, instances that inherit the plugin will NOT inherit hooks and schema.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.onBeforeHandle(() => {
console.log('hi')
})
.get('/child', () => 'log hi')
const main = new Elysia()
.use(plugin)
.get('/parent', () => 'not log hi')
To apply hook to globally, we need to specify hook as global.
import { Elysia } from 'elysia'
const plugin = new Elysia()
.onBeforeHandle(() => {
return 'hi'
})
.get('/child', () => 'child')
.as('plugin')
const main = new Elysia()
.use(plugin)
.get('/parent', () => 'parent')
GET