Elysia 0.5 – Radiant
Posted on 15 May 2023 by @saltyaom

Named after Arknights’ original music, “Radiant” composed by Monster Sirent Records.
Radiant pushes the boundary of performance with more stability improvements—especially around types and dynamic routes.
Static Code Analysis
Elysia 0.4 introduced Ahead‑of‑Time compilation to optimise function calls and eliminate overhead.
Today we expand that to Static Code Analysis, making Elysia the fastest Bun‑based web framework.
Static Code Analysis allows Elysia to read your handlers, lifecycle hooks, and schemas, then compile the handler ahead of time, removing unused code and optimising where possible.
Example – JSON body parsing
app.post(
'/sign-in',
({ body }) => signIn(body),
{
schema: {
body: t.Object({
username: t.String(),
password: t.String(),
})
}
}
)
Because the schema specifies a JSON body, Elysia parses it as JSON instead of checking the Content-Type header dynamically, improving body parsing performance by ~2.5×.
Skipping unused body parsing
app.post(
'/id/:id',
({ params }) => id,
{
schema: {
body: t.Object({
username: t.String(),
password: t.String(),
})
}
}
)
With Static Code Analysis, if a route does not use the body, parsing is skipped entirely, boosting performance.
Performance gains
- Overall improvement ≈ 15 %
- Static router fast ≈ 33 %
- Empty query parsing ≈ 50 % faster
- Strict type body parsing ≈ 100 % faster
- Empty body parsing ≈ 150 % faster
The result is that Elysia 0.5.0‑beta.0 outperforms Stricjs (2.0.4).
New Router – “Memoirist”
From 0.2 we had Raikiri, a custom Radix‑tree router.
Complex reconciliation bugs in dynamic routes made it expensive.
Memoirist is a stable Radix‑tree router that fast‑tracks dynamic paths using Medley Router’s algorithm, while the static side benefits from Ahead‑of‑Time compilation.
Elysia will migrate from Raikiri to Memoirist for improved stability and speed.
TypeBox 0.28
Elysia’s strict type system is powered by TypeBox.
We upgraded from 0.26 → 0.28, adding fine‑grained type features such as Constant Generics.
new Elysia()
.decorate('version', 'Elysia Radiant')
.model(
'name',
Type.TemplateLiteral([
Type.Literal('Elysia '),
Type.Union([
Type.Literal('The Blessing'),
Type.Literal('Radiant')
])
])
)
.get('/', ({ version }) => version)
Ahead‑of‑Time & Type System
Elysia now introduces the dedicated URLEncoded type for body parsing:
t.URLEncoded→application/x-www-form-urlencodedt.Object→application/jsont.File→multipart/form-data- others →
text/plain
You can override with a type field:
app.post(
'/',
({ body }) => body,
{ type: 'json' }
)
type may be one of:
type ContentType =
| 'text'
| 'json'
| 'formdata'
| 'urlencoded'
| 'text/plain'
| 'application/json'
| 'multipart/form-data'
| 'application/x-www-form-urlencoded'
More details in the explicit body concept page.
Numeric Type
Parsing numeric strings was boilerplate‑heavy.
With Static Code Analysis, Numeric automatically parses numeric strings into numbers at runtime and type‑level.
app.get(
'/id/:id',
({ params }) => id,
{
params: t.Object({
id: t.Numeric()
})
}
)
You can use Numeric for params, query, headers, body, and response.
See the numeric type concept page.
Inline Schema
We moved from hook.schema to inline schemas in the hook statement—a breaking change.
Before
app.post(
'/',
({ body }) => body,
{
schema: {
body: t.Object({
username: t.String()
})
}
}
)
After
app.post(
'/',
({ body }) => body,
{
body: t.Object({
username: t.String()
})
}
)
Pros & Cons
| Aspect | Old | New |
|---|---|---|
| Separation | Explicit schema object | Inline in hook |
| Autocomplete | Clear separation | More options to choose from |
| Readability | Clear | Still clear with t.Type and function syntax |
"headers" Fields
Headers are now parsed automatically when referenced by code, thanks to Static Code Analysis.
app.post(
'/headers',
({ headers }) => headers['content-type']
)
The parsed headers are available as a plain object with lower‑case keys.
State, Decorate, Model Rework
Unified API: state, decorate, and model now share the same signature and overloads.
import { Elysia, t } from 'elysia'
const app = new Elysia()
// set model using a label
.model('string', t.String())
// set model using an object
.model({ number: t.Number() })
// set state using a label
.state('visitor', 1)
// set state using an object
.state({ multiple: 'value', are: 'now supported!' })
// set decorate using a label
.decorate('visitor', 1)
// set decorate using an object
.decorate({ name: 'world', number: 2 })
state, decorate, and model now support literal types and template strings.
app.get(
'/',
({ body }) => number,
{
body: t.Literal(1),
response: t.Literal(2)
}
)
Group and Guard
group can now take an optional guard scope as a second parameter.
// old
app.group('/v1', (app) =>
app.guard({ body: t.Literal() }, (app) =>
app.get('/student', () => 'Rikuhachima Aru')
)
)
// new
app.group(
'/v1',
{ body: t.Literal('Rikuhachima Aru') },
(app) => app.get('/student', () => 'Rikuhachima Aru')
)
Type Stability
Elysia’s type system is complex. We added unit tests at the type level to catch regressions before each release, ensuring greater type integrity.
Notable Improvements
- Add CommonJS support for running Elysia with Node adapter
- Remove manual fragment mapping to speed up path extraction
- Inline validator in
composeHandlerto improve performance - One‑time context assignment
- Lazy context injection via Static Code Analysis
- Ensure response non‑nullability
- Unioned body validator check
- Default object handler inherits
constructor.namemapping instead ofinstanceof- Dedicated error constructor
- Conditional literal function for
onRequestiteration - Improved WebSocket type
Breaking Changes
| Old | New |
|---|---|
innerHandle | fetch |
.setModel | .model |
hook.schema | hook (inline) |
// from
app.post('/', ({ body }) => body, {
schema: { body: t.Object({ username: t.String() }) }
})
// to
app.post('/', ({ body }) => body, {
body: t.Object({ username: t.String() })
})
mapPathnameRegex(internal) removed
Afterward
Pushing performance boundaries with Bun is exciting!
Elysia keeps getting faster, reliable and stable.
Thanks for your continuous support, and we hope to see you in the next release.
“I won’t let the people down, gonna raise them high… full speed ahead.”