Fastro API Documentation

Complete API reference for Fastro - covering server setup, routing, middleware, SSR, stores, and more

Core Server

Fastro Class

The main server class that implements the Fastro interface.

import fastro from "https://fastro.deno.dev/mod.ts";

const f = new fastro(options?: Record<string, any>);

Constructor Options

  • options - Optional configuration object for server initialization

Methods

serve(options?: { port?: number; onListen?: ListenHandler }): Promise<void>

Start the HTTP server.

await f.serve({
  port: 8000,
  onListen: ({ hostname, port }) => {
    console.log(`Server running on ${hostname}:${port}`);
  },
});
shutdown(): void

Gracefully shutdown the server.

await f.shutdown();
group(mf: ModuleFunction): Promise<Fastro>

Group related routes and handlers into modules.

const userModule = (f: Fastro) => {
  return f.get("/users", () => "User list")
    .post("/users", () => "Create user");
};

await f.group(userModule);
use(...handlers: Handler[]): Fastro

Add global middleware.

f.use((req, ctx) => {
  console.log(`${req.method} ${req.url}`);
  return ctx.next();
});
static(path: string, options?: StaticOptions): Fastro

Serve static files.

f.static("/static", {
  folder: "public",
  maxAge: 86400,
  referer: true,
});

StaticOptions:

  • folder?: string - Static files directory (default: "static")
  • maxAge?: number - Cache control max-age in seconds (default: 0)
  • referer?: boolean - Check referer header (default: false)

Routing

HTTP Methods

get(path: string, ...handlers: Handler[]): Fastro

Handle GET requests.

f.get("/", () => "Hello World");
f.get("/users/:id", (req) => `User ${req.params?.id}`);

post(path: string, ...handlers: Handler[]): Fastro

Handle POST requests.

f.post("/users", async (req) => {
  const body = await req.parseBody<{ name: string }>();
  return { message: `Created user ${body.name}` };
});

put(path: string, ...handlers: Handler[]): Fastro

Handle PUT requests.

f.put("/users/:id", (req) => `Update user ${req.params?.id}`);

patch(path: string, ...handlers: Handler[]): Fastro

Handle PATCH requests.

f.patch("/users/:id", (req) => `Patch user ${req.params?.id}`);

delete(path: string, ...handlers: Handler[]): Fastro

Handle DELETE requests.

f.delete("/users/:id", (req) => `Delete user ${req.params?.id}`);

options(path: string, ...handlers: Handler[]): Fastro

Handle OPTIONS requests.

f.options("/api/*", () =>
  new Response(null, {
    headers: {
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
      "Access-Control-Allow-Headers": "Content-Type",
    },
  }));

head(path: string, ...handlers: Handler[]): Fastro

Handle HEAD requests.

f.head("/health", () => new Response(null, { status: 200 }));

Route Parameters

Routes support URL parameters using the :param syntax:

f.get("/users/:id", (req) => {
  const userId = req.params?.id;
  return `User ID: ${userId}`;
});

f.get("/posts/:category/:slug", (req) => {
  const { category, slug } = req.params || {};
  return `Category: ${category}, Slug: ${slug}`;
});

HTTP Request Extensions

HttpRequest Class

Extends the native Request with additional properties:

class HttpRequest extends Request {
  params?: Record<string, string | undefined>;
  query?: Record<string, string>;
  parseBody!: <T>() => Promise<T>;
}

Properties

  • params - URL parameters from route patterns
  • query - Query string parameters as an object

Methods

parseBody<T>(): Promise<T>

Parse request body as JSON.

f.post("/users", async (req) => {
  const userData = await req.parseBody<{
    name: string;
    email: string;
  }>();

  return { message: `Hello ${userData.name}` };
});

Context API

Context Class

Provides request context and response utilities.

class Context {
  info: Deno.ServeHandlerInfo;
  url: URL;
  server: Fastro;
  kv: Deno.Kv;
  options: Record<string, any>;
  stores: Map<string, Store<any, any>>;

  render<T>(data?: T, headers?: Headers): Response | Promise<Response>;
  send<T>(data?: T, status?: number, headers?: Headers): Response;
  next(): void;
}

Methods

render<T>(data?: T, headers?: Headers): Response | Promise<Response>

Render JSX components or page data.

// Render JSX component
f.get("/hello", (_, ctx) => {
  return ctx.render(<h1>Hello World</h1>);
});

// Render page data (when used in page handlers)
f.page("/profile", {
  component: ProfilePage,
  layout: AppLayout,
  handler: (_, ctx) => {
    return ctx.render({ user: { name: "John" } });
  },
});
send<T>(data?: T, status?: number, headers?: Headers): Response

