GioJSdocs
Building Your App

Middleware

Declarative redirects, rewrites, response headers, and auth guards - executed in Rust before routing.

GioJS middleware is a set of declarative rules, not request-time JavaScript. You describe redirects, rewrites, response headers, and cookie guards; they are compiled once at load time and evaluated in the Rust HTTP layer on every request - before routing, before the cache, and before any Node code runs. Because the rules execute inside the server itself rather than in a separate step, there is no request header that skips them (contrast with Next.js middleware, where thex-middleware-subrequest header could bypass authorization checks entirely - CVE-2025-29927). A request either satisfies the rules or never reaches your pages.

Rules come from two places, and you can use either or both:

  • gio.toml - static rules in your server config
  • middleware.ts - a file at the project root (sibling of app/)

gio.toml rules

toml
[[redirects]]
from   = "/old-home"
to     = "/"
status = 301          # 301, 302, 307, or 308 - defaults to 302

[[redirects]]
from = "/blog/:slug"
to   = "/posts/:slug"

[[rewrites]]
from = "/docs/*rest"  # served under the requested URL
to   = "/guide/*rest"

[[headers]]
path = "/docs/*rest"
[headers.headers]
x-frame-options = "DENY"

[[guards]]
path           = "/admin/*rest"
require_cookie = "session"
redirect_to    = "/login"

middleware.ts

The same four rule kinds, typed. Export the result of defineMiddleware as the default export (guard fields are camelCase here):

ts
// middleware.ts (project root, next to app/)
import { defineMiddleware } from '@gio.js/core';

export default defineMiddleware({
  redirects: [
    { from: '/blog/:slug', to: '/posts/:slug', status: 301 },
  ],
  rewrites: [
    { from: '/docs/*rest', to: '/guide/*rest' },
  ],
  headers: [
    { path: '/admin/*rest', headers: { 'x-frame-options': 'DENY' } },
  ],
  guards: [
    { path: '/admin/*rest', requireCookie: 'session', redirectTo: '/login' },
  ],
});

These rules travel to the Rust server inside the worker's READY frame and refresh whenever the worker restarts. In development the watcher restarts the worker on changes under app/ and to gio.toml / gio.config.*, so middleware edits are picked up with the next restart. gio.toml rules are compiled once at server startup.

Pattern language

Patterns use the routing conventions and must start with /:

  • Literal segments - /about matches exactly /about (a trailing slash on the request is tolerated)
  • :param - captures one segment: /posts/:id matches /posts/42 but not /posts or /posts/a/b
  • *rest - captures the entire remainder, slashes included: /docs/*rest matches /docs/a/b/c. It must be the last segment and requires at least one segment (/docs alone does not match)

Captures substitute into to targets by name, in any order:

toml
[[redirects]]
from = "/u/:user/p/:post"
to   = "/p/:post/by/:user"   # /u/alice/p/42 -> /p/42/by/alice

Every rule is validated when it is loaded, never at request time: a relative pattern, a catch-all in the middle, a to target referencing an unknown capture, a disallowed redirect status, or an invalid header name/value causes that rule to be skipped with a warning in the server log.

Evaluation order

Per request, the short-circuiting phases run in a fixed order:

  1. Guards
  2. Redirects
  3. Rewrites

Within each phase the first matching rule wins, and gio.toml rules are checked before middleware.ts rules. The phase order holds across both sources - a middleware.ts guard beats a gio.toml redirect on the same path.

The original query string is preserved verbatim: redirects append it to the Location header, and rewrites keep it on the rewritten URI. A rewrite changes the path that routing and the cache key see, while the browser URL stays what the client requested.

Guards

A guard redirects (302) any request to a matching path that does not carry a non-empty cookie of the given name - the request never reaches Node. It is a presence check, not validation: use it to keep anonymous traffic out of authenticated sections cheaply, and verify the session itself in getServerSideProps or a route handler.

Header rules

Header rules stamp response headers and do not short-circuit: every header rule whose path matches contributes its headers. They match the path the client requested (before any rewrite), and names/values are validated once at load time.

Internal /_gio/* endpoints (health, metrics, image optimization, devtools) are exempt from all middleware rules.