Life Cycle
Also known as middleware with name in Express or Hook in Fastify.
Imagine we want to return a text of HTML.
We need to set "Content-Type" headers as "text/html" to for browser to render HTML.
Explicitly specifying that response is HTML could be repetitive if there are a lot of handlers, says ~200 endpoints.
We can see a duplicated code for just specifying that response is HTML.
But what if after we sent a response, we could detect if a response is an HTML string then append headers automatically?
That's when the concept of Life Cycle comes into play.
Life Cycle allows us to intercept important events, and customize the behavior of Elysia, like adding an HTML header automatically.
Elysia's Life Cycle event can be illustrated as the following.
You don't have to understand/memorize all of the events in one go, we will be covering each on the next chapter.
Events
Most of the events you are going to use are highlighted in the blue area but to summarize:
Elysia does the following for every request:
- Request
- Notify when a new event is received, providing only the most minimal context to reduce overhead
- Best for:
- Caching
- Analytics
- Parse
- Parse body and add to
Context.body
- Best for:
- Providing custom body-parser
- Parse body and add to
- Transform
- Modify
Context
before validation - Best for:
- Mutate existing context to conform with validation.
- Adding new context (derive this)
- Modify
- Validation (not interceptable)
- Strictly validate incoming request provided by
Elysia.t
- Strictly validate incoming request provided by
- Before Handle
- Custom validation before route handler
- If value is returned, route handler will be skipped
- Best for:
- Providing custom requirements to access route, eg. user session, authorization.
- Handle (Route Handler)
- A callback function of each route
- After Handle
- Map returned value into a response
- Best for:
- Add custom headers or transform the value into a new response
- Error
- Capture error when thrown
- Best for:
- Provide a custom error response
- Catching error response
- After Response
- Executed after response sent to the client
- Best for:
- Cleaning up response
- Analytics
These events are designed to help you decouple code into smaller reusable pieces instead of having long, repetitive code in a handler.
Hook
We refer to each function that intercepts the life cycle event as "hook", as the function hooks into the lifecycle event.
Hooks can be categorized into 2 types:
- Local Hook: Execute on a specific route
- Interceptor Hook: Execute on every route
TIP
The hook will accept the same Context as a handler, you can imagine adding a route handler but at a specific point.
Local Hook
The local hook is executed on a specific route.
To use a local hook, you can inline hook into a route handler:
import { Elysia } from 'elysia'
import { isHtml } from '@elysiajs/html'
new Elysia()
.get('/', () => '<h1>Hello World</h1>', {
afterHandle({ response, set }) {
if (isHtml(response))
set.headers['Content-Type'] = 'text/html; charset=utf8'
}
})
.get('/hi', () => '<h1>Hello World</h1>')
.listen(3000)
The response should be listed as follows:
Path | Content-Type |
---|---|
/ | text/html; charset=utf8 |
/hi | text/plain; charset=utf8 |
Interceptor Hook
Register hook into every handler of the current instance that came after.
To add an interceptor hook, you can use .on
followed by a life cycle event in camelCase:
import { Elysia } from 'elysia'
import { isHtml } from '@elysiajs/html'
new Elysia()
.get('/none', () => '<h1>Hello World</h1>')
.onAfterHandle(({ response, set }) => {
if (isHtml(response))
set.headers['Content-Type'] = 'text/html; charset=utf8'
})
.get('/', () => '<h1>Hello World</h1>')
.get('/hi', () => '<h1>Hello World</h1>')
.listen(3000)
The response should be listed as follows:
Path | Content-Type |
---|---|
/ | text/html; charset=utf8 |
/hi | text/html; charset=utf8 |
/none | text/plain; charset=utf8 |
Events from other plugins are also applied to the route so the order of code is important.
Order of code
The order of Elysia's life-cycle code is very important.
Elysia's life-cycle event is stored as a queue, aka first-in first-out. So Elysia will always respect the order of code from top-to-bottom followed by the order of life-cycle events.
import { Elysia } from 'elysia'
new Elysia()
.onBeforeHandle(() => {
console.log('1')
})
.onAfterHandle(() => {
console.log('3')
})
.get('/', () => 'hi', {
beforeHandle() {
console.log('2')
}
})
.listen(3000)
Console should log as the following:
1
2
3