And More

Helpers that sit beside routing and handlers: disk files, cross-origin access, and styling.

Static files

staticFile(ctx, dir, relative) serves a file from disk. Call it from a route handler, not middleware. The third argument is the path under dir, usually from ctx.params.

main.ts

import { CacheStrategy, route, staticFile } from "dashi";

route("/static/:file", {
  GET: ({ ctx }) =>
    staticFile(ctx, `{import.meta.dirname}/static`, ctx.params.file, {
      strategy: CacheStrategy.Immutable,
    }),
});

Cache defaults to CacheStrategy.NoStore. Omitted paths and traversal outside dir return 404.

CORS

cors() is middleware for serve() and group(). OPTIONS returns 204 with CORS headers; other methods pass through and get the same headers on the response.

credentials: true requires an explicit origin. Wildcard * is not allowed with credentials.

api.ts

import { cors } from "dashi/cors";
import { group, route } from "dashi";

export const api = group("/api", ({ route }) => ({
  middleware: [cors({ origin: "https://app.example.com", credentials: true })],
  routes: [route("/ok", { GET: () => Response.json({ ok: true }) })],
}));

CSS

dashi is CSS-agnostic. Link a stylesheet from your layout like any other app.

For cache-friendly CSS, build to a hashed filename and serve it with staticFile on a route such as /generated/:file. The URL changes when the file changes, so you can set CacheStrategy.Immutable.

The deno create jsr:@cookingpot/dashi scaffold implements Tailwind v4 like this.