This isn’t just another release — it’s a massive routing overhaul. We’re introducing the new TrieRouter under the hood, delivering blistering-fast match speeds, smarter fallback handling, and precise middleware chains.
From the moment a request hits your app, every part of the routing pipeline is now more predictable, transparent, and customizable.
We didn’t stop at performance — we improved on error handling, brought further consistency across the entire chain, and added some additional tools to shape fallback and error behavior exactly how you want. No surprises, no magic — just clean, controlled power.
Added
- feat: New TrieRouter implementation under the hood for all routing operations — offering faster, more precise matching with support for static, param, wildcard, and deep-nested patterns. This powers the entire TriFrost routing tree and improves performance across large route sets.
- feat: Introduced
.onError()
handler registration. This allows end-users to override the default500
error fallback. By default, if no error handler is registered TriFrost still callsctx.abort(500)
, but now you can fully customize how error fallback routes behave:
/* Generic responder */
app.onError(ctx => ctx.json({error: 'something went wrong'}, {status: 500}));
/* Less generic */
app.onError(ctx => {
switch (ctx.statusCode) {
case 401:
return ctx.json({error: 'no access you have'}, {status: 401});
case 500:
return ctx.json({error: 'oopsie'}, {status: 500});
}
});
/* Somewhere in your code */
... return ctx.setStatus(401);
Improved
- feat:
ctx.html
will now automatically prefix a<!DOCTYPE html>
when it detects a full-page html body starting with<html
- feat:
ctx.html
,ctx.json
,ctx.text
andctx.file
will no longer set theContent-Type
if aContent-Type
already exists on the response headers, the default behavior remains the same if noContent-Type
exists on the response headers - feat:
ctx.abort()
will now early-return if the context was already aborted, ensuring no side-effects when a request was already aborted - qol: All TriFrost middleware now have a FingerPrint symbol marker attached to it to identify it clearly inside of the middleware chain for a route, this is both helpful for internal behaviors (see options routes in Trie router), but can also be helpful for future plugins 🧙♂️.
import {
Sym_TriFrostFingerPrint,
Sym_TriFrostMiddlewareCors,
Sym_TriFrostMiddlewareSecurity,
} from '@trifrost/core';
const fn = ...; /* Some random method from a middleware chain */
switch (Reflect.get(fn, Sym_TriFrostFingerPrint)) {
case Sym_TriFrostMiddlewareCors:
console.log('Fn is trifrost cors');
break;
case Sym_TriFrostMiddlewareSecurity:
console.log('Fn is trifrost security');
break;
...
}
- qol: TriFrost now automatically catches handlers or middleware that only set a status but don’t end the response. For example:
return ctx.setStatus(404);
will now trigger the nearest.onNotFound()
handler if registered, or.onError()
if the status is >= 400 — ensuring graceful fallback handling even when the context isn’t explicitly locked. - qol: Options routes will no longer get the entire middleware chain for the path they're running on, but instead will only look at cherry picking a registered
Cors
middleware from the chain. - feat: You can now chain .use() in between verb methods, allowing branch-specific middleware stacking. This makes the middleware chain incrementally extendable, letting you scope middleware precisely where needed without affecting earlier verbs.
/* Works on a router level */
router
.use(globalMw)
.get('/users', usersHandler) // gets globalMw
.use(adminMw)
.post('/admin', adminHandler); // gets globalMw + adminMw
/* Works inside .route blocks */
router
.use(globalMw)
.route('/users', route => {
route
.use(aclChecker).get(usersHandler) /* globalMw + aclChecker */
.use(writeAclChecker).post(usersPostHandler) /* globalMw + aclChecker + writeAclChecker */
)
.use(adminMw)
.post('/admin', adminHandler); /* globalMw + adminMw */
Breaking
- The
.limit()
method now applies rate limiting immediately in-place when chained, instead of magically attaching at the end of the middleware chain. This aligns with TriFrost’s no-magic-ever philosophy, making middleware chains predictable and readable. Previously,.limit()
was automatically pushed to the end, regardless of where you called it. Now, its position matters — allowing clear and intuitive control.
router
.limit(5) // ⏰ applies here
.use(someMw) // ✅ runs AFTER limit
.get('/path', h);
router
.use(myFancyAuth) // ✅ runs BEFORE limit
.limit(ctx => ctx.state.$auth.limit) // ⏰ applies here and can use the response from $auth)
.get('/path', h);
- The
.notfound()
method has been renamed to.onNotFound()
for better semantic clarity, matching.onError()
and making route fallback behavior easier to reason about.
With the new TrieRouter, TriFrost delivers consistent, blazing-fast route matching, holding its ground even against static lookups while unlocking dynamic and param patterns with near-zero performance cost.
Glacier isn’t just a name — it’s the feeling you get when your backend slices through traffic with icy precision. Stay sharp, stay fast, stay frosty ❄️