Documentation

Docs

Introduction

What is rune

Philosophy

Why rune

Architecture

Getting Started

Installation

Quick start

Project structure

The dev loop

Tutorial: build an app

1. Create the app

2. Pages & routes

3. Layouts

4. State with Mimir

5. SSR & data

6. API routes

7. Styling

8. Build & deploy

Core Concepts

The application object

Rendering pipeline

Hydration

The build manifest

Configuration (ekko.json)

Permissions

Conventions

Routing

File-based routing

Dynamic routes

The router (useRouter)

Navigation & Link

Guards & redirects

Programmatic routes

Server-Side Rendering

Overview

The ssr() function

Strategies (eager/lazy)

SSR → hydration

Caching & invalidation

SEO

Mimir, state management

Overview

Atoms

Reading & writing

Selectors (derived state)

Subscriptions & the store

SSR & hydration

Persistence & sessions

Patterns & recipes

Pitfalls

Pages & Layouts

Pages

Layouts

Error & not-found

API Routes

Defining routes

Request & response

Middleware

helmet

cors

rateLimit

bodyLimit

validateContentType

csrf

requestId

timeout

errorHandler

httpsRedirect

secureCookies

ipFilter

safePath

Validation & options

Styling & Theming

SCSS

Theming (light/dark)

Asgard integration

No flash (no-FOUC)

Building & Deploying

The build

Static assets

Production deploy

API Reference

ekko:rune

ekko:rune/router

ekko:rune/mimir

ekko:rune/seo

ekko:ssr / css

ekko.json schema

CLI commands

Guides

Rune app from scratch

Recipes

Dark mode

Forms

Data fetching

Authentication

Pagination

FAQ (use cases)

Documentation

Docs

Introduction

What is rune

Philosophy

Why rune

Architecture

Getting Started

Installation

Quick start

Project structure

The dev loop

Tutorial: build an app

1. Create the app

2. Pages & routes

3. Layouts

4. State with Mimir

5. SSR & data

6. API routes

7. Styling

8. Build & deploy

Core Concepts

The application object

Rendering pipeline

Hydration

The build manifest

Configuration (ekko.json)

Permissions

Conventions

Routing

File-based routing

Dynamic routes

The router (useRouter)

Navigation & Link

Guards & redirects

Programmatic routes

Server-Side Rendering

Overview

The ssr() function

Strategies (eager/lazy)

SSR → hydration

Caching & invalidation

SEO

Mimir, state management

Overview

Atoms

Reading & writing

Selectors (derived state)

Subscriptions & the store

SSR & hydration

Persistence & sessions

Patterns & recipes

Pitfalls

Pages & Layouts

Pages

Layouts

Error & not-found

API Routes

Defining routes

Request & response

Middleware

helmet

cors

rateLimit

bodyLimit

validateContentType

csrf

requestId

timeout

errorHandler

httpsRedirect

secureCookies

ipFilter

safePath

Validation & options

Styling & Theming

SCSS

Theming (light/dark)

Asgard integration

No flash (no-FOUC)

Building & Deploying

The build

Static assets

Production deploy

API Reference

ekko:rune

ekko:rune/router

ekko:rune/mimir

ekko:rune/seo

ekko:ssr / css

ekko.json schema

CLI commands

Guides

Rune app from scratch

Recipes

Dark mode

Forms

Data fetching

Authentication

Pagination

FAQ (use cases)

Middleware

Middleware runs for every request before your page and API handlers. Register it with app.use(...). A middleware is a (req, res, next) function: inspect or augment req, set headers or short-circuit on res, then call next() to continue (or send a response and return to stop).

1
app.use((req, res, next) => { /* ... */ next(); });

EkkoJS ships 13 production-ready middleware in ekko:web, so you rarely write the cross-cutting ones yourself. Import what you need and app.use it:

1
2
3
4
import {
helmet, cors, rateLimit, bodyLimit, csrf, requestId, timeout,
errorHandler, httpsRedirect, secureCookies, ipFilter, validateContentType, safePath,
} from "ekko:web";