Send response data.

f.get("/api/users", (_, ctx) => {
  return ctx.send([{ id: 1, name: "John" }], 200);
});

f.get("/error", (_, ctx) => {
  return ctx.send({ error: "Not found" }, 404);
});
next(): void

Call the next middleware in the chain.

f.use((req, ctx) => {
  console.log(`Request: ${req.method} ${req.url}`);
  return ctx.next();
});

Middleware

Handler Type

type Handler = (
  req: HttpRequest,
  ctx: Context,
) => string | object | number | boolean | undefined | Response | Promise<any>;

Global Middleware

Applied to all routes:

f.use((req, ctx) => {
  // Add timestamp to all requests
  req.timestamp = Date.now();
  return ctx.next();
});

f.use(async (req, ctx) => {
  // Authentication middleware
  const token = req.headers.get("Authorization");
  if (!token) {
    return ctx.send({ error: "Unauthorized" }, 401);
  }
  return ctx.next();
});

Route-specific Middleware

Applied to specific routes:

f.get(
  "/protected", // Middleware 1
  (req, ctx) => {
    if (!req.headers.get("Authorization")) {
      return ctx.send({ error: "Unauthorized" }, 401);
    }
    return ctx.next();
  }, // Middleware 2
  (req, ctx) => {
    req.user = { id: 1, name: "John" };
    return ctx.next();
  }, // Final handler
  (req, ctx) => {
    return ctx.send({ message: `Hello ${req.user.name}` });
  },
);

Pages & SSR

Page Definition

type Page<T = any> = {
  component: FunctionComponent | JSX.Element;
  layout: Layout<T>;
  handler: Handler;
  script?: string;
  folder?: string;
};

Creating Pages

import { PageProps } from "@app/core/server/types.ts";

// Page component
function HomePage({ data }: PageProps<{ title: string }>) {
  return <h1>{data.title}</h1>;
}

// Layout component
function AppLayout({ children, data }: LayoutProps<any>) {
  return (
    <html>
      <head>
        <title>{data.title}</title>
      </head>
      <body>{children}</body>
    </html>
  );
}

// Register page
f.page("/", {
  component: HomePage,
  layout: AppLayout,
  handler: (_, ctx) => {
    return ctx.render({ title: "Welcome" });
  },
});

Page Middleware

f.page(
  "/dashboard",
  {
    component: Dashboard,
    layout: AppLayout,
    handler: (req, ctx) => {
      return ctx.render({ user: req.user });
    },
  }, // Page middleware
  (req, ctx) => {
    // Authentication check
    if (!req.user) {
      return new Response(null, {
        status: 302,
        headers: { Location: "/login" },
      });
    }
    return ctx.next();
  },
);

Client-side Hydration

Pages with function components automatically support client-side hydration:

function Counter({ data }: PageProps<{ count: number }>) {
  const [count, setCount] = useState(data.count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

Store

Store Class

Thread-safe, TTL-enabled key-value store with persistence.

import { Store } from "@app/core/map/mod.ts";

const store = new Store<string, any>({
  key: "my-store",
  namespace: ["app", "cache"],
});

Constructor Options

type StoreOptions = {
  key: string;
  namespace?: Array<string>;
} | null;

Methods

set(key: K, value: V, ttl?: number): Store

Set a value with optional TTL (time-to-live) in milliseconds.

store.set("user:1", { name: "John" }, 60000); // Expires in 1 minute
store.set("config", { theme: "dark" }); // No expiration
get(key: K): Promise<V | undefined>

Get a value by key.

const user = await store.get("user:1");
console.log(user); // { name: "John" } or undefined if expired
has(key: K): Promise<boolean>

Check if key exists and hasn't expired.

const exists = await store.has("user:1");
delete(key: K): boolean

Delete a key.

const deleted = store.delete("user:1");
clear(): void

Clear all entries.

store.clear();
size(): number

Get the number of entries (excluding expired ones).

const count = store.size();
commit(): Promise<any>

Manually save to persistent storage.

await store.set("key", "value").commit();
sync(interval?: number): number

Auto-sync to persistent storage at intervals.

const intervalId = store.sync(5000); // Sync every 5 seconds
clearInterval(intervalId); // Stop syncing
destroy(): Store

Clean up resources and clear data.

store.destroy();

Using Store in Handlers

f.get("/cache/:key", async (req, ctx) => {
  const key = req.params?.key;
  const value = await ctx.stores.get("cache")?.get(key);

  if (!value) {
    return ctx.send({ error: "Not found" }, 404);
  }

  return ctx.send({ data: value });
});

f.post("/cache/:key", async (req, ctx) => {
  const key = req.params?.key;
  const { value, ttl } = await req.parseBody<{
    value: any;
    ttl?: number;
  }>();

  const store = ctx.stores.get("cache") || new Store({ key: "cache" });
  store.set(key, value, ttl);
  await store.commit();

  return ctx.send({ message: "Cached" });
});

Utils

Task Queue

Sequential task execution utility.

import { createTaskQueue } from "@app/core/utils/queue.ts";

const queue = createTaskQueue();

// Process tasks sequentially
const result1 = await queue.process(() => "task 1");
const result2 = await queue.process(async () => {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return "task 2";
});

Key-Value Database

Deno KV singleton instance.

import { collectValues, kv, reset } from "@app/core/utils/kv.ts";

// Set value
await kv.set(["users", "123"], { name: "John" });

// Get value
const user = await kv.get(["users", "123"]);

// List values
const users = kv.list({ prefix: ["users"] });
const allUsers = await collectValues(users);

// Reset database (development only)
await reset();

Build System

EsbuildMod

Handles client-side JavaScript bundling for page components.

import { EsbuildMod } from "@app/core/build/esbuildMod.ts";

const builder = new EsbuildMod(page);
const result = await builder.build();

Build Configuration

The build system automatically:

  • Bundles page components for client-side hydration
  • Generates hydration scripts
  • Minifies and optimizes JavaScript
  • Handles TypeScript and JSX compilation

Render Engine

Render Class

Handles server-side rendering and client-side hydration.

import { Render } from "@app/core/render/render.ts";

const render = new Render(server);

Methods

renderJsx(jsx: JSX.Element, headers?: Headers): Response

Render JSX to HTML response.

const response = render.renderJsx(<h1>Hello</h1>);
render<T>(key: string, page: Page, data: T, nonce: string, headers?: Headers): Promise<Response>

Render a complete page with layout and hydration.

const response = await render.render("/", page, { title: "Home" }, nonce);

Environment Variables

Development Mode

Set ENV=DEVELOPMENT to enable:

  • Hot reload functionality
  • Development-specific endpoints
  • Unminified JavaScript output

Build ID

  • DENO_DEPLOYMENT_ID - Used for cache busting in production

KV Database

  • DENO_KV_PATH - Custom path for Deno KV database

Error Handling

Global Error Handling

f.use((req, ctx) => {
  try {
    return ctx.next();
  } catch (error) {
    console.error("Error:", error);
    return ctx.send({ error: "Internal Server Error" }, 500);
  }
});

Route-specific Error Handling

f.get("/users/:id", async (req, ctx) => {
  try {
    const user = await getUserById(req.params?.id);
    return ctx.send(user);
  } catch (error) {
    if (error instanceof UserNotFoundError) {
      return ctx.send({ error: "User not found" }, 404);
    }
    throw error;
  }
});

Security

Content Security Policy

Automatic CSP header for pages:

"Content-Security-Policy": `script-src 'self' 'unsafe-inline' 'nonce-${nonce}' https: http: ; object-src 'none'; base-uri 'none';`

Referer Checking

For static files with referer: true:

f.static("/assets", { referer: true });

Examples

Basic API Server

import fastro from "https://fastro.deno.dev/mod.ts";

const f = new fastro();

f.get("/api/health", (_, ctx) => {
  return ctx.send({ status: "healthy" });
});

f.post("/api/users", async (req, ctx) => {
  const user = await req.parseBody<{ name: string; email: string }>();
  // Save user...
  return ctx.send({ id: 1, ...user }, 201);
});

await f.serve({ port: 8000 });

Full-stack Application

import fastro from "https://fastro.deno.dev/mod.ts";

const f = new fastro();

// Static files
f.static("/public", { folder: "assets", maxAge: 86400 });

// API routes
f.get("/api/posts", (_, ctx) => {
  return ctx.send([{ id: 1, title: "Hello World" }]);
});

// Pages
f.page("/", {
  component: HomePage,
  layout: AppLayout,
  handler: (_, ctx) => {
    return ctx.render({ title: "Welcome" });
  },
});

f.page("/posts/:id", {
  component: PostPage,
  layout: AppLayout,
  handler: async (req, ctx) => {
    const post = await getPost(req.params?.id);
    return ctx.render({ post });
  },
});

await f.serve({ port: 8000 });