Files
2025-11-29 18:29:26 +08:00

5.1 KiB

TanStack Start Server Functions

Complete examples for creating and using server functions.

Creating Server Functions

// src/lib/server/functions/users.ts

import { createServerFn } from "@tanstack/start";
import { db } from "~/lib/server/db";
import { users } from "~/lib/server/schema/users";
import { eq, and } from "drizzle-orm";

// GET server function (automatic caching)
export const getUserById = createServerFn("GET", async (
  userId: string,
  tenantId: string
) => {
  // Server-side code with database access
  const user = await db.query.users.findFirst({
    where: and(
      eq(users.id, userId),
      eq(users.tenant_id, tenantId) // Multi-tenant isolation!
    ),
  });

  if (!user) {
    throw new Error("User not found");
  }

  return user;
});

// POST server function (mutations)
export const createUser = createServerFn("POST", async (
  data: { name: string; email: string },
  tenantId: string
) => {
  const user = await db.insert(users).values({
    ...data,
    tenant_id: tenantId,
  }).returning();

  return user[0];
});

// DELETE server function
export const deleteUser = createServerFn("DELETE", async (
  userId: string,
  tenantId: string
) => {
  await db.delete(users).where(
    and(
      eq(users.id, userId),
      eq(users.tenant_id, tenantId)
    )
  );

  return { success: true };
});

Using Server Functions in Components

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { getUserById, createUser, deleteUser } from "~/lib/server/functions/users";

function UserManagement({ tenantId }: { tenantId: string }) {
  const queryClient = useQueryClient();

  // Query using server function
  const { data: user } = useQuery({
    queryKey: ["user", "123"],
    queryFn: () => getUserById("123", tenantId),
  });

  // Mutation using server function
  const createMutation = useMutation({
    mutationFn: (data: { name: string; email: string }) =>
      createUser(data, tenantId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });

  const deleteMutation = useMutation({
    mutationFn: (userId: string) => deleteUser(userId, tenantId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });

  return <div>...</div>;
}

Server Functions with Auth Context

// src/lib/server/functions/auth.ts

import { createServerFn } from "@tanstack/start";
import { getSession } from "~/lib/server/auth";

export const getAuthenticatedUser = createServerFn("GET", async () => {
  const session = await getSession();

  if (!session) {
    throw new Error("Not authenticated");
  }

  // Automatically includes tenant_id from session
  return {
    user: session.user,
    tenantId: session.tenantId,
  };
});
// Using in a component
function ProfilePage() {
  const { data: authData } = useQuery({
    queryKey: ["auth", "current-user"],
    queryFn: () => getAuthenticatedUser(),
    staleTime: 300000, // 5 minutes for auth data
  });

  return <div>Welcome, {authData?.user.name}!</div>;
}

Multi-Tenant Server Functions

// ALWAYS include tenant_id in server functions
export const listOrganizations = createServerFn("GET", async (tenantId: string) => {
  return await db.query.organizations.findMany({
    where: eq(organizations.tenant_id, tenantId),
  });
});

// Use in component with tenant context
function OrganizationsList() {
  const { tenantId } = useTenant(); // Custom hook for tenant context

  const { data: orgs } = useQuery({
    queryKey: ["organizations", tenantId],
    queryFn: () => listOrganizations(tenantId),
    staleTime: 60000,
  });

  return <div>...</div>;
}

RLS with Server Functions

// Server function uses RLS-enabled database connection
export const getUsers = createServerFn("GET", async () => {
  // Uses authenticated database connection with RLS
  // tenant_id automatically filtered by RLS policies
  return await db.query.users.findMany();
});

Key Patterns

HTTP Methods

  • GET: Read operations (automatic caching)
  • POST: Create operations
  • PUT: Update operations
  • DELETE: Delete operations

Multi-Tenant Isolation

Always include tenant_id parameter:

export const someFunction = createServerFn("GET", async (
  param: string,
  tenantId: string // REQUIRED
) => {
  // Filter by tenant_id
});

Error Handling

export const getUser = createServerFn("GET", async (userId: string) => {
  const user = await db.query.users.findFirst({
    where: eq(users.id, userId),
  });

  if (!user) {
    throw new Error("User not found"); // Automatically returns 500
  }

  return user;
});

Type Safety

Server functions are fully type-safe:

// Server function
export const updateUser = createServerFn("POST", async (
  userId: string,
  data: UserUpdate // Type-safe parameter
): Promise<User> => { // Type-safe return
  // ...
});

// Client usage (types inferred)
const mutation = useMutation({
  mutationFn: (data: UserUpdate) => updateUser(userId, data),
  // TypeScript knows the return type is Promise<User>
});