Each middleware has its own page below, with a flow diagram, the use case, every option, and a copy-paste example. This page is the map: what exists, what order to register it in, and a recommended production stack.

The built-in middleware

MiddlewareWhat it does
helmetSecurity headers on every response.
corsAllow other origins to call your API.
rateLimitThrottle abuse / brute force by IP.
bodyLimitReject oversized request bodies (DoS).
validateContentTypeReject unexpected body content types.
csrfCSRF protection for cookie-auth writes.
requestIdCorrelate logs across a request.
timeoutReturn 504 for a hung request.
errorHandlerClean 500s + logging, no leaked stacks.
httpsRedirectForce plaintext requests to HTTPS.
secureCookiesMake every cookie Secure by default.
ipFilterAllow / deny by client IP.
safePathBlock .. path traversal app-wide.

Order matters

Middleware runs in registration order, before route handlers. Put the broad, always-on concerns first (request id, security headers), the ones that may reject early next (rate limit, body limit, content-type, CSRF), and the error wrapper outermost:

1
2
3
4
5
6
7
8
9
app.use(errorHandler()); // wraps everything below
app.use(requestId());
app.use(helmet());
app.use(httpsRedirect());
app.use(cors({ origin: ["https://app.example.com"], credentials: true }));
app.use(rateLimit({ max: 300, window: 60_000 }));
app.use(bodyLimit({ max: 1_000_000 }));
app.use(validateContentType());
app.use(csrf());
1
2
3
4
5
6
7
8
9
10
11
12
import { errorHandler, requestId, helmet, cors, rateLimit, bodyLimit, validateContentType, secureCookies } from "ekko:web";
 
app.use(errorHandler()); // shape errors, log
app.use(requestId()); // trace
app.use(helmet()); // security headers
app.use(secureCookies()); // Secure cookies
app.use(cors({ origin: ["https://app.example.com"], credentials: true })); // if cross-origin clients
app.use(rateLimit({ max: 300, window: 60_000 })); // abuse protection
app.use(bodyLimit({ max: 1_000_000 })); // size cap
app.use(validateContentType({ types: ["application/json"] })); // type allow-list
// app.use(csrf()); // if cookie-authenticated
// ...then your pages + app.api(...) handlers

Adjust to your app: drop cors if the front-end is this same rune app; add csrf for cookie auth; add httpsRedirect / ipFilter where the topology calls for it.

Per-route middleware and options

Beyond app-wide app.use(...), you can scope middleware and limits to one route. Pass middleware functions between the path and the handler, or use route options:

1
2
3
4
5
// per-route middleware (runs only for this route)
app.api("POST", "/api/upload", bodyLimit({ max: 10_000_000 }), async (req, res) => { /* ... */ });
 
// per-route rate limit (tighter than the global one) , via the options object
app.api("POST", "/api/login", { rateLimit: { max: 5, window: 60_000 } }, async (req) => login(await req.json()));

Route options the server understands include rateLimit: { max, window }, an auth scheme name, required roles, and anonymous: true (skip auth). These let a generous global policy coexist with a strict per-endpoint one (e.g. 300 req/min globally, but 5 login attempts/min).

Writing your own

The built-ins cover the common cases; write a custom middleware for app-specific logic, the most common being authentication (attach req.user):

1
2
3
4
5
app.use((req, _res, next) => {
const token = (req.headers["authorization"] || "").replace(/^Bearer /, "");
req.user = token ? verifyToken(token) : null; // null when anonymous
next();
});

rune carries req.user into the page data as __user, so SSR and components can read the current user, see Authentication. Short-circuit by writing a response and returning without next():

1
2
3
4
app.use((req, res, next) => {
if (req.path.startsWith("/admin") && !isAdmin(req.user)) { res.redirect("/login"); return; }
next();
});

Next: per-route options and automatic 405s, Validation & options.