Skip to content

Scope

By default, hook and schema will apply to current instance only.

Elysia has an encapsulation scope for to prevent unintentional side effects.

Scope

Scope type is to specify the scope of hook whether is should be encapsulated or global.

typescript
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.

typescript
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
)

Scope level

Elysia has 3 levels of scope as the following: Scope type are as the following:

  • local (default) - apply to only current instance and descendant only
  • scoped - apply to parent, current instance and descendants
  • global - apply to all instance that apply the plugin (all parents, current, and descendants)

Let's review what each scope type does by using the following example:

typescript
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:

typechildcurrentparentmain
'local'
'scoped'
'global'

Guard

Guard allows us to apply hook and schema into multiple routes all at once.

typescript
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:

PathHas 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:

typescript
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)

Grouped Guard

We can use a group with prefixes by providing 3 parameters to the group.

  1. Prefix - Route prefix
  2. Guard - Schema
  3. Scope - Elysia app callback

With the same API as guard apply to the 2nd parameter, instead of nesting group and guard together.

Consider the following example:

typescript
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:

typescript
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:

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
group
(
'/v1', {
body
:
t
.
Literal
('Rikuhachima Aru')
}, (
app
) =>
app
.
post
('/student', ({
body
}) =>
body
)
) .
listen
(3000)
localhost

POST

Scope cast

To apply hook to parent may use one of the following:

  1. inline as apply only to a single hook
  2. guard as apply to all hook in a guard
  3. instance as apply to all hook in an instance

1. Inline as

Every event listener will accept as parameter to specify the scope of the hook.

typescript
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.

2. Guard as

Every event listener will accept as parameter to specify the scope of the hook.

typescript
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.

3. Instance as

as will read all hooks and schema scope of the current instance, modify.

typescript
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.

typescript
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; }>'.

Plugin

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.

typescript
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.

typescript
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')
localhost

GET

hi