Files
gh-greyhaven-ai-claude-code…/skills/api-design-standards/examples/tanstack-start.md
2025-11-29 18:29:15 +08:00

2.4 KiB

TanStack Start Server Functions

Complete server function examples with Drizzle ORM and tenant isolation.

See ../templates/tanstack-server-function.ts for full template.

Complete CRUD Server Functions

// app/routes/api/users.ts
import { createServerFn } from "@tanstack/start";
import { z } from "zod";
import { db } from "~/utils/db.server";
import { usersTable } from "~/db/schema";
import { getAuthUser } from "~/utils/auth.server";
import { eq, and, like, count, desc } from "drizzle-orm";
import { hashPassword } from "~/utils/password.server";

// Validation schemas
const createUserSchema = z.object({
  email: z.string().email(),
  fullName: z.string().min(1).max(255),
  password: z.string().min(8),
});

const updateUserSchema = z.object({
  email: z.string().email().optional(),
  fullName: z.string().min(1).max(255).optional(),
  isActive: z.boolean().optional(),
});

// List users
export const listUsers = createServerFn({ method: "GET" })
  .validator(z.object({ skip: z.number().min(0).default(0), limit: z.number().min(1).max(100).default(100) }))
  .handler(async ({ data, context }) => {
    const authUser = await getAuthUser(context);
    if (!authUser) throw new Error("Unauthorized", { status: 401 });

    const users = await db.select().from(usersTable)
      .where(eq(usersTable.tenantId, authUser.tenantId))
      .orderBy(desc(usersTable.createdAt))
      .limit(data.limit).offset(data.skip);

    const [{ count: total }] = await db.select({ count: count() })
      .from(usersTable).where(eq(usersTable.tenantId, authUser.tenantId));

    return {
      items: users.map(({ hashedPassword, ...user }) => user),
      total, skip: data.skip, limit: data.limit,
      hasMore: data.skip + data.limit < total,
    };
  });

// Create user
export const createUser = createServerFn({ method: "POST" })
  .validator(createUserSchema)
  .handler(async ({ data, context }) => {
    const authUser = await getAuthUser(context);
    if (!authUser) throw new Error("Unauthorized", { status: 401 });

    const [user] = await db.insert(usersTable).values({
      ...data, hashedPassword: await hashPassword(data.password),
      tenantId: authUser.tenantId,
    }).returning();

    const { hashedPassword: _, ...userPublic } = user;
    return userPublic;
  });

See also: fastapi-crud.md for FastAPI examples