Back to list
oakoss

server-functions

by oakoss

Open-source SaaS starter kit with React, TanStack, and Better Auth

0🍴 0📅 Jan 26, 2026

SKILL.md


name: server-functions description: Use createServerFn for RPC-style functions. Use when writing server functions with validators, middleware chains, or form action handlers.

Server Functions

Quick Start

import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
import { db } from '@oakoss/database';

const getUsers = createServerFn({ method: 'GET' }).handler(async () => {
  return await db.query.users.findMany();
});

const createUser = createServerFn({ method: 'POST' })
  .inputValidator(z.object({ name: z.string().min(1), email: z.email() }))
  .handler(async ({ data }) => {
    const [user] = await db.insert(users).values(data).returning();
    return user;
  });

Pattern Overview

PatternUse CaseMethod
Data fetchingRoute loaders, queriesGET
Form submissionCreate/update operationsPOST
Authenticated actionProtected mutationsPOST + auth check

GET Functions

import { db, eq } from '@oakoss/database';
import { posts } from '@oakoss/database/schema';

const getAllPosts = createServerFn({ method: 'GET' }).handler(async () => {
  return await db.query.posts.findMany();
});

const getPost = createServerFn({ method: 'GET' })
  .inputValidator(z.object({ id: z.string() }))
  .handler(async ({ data }) => {
    return await db.query.posts.findFirst({
      where: eq(posts.id, data.id),
    });
  });

export const Route = createFileRoute('/posts/$id')({
  loader: async ({ params }) => await getPost({ data: { id: params.id } }),
});

POST Functions

const createPost = createServerFn({ method: 'POST' })
  .inputValidator(
    z.object({
      title: z.string().min(1),
      content: z.string(),
    }),
  )
  .handler(async ({ data }) => {
    const [post] = await db.insert(posts).values(data).returning();
    return post;
  });

const handleSubmit = async (formData: FormData) => {
  const result = await createPost({
    data: { title: formData.get('title'), content: formData.get('content') },
  });
};

Authenticated Functions

import { auth } from '@oakoss/auth/server';
import { db, eq } from '@oakoss/database';
import { posts } from '@oakoss/database/schema';

const deletePost = createServerFn({ method: 'POST' })
  .inputValidator(z.object({ id: z.string() }))
  .handler(async ({ data, request }) => {
    // Get session from request headers
    const session = await auth.api.getSession({ headers: request.headers });

    if (!session) {
      return { error: 'Authentication required', code: 'AUTH_REQUIRED' };
    }

    // Check ownership
    const post = await db.query.posts.findFirst({
      where: eq(posts.id, data.id),
    });

    if (post?.authorId !== session.user.id) {
      return { error: 'Not authorized', code: 'FORBIDDEN' };
    }

    await db.delete(posts).where(eq(posts.id, data.id));
    return { success: true };
  });

Error Handling

Standard error response format:

type ApiResult<T> = { data: T } | { error: string; code: string };

const updateUser = createServerFn({ method: 'POST' })
  .inputValidator(schema)
  .handler(async ({ data, request }): Promise<ApiResult<User>> => {
    try {
      const [user] = await db.update(users).set(data).returning();
      return { data: user };
    } catch (error) {
      console.error('updateUser failed:', error);
      return { error: 'Update failed', code: 'INTERNAL_ERROR' };
    }
  });

Request Context

Access request details:

const serverFn = createServerFn({ method: 'POST' }).handler(
  async ({ request }) => {
    // Headers
    const authHeader = request.headers.get('Authorization');

    // URL info
    const url = new URL(request.url);

    // Cookies (via Better Auth)
    const session = await auth.api.getSession({ headers: request.headers });
  },
);

Common Mistakes

MistakeCorrect Pattern
Missing auth checkAlways verify session from request headers
Throwing instead of returning errorReturn { error, code } format
Not validating inputAlways use .inputValidator(schema)
Using deprecated Zod v3 syntaxUse z.email() not z.string().email()
Missing error codeReturn { error: 'message', code: 'ERROR_CODE' }
Not logging server errorsUse console.error() before returning
Returning raw DB errorsCatch and return user-friendly messages
Using GET for mutationsUse POST for create/update/delete

Delegation

  • Complex queries: For database patterns, see database skill
  • Auth patterns: For authentication, see auth skill
  • API routes: For REST endpoints, see tanstack-start skill
  • Code review: After creating server functions, delegate to code-reviewer agent

References

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon