---
name: server-functions
description: "Use when: creating or modifying TanStack Start server functions, API routes or middleware."
---

> Mechanics (`createServerFn`, `.validator`, `createMiddleware`, `createHandlers`) are covered by TanStack's own skills — load `@tanstack/start-client-core#start-core/server-functions`, `#start-core/middleware`, and `#start-core/server-routes` for the API details. This skill adds only the wcz-layout middleware conventions on top.

## Rules

- Before generating code, clarify whether the feature should be exposed as public REST API routes or implemented as internal TanStack Start server functions (recommended), and what permission should be required for access.
- Every server function MUST include `authorizationMiddleware` — server functions are callable RPC endpoints, so a route-level `requirePermission` in `beforeLoad` only hides the UI and does not protect them.
- Follow the chain order for server functions: `middleware` → `validator` → `handler`.
- Follow the order for middleware arrays: `authorizationMiddleware(<permissionKey>)` → `databaseMiddleware()`.
- Use `validationMiddleware` only in REST API routes. Server functions use `.validator()`.
- `databaseMiddleware` wraps the handler in a DB transaction and sets `app.user_name` so audit triggers can record who made the change.
- Reuse schemas from `src/lib/schemas/`.

## File Placement

```
src/server/actions/             — server functions
src/routes/api/                 — REST API routes
src/server/middleware/          — databaseMiddleware
wcz-layout/middleware/          — authorizationMiddleware, validationMiddleware
src/server/db/schemas/          — tables, enums, relations
src/lib/schemas/                — Zod schemas (shared between client and server)
src/lib/auth/permissions.ts     — permission keys
```

## Examples

```ts
// src/server/actions/<feature>.ts
export const selectFeatures = createServerFn()
  .middleware([authorizationMiddleware("all"), databaseMiddleware()])
  .handler(async ({ context }) => {
    return await context.db.select().from(featureTable);
  });

export const insertFeature = createServerFn({ method: "POST" })
  .middleware([authorizationMiddleware("admin"), databaseMiddleware()])
  .validator(FeatureSchema)
  .handler(async ({ data, context }) => {
    await context.db.insert(featureTable).values(data);
  });

export const updateFeature = createServerFn({ method: "POST" })
  .middleware([authorizationMiddleware("admin"), databaseMiddleware()])
  .validator(FeatureSchema)
  .handler(async ({ data, context }) => {
    await context.db.update(featureTable).set(data).where(eq(featureTable.id, data.id));
  });

export const deleteFeature = createServerFn({ method: "POST" })
  .middleware([authorizationMiddleware("admin"), databaseMiddleware()])
  .validator(FeatureSchema.pick({ id: true }))
  .handler(async ({ data, context }) => {
    await context.db.delete(featureTable).where(eq(featureTable.id, data.id));
  });

// src/routes/api/<feature>s/index.ts
export const Route = createFileRoute("/api/features/")({
  server: {
    middleware: [databaseMiddleware()],
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: {
          middleware: [authorizationMiddleware("all")],
          handler: async ({ context }) => {
            const items = await context.db.select().from(featureTable);
            return Response.json(items);
          },
        },
        POST: {
          middleware: [validationMiddleware(FeatureSchema), authorizationMiddleware("admin")],
          handler: async ({ context }) => {
            const [response] = await context.db
              .insert(featureTable)
              .values(context.data)
              .returning();
            return Response.json(response, { status: 201 });
          },
        },
      }),
  },
});

// src/routes/api/<feature>s/$id.ts
export const Route = createFileRoute("/api/features/$id")({
  server: {
    middleware: [authorizationMiddleware("admin"), databaseMiddleware()],
    handlers: ({ createHandlers }) =>
      createHandlers({
        PUT: {
          middleware: [validationMiddleware(FeatureSchema)],
          handler: async ({ params, context }) => {
            await context.db
              .update(featureTable)
              .set(context.data)
              .where(eq(featureTable.id, params.id));
            return new Response(null, { status: 204 });
          },
        },
        DELETE: async ({ params, context }) => {
          await context.db.delete(featureTable).where(eq(featureTable.id, params.id));
          return new Response(null, { status: 204 });
        },
      }),
  },
});
```
