← Back to list

tanstack-start
by oakoss
Open-source SaaS starter kit with React, TanStack, and Better Auth
⭐ 0🍴 0📅 Jan 26, 2026
SKILL.md
name: tanstack-start description: TanStack Start SSR framework. Use for server functions, API routes, SSR modes, middleware chains, server-only code
TanStack Start
Server Functions
import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
// GET - No input validation needed
const getUsers = createServerFn({ method: 'GET' }).handler(async () => {
return await db.query.users.findMany();
});
// POST - Always validate with Zod
const createUser = createServerFn({ method: 'POST' })
.inputValidator(z.object({ name: z.string().min(1), email: z.email() }))
.handler(async ({ data }) => {
return await db.insert(users).values(data).returning();
});
Authenticated Server Functions
import { auth } from '@oakoss/auth/server';
const deletePost = createServerFn({ method: 'POST' })
.inputValidator(z.object({ id: z.string() }))
.handler(async ({ data, request }) => {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) {
return { error: 'Authentication required', code: 'AUTH_REQUIRED' };
}
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 };
});
API Routes (Server Routes)
// routes/api/posts.ts
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/api/posts')({
server: {
handlers: {
GET: async () => {
const posts = await db.query.posts.findMany();
return Response.json(posts);
},
POST: async ({ request }) => {
const body = await request.json();
const result = schema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: 'Validation failed', code: 'VALIDATION_ERROR' },
{ status: 400 },
);
}
const [post] = await db.insert(posts).values(result.data).returning();
return Response.json(post, { status: 201 });
},
},
},
});
Middleware Chain
import { createServerFn, createMiddleware } from '@tanstack/react-start';
const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) {
throw new Error('Unauthorized');
}
return next({ context: { user: session.user } });
});
const getProtectedData = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context }) => {
return await fetchUserData(context.user.id);
});
Environment Functions
import {
createIsomorphicFn,
createServerOnlyFn,
createClientOnlyFn,
} from '@tanstack/react-start';
// Runs different code on client vs server
const getStorageValue = createIsomorphicFn()
.server(() => process.env.FEATURE_FLAG)
.client(() => localStorage.getItem('featureFlag'));
// Server-only (throws on client)
const readSecretConfig = createServerOnlyFn(() => {
return process.env.SECRET_API_KEY;
});
// Client-only (throws on server)
const getGeolocation = createClientOnlyFn(() => {
return navigator.geolocation.getCurrentPosition();
});
Error Handling Pattern
type ApiResult<T> = { data: T } | { error: string; code: string };
const updateUser = createServerFn({ method: 'POST' })
.inputValidator(schema)
.handler(async ({ data }): 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' };
}
});
Standard Error Codes
| Code | Status | Description |
|---|---|---|
AUTH_REQUIRED | 401 | User must be authenticated |
FORBIDDEN | 403 | User lacks permission |
NOT_FOUND | 404 | Resource doesn't exist |
VALIDATION_ERROR | 400 | Invalid input data |
CONFLICT | 409 | Resource already exists |
INTERNAL_ERROR | 500 | Server error |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
| Missing auth check | Always verify session from request headers |
| Throwing instead of returning error | Return { error, code } format |
| Not validating input | Always use .inputValidator(schema) |
| Not validating input | Always use .inputValidator(schema) |
| Using GET for mutations | Use POST for create/update/delete |
| Not logging server errors | Use console.error() before returning |
| Exposing raw DB errors | Catch and return user-friendly messages |
| Missing error code | Always include both error and code |
| Returning sensitive data in errors | Only return what the client needs |
| Not handling validation errors in POST | Check result.success and return 400 |
Delegation
- Auth patterns: For authentication, see auth skill
- Database queries: For Drizzle patterns, see database skill
- Route integration: For loader patterns, see tanstack-router skill
- Code review: After implementing server functions, delegate to
code-revieweragent
Topic References
- Server Functions - Middleware, environment functions, error handling
- API Routes - RESTful routes, webhooks, file uploads
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

