# err-server-errors: Handle Server Function Errors ## Priority: MEDIUM ## Explanation Server function errors cross the network boundary. Handle them gracefully with appropriate error types, status codes, and user-friendly messages. Avoid exposing internal details in production. ## Bad Example ```tsx // Throwing raw errors - exposes internals export const createUser = createServerFn({ method: 'POST' }) .validator(createUserSchema) .handler(async ({ data }) => { const user = await db.users.create({ data }) // May throw DB error return user // Prisma error with stack trace sent to client }) // Generic error handling - no useful info for client export const getPost = createServerFn() .handler(async ({ data }) => { try { return await fetchPost(data.id) } catch (e) { throw new Error('Something went wrong') // Too vague } }) ``` ## Good Example: Structured Error Handling ```tsx // lib/errors.ts export class AppError extends Error { constructor( message: string, public code: string, public status: number = 400 ) { super(message) this.name = 'AppError' } } export class NotFoundError extends AppError { constructor(resource: string) { super(`${resource} not found`, 'NOT_FOUND', 404) } } export class UnauthorizedError extends AppError { constructor(message = 'Unauthorized') { super(message, 'UNAUTHORIZED', 401) } } export class ValidationError extends AppError { constructor(message: string, public fields?: Record) { super(message, 'VALIDATION_ERROR', 400) } } ``` ## Good Example: Server Function with Error Handling ```tsx import { createServerFn, notFound } from '@tanstack/react-start' import { setResponseStatus } from '@tanstack/react-start/server' export const getPost = createServerFn() .validator(z.object({ id: z.string() })) .handler(async ({ data }) => { const post = await db.posts.findUnique({ where: { id: data.id }, }) if (!post) { // Use built-in notFound for 404s throw notFound() } return post }) export const createPost = createServerFn({ method: 'POST' }) .validator(createPostSchema) .handler(async ({ data }) => { try { const post = await db.posts.create({ data }) return post } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { if (error.code === 'P2002') { // Unique constraint violation setResponseStatus(409) throw new AppError('A post with this title already exists', 'DUPLICATE', 409) } } // Log full error server-side console.error('Failed to create post:', error) // Return sanitized error to client setResponseStatus(500) throw new AppError('Failed to create post', 'INTERNAL_ERROR', 500) } }) ``` ## Good Example: Client-Side Error Handling ```tsx function CreatePostForm() { const [error, setError] = useState(null) const createMutation = useMutation({ mutationFn: createPost, onError: (error) => { if (error instanceof AppError) { setError(error.message) } else if (error instanceof ValidationError) { // Handle field-specific errors Object.entries(error.fields ?? {}).forEach(([field, message]) => { form.setError(field, { message }) }) } else { setError('An unexpected error occurred') } }, onSuccess: (post) => { navigate({ to: '/posts/$postId', params: { postId: post.id } }) }, }) return (
{error && {error}} {/* form fields */}
) } ``` ## Good Example: Using Redirects for Auth Errors ```tsx export const updateProfile = createServerFn({ method: 'POST' }) .validator(updateProfileSchema) .handler(async ({ data }) => { const session = await getSessionData() if (!session) { // Redirect to login for auth errors throw redirect({ to: '/login', search: { redirect: '/settings' }, }) } return await db.users.update({ where: { id: session.userId }, data, }) }) ``` ## Error Response Best Practices | Scenario | HTTP Status | Response | |----------|-------------|----------| | Validation failed | 400 | Field-specific errors | | Not authenticated | 401 | Redirect to login | | Not authorized | 403 | Generic forbidden message | | Resource not found | 404 | Use `notFound()` | | Conflict (duplicate) | 409 | Specific conflict message | | Server error | 500 | Generic message, log details | ## Context - Use `notFound()` for 404 errors - integrates with router - Use `redirect()` for auth-related errors - Set status codes with `setResponseStatus()` - Log full errors server-side, sanitize for client - Create custom error classes for consistent handling - Validation errors from `.validator()` are automatic