Skip to content
Back to skills

2611 Server Functions 535f27be

ASecurity

Patterns for `createServerFn` on Cloudflare Workers with D1, Drizzle, and better-auth.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmenttypescriptreactapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2611-server-functions_535f27be --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2611 Server Functions 535f27be?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2611 Server Functions 535f27be
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2611-server-functions-535f27be/badge)](https://www.skillsdirectory.com/skills/tools-only-2611-server-functions-535f27be)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Server Functions Reference

Patterns for `createServerFn` on Cloudflare Workers with D1, Drizzle, and better-auth.

## Core Pattern

```typescript
import { createServerFn } from "@tanstack/react-start";
import { getDb } from "../db";

export const getItems = createServerFn({ method: "GET" }).handler(async () => {
  const db = getDb(); // Per-request Drizzle client
  return db.select().from(items).all();
});
```

**CRITICAL**: Create the Drizzle client inside the handler, NOT at module level. The `env` import from `cloudflare:workers` is only available during request handling.

## Env Access on Cloudflare

```typescript
// ✅ CORRECT — inside handler or called from handler
import { env } from "cloudflare:workers";

export const getItems = createServerFn({ method: "GET" }).handler(async () => {
  const db = drizzle(env.DB, { schema }); // env available here
  return db.select().from(items).all();
});

// ❌ WRONG — module level
import { env } from "cloudflare:workers";
const db = drizzle(env.DB, { schema }); // env is undefined here!
```

## Input Validation

Use `.inputValidator()` with Zod or inline validator:

```typescript
import { z } from "zod";

export const createItem = createServerFn({ method: "POST" })
  .inputValidator(
    z.object({
      name: z.string().min(1),
      description: z.string().optional(),
    })
  )
  .handler(async ({ data }) => {
    const db = getDb();
    const id = crypto.randomUUID();
    await db.insert(items).values({ id, ...data, createdAt: Date.now() });
    return { id };
  });
```

## Protected Server Functions

Check auth session inside the handler. Throw redirect for unauthenticated users:

```typescript
import { redirect } from "@tanstack/react-router";
import { getAuth } from "../lib/auth.server";

async function requireSession(request?: Request) {
  const auth = getAuth();
  const session = await auth.api.getSession({ headers: request?.headers ?? new Headers() });
  if (!session) {
    throw redirect({ to: "/login" });
  }
  return session;
}

// Reusable auth check as a server function
export const getSessionFn = createServerFn({ method: "GET" }).handler(
  async ({ request }) => {
    const auth = getAuth();
    const session = await auth.api.getSession({
      headers: request.headers,
    });
    return session;
  }
);

export const getItems = createServerFn({ method: "GET" }).handler(
  async ({ request }) => {
    const session = await requireSession(request);
    const db = getDb();
    return db
      .select()
      .from(items)
      .where(eq(items.userId, session.user.id))
      .all();
  }
);
```

## Route Loader Pattern

Use server functions in route `loader` for data fetching:

```typescript
import { createFileRoute } from "@tanstack/react-router";
import { getItems } from "../../server/functions";

export const Route = createFileRoute("/_authed/items")({
  loader: () => getItems(),
  component: ItemsPage,
});

function ItemsPage() {
  const items = Route.useLoaderData();

  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}
```

## Route beforeLoad (Auth Guard)

Use `beforeLoad` on layout routes to protect groups of routes:

```typescript
import { createFileRoute, redirect } from "@tanstack/react-router";
import { getSessionFn } from "../server/functions";

export const Route = createFileRoute("/_authed")({
  beforeLoad: async () => {
    const session = await getSessionFn();
    if (!session) {
      throw redirect({ to: "/login" });
    }
    return { session };
  },
});
```

Child routes access the session via `useRouteContext()`:

```typescript
function DashboardPage() {
  const { session } = Route.useRouteContext();
  return <h1>Welcome, {session.user.name}!</h1>;
}
```

## Mutation + Router Invalidation

After mutations, invalidate the router to refetch loader data:

```typescript
function CreateItemForm() {
  const router = useRouter();

  const handleSubmit = async (data: NewItem) => {
    await createItem({ data });
    router.invalidate(); // Refetches all active loaders
    router.navigate({ to: "/items" });
  };

  return <form onSubmit={...}>...</form>;
}
```

## Type Safety

Use Drizzle's type inference for server function input/output:

```typescript
import { items } from "../db/schema";
import type { InferSelectModel, InferInsertModel } from "drizzle-orm";

type Item = InferSelectModel<typeof items>;
type NewItem = InferInsertModel<typeof items>;

export const getItems = createServerFn({ method: "GET" }).handler(
  async (): Promise<Item[]> => {
    const db = getDb();
    return db.select().from(items).all();
  }
);
```

## Error Handling

Server functions can throw errors that propagate to the client:

```typescript
export const getItem = createServerFn({ method: "GET" })
  .inputValidator(z.object({ id: z.string() }))
  .handler(async ({ data }) => {
    const db = getDb();
    const item = await db
      .select()
      .from(items)
      .where(eq(items.id, data.id))
      .get();

    if (!item) {
      throw new Error("Item not found");
    }

    return item;
  });
```

For auth failures, always use `throw redirect()` — not error responses.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.