Server RPC
Server-side Remote Procedure Call utilities for TanStack Start.createServerRpc
Create server-side RPC endpoints that can call other server functions.import { createServerRpc } from '@tanstack/react-start/server'
const internalApi = createServerRpc()
.handler(async ({ context }) => {
// This runs only on the server
const data = await db.query()
return data
})
Server-Only Functions
Functions that can only be called server-side.import { createServerFn } from '@tanstack/start'
// Server-only utility
export const validateApiKey = async (apiKey: string) => {
'use server' // Mark as server-only
const key = await db.apiKeys.findUnique({
where: { key: apiKey }
})
return key?.isValid || false
}
// Use in server function
const protectedAction = createServerFn({ method: 'POST' })
.handler(async ({ data }) => {
const isValid = await validateApiKey(data.apiKey)
if (!isValid) {
throw new Error('Invalid API key')
}
return { success: true }
})
Server Context
Access server-side context in server functions.import { getServerContext } from '@tanstack/start/server'
const myServerFn = createServerFn({ method: 'GET' })
.handler(async () => {
const context = getServerContext()
// Access request information
console.log('Request URL:', context.request.url)
console.log('Request headers:', context.request.headers)
// Access server-side only data
const session = context.session
return { data: 'server data' }
})
Database Transactions
Execute database operations within transactions.import { createServerFn } from '@tanstack/start'
const createPostWithTags = createServerFn({ method: 'POST' })
.inputValidator(z.object({
title: z.string(),
content: z.string(),
tags: z.array(z.string())
}))
.handler(async ({ data }) => {
return await db.$transaction(async (tx) => {
// Create post
const post = await tx.posts.create({
data: {
title: data.title,
content: data.content
}
})
// Create tags
await tx.tags.createMany({
data: data.tags.map(name => ({
name,
postId: post.id
}))
})
// Return post with tags
return await tx.posts.findUnique({
where: { id: post.id },
include: { tags: true }
})
})
})
Caching Server Results
Cache server function results for improved performance.import { createServerFn } from '@tanstack/start'
const cache = new Map()
const getExpensiveData = createServerFn({ method: 'GET' })
.handler(async ({ data }) => {
const cacheKey = `data-${data.id}`
// Check cache
if (cache.has(cacheKey)) {
return cache.get(cacheKey)
}
// Compute expensive data
const result = await performExpensiveComputation(data.id)
// Store in cache
cache.set(cacheKey, result)
// Set expiration
setTimeout(() => cache.delete(cacheKey), 5 * 60 * 1000) // 5 minutes
return result
})
Server Function Composition
Compose multiple server functions together.import { createServerFn } from '@tanstack/start'
// Base server functions
const getUser = createServerFn({ method: 'GET' })
.handler(async ({ data }) => {
return await db.users.findUnique({
where: { id: data.userId }
})
})
const getUserPosts = createServerFn({ method: 'GET' })
.handler(async ({ data }) => {
return await db.posts.findMany({
where: { authorId: data.userId }
})
})
// Composed function
const getUserWithPosts = createServerFn({ method: 'GET' })
.handler(async ({ data }) => {
const [user, posts] = await Promise.all([
getUser({ data: { userId: data.userId } }),
getUserPosts({ data: { userId: data.userId } })
])
return { user, posts }
})
Background Jobs
Trigger background jobs from server functions.import { createServerFn } from '@tanstack/start'
import { queue } from './job-queue'
const sendEmail = createServerFn({ method: 'POST' })
.handler(async ({ data }) => {
// Add job to queue instead of blocking
await queue.add('send-email', {
to: data.email,
subject: data.subject,
body: data.body
})
return { queued: true }
})
// Process jobs in background
queue.process('send-email', async (job) => {
await emailService.send({
to: job.data.to,
subject: job.data.subject,
html: job.data.body
})
})
Server-Side Redirects
import { createServerFn, redirect } from '@tanstack/start'
const requireAuth = createServerFn({ method: 'GET' })
.handler(async ({ context }) => {
const session = await getSession(context.request)
if (!session) {
throw redirect({
to: '/login',
status: 302
})
}
return session
})
Server-Side Logging
import { createServerFn } from '@tanstack/start'
import { logger } from './logger'
const createPost = createServerFn({ method: 'POST' })
.handler(async ({ data, context }) => {
logger.info('Creating post', {
userId: context.userId,
title: data.title
})
try {
const post = await db.posts.create({ data })
logger.info('Post created', {
postId: post.id
})
return post
} catch (error) {
logger.error('Failed to create post', {
error: error.message,
userId: context.userId
})
throw error
}
})
Rate Limiting
import { createServerFn } from '@tanstack/start'
import { rateLimit } from './rate-limiter'
const sendMessage = createServerFn({ method: 'POST' })
.handler(async ({ data, context }) => {
// Check rate limit
const limited = await rateLimit.check(context.userId, {
max: 10, // 10 requests
window: 60 * 1000 // per minute
})
if (limited) {
throw new Error('Rate limit exceeded')
}
// Process message
return await sendMessageToQueue(data)
})
Examples
Multi-Step Workflow
import { createServerFn } from '@tanstack/start'
const processOrder = createServerFn({ method: 'POST' })
.handler(async ({ data }) => {
// Step 1: Validate inventory
const available = await checkInventory(data.items)
if (!available) {
throw new Error('Items not available')
}
// Step 2: Process payment
const payment = await processPayment({
amount: data.total,
method: data.paymentMethod
})
if (!payment.success) {
throw new Error('Payment failed')
}
// Step 3: Create order
const order = await db.orders.create({
data: {
items: data.items,
total: data.total,
paymentId: payment.id
}
})
// Step 4: Update inventory
await updateInventory(data.items)
// Step 5: Send confirmation
await sendOrderConfirmation(order)
return order
})
Server-Side Analytics
import { createServerFn } from '@tanstack/start'
import { analytics } from './analytics'
const trackEvent = createServerFn({ method: 'POST' })
.handler(async ({ data, context }) => {
await analytics.track({
userId: context.userId,
event: data.event,
properties: data.properties,
timestamp: new Date(),
ip: context.request.headers.get('x-forwarded-for'),
userAgent: context.request.headers.get('user-agent')
})
return { tracked: true }
})
Server-Side Feature Flags
import { createServerFn } from '@tanstack/start'
import { featureFlags } from './feature-flags'
const getFeatures = createServerFn({ method: 'GET' })
.handler(async ({ context }) => {
const flags = await featureFlags.getForUser(context.userId)
return {
newDashboard: flags.includes('new-dashboard'),
betaFeatures: flags.includes('beta-features'),
aiAssistant: flags.includes('ai-assistant')
}
})