Server Actions
Server actions and mutations provide type-safe server-side functionality with client-side integration using React Query.
Project Structure
server/
├── actions/ # Server actions
│ ├── auth-actions.ts
│ ├── user-actions.ts
│ └── workspace-actions.ts
└── db/
└── mutations/ # Client-side mutations
├── use-user-api.ts
├── use-workspace-api.ts
└── use-member-api.ts
Server Actions
server-only
Server actions are located in server/actions/
and handle server-side operations with built-in error handling and rate limiting.
Example: User Action
Server-side implementation of user creation:
"use server"
export const createUserAction = async ({
values,
isFromInvitation,
}: {
values: z.infer<typeof userSchema>
isFromInvitation?: boolean
}) => {
try {
// 1. Validate input
const validateValues = userSchema.safeParse(values)
if (!validateValues.success) {
throw new ValidationError(validateValues.error.message)
}
// 2. Check authentication
const { user } = await getCurrentUser()
if (!user) {
throw new AuthenticationError()
}
// 3. Check rate limit
const identifier = `ratelimit:create-user:${user.id}`
const { success } = await ratelimit.limit(identifier)
if (!success) {
throw new RateLimitError()
}
// 4. Perform database operations
return await dbClient.transaction(async (tx) => {
// ... implementation
})
} catch (error) {
if (error instanceof ApiError) {
throw error
}
throw new DatabaseError("Failed to create user")
}
}
Client Mutations
use-hooks
Client mutations are React hooks located in server/db/mutations/
that wrap server actions with React Query for state management.
Example: User Mutation Hook
Client-side mutation hook for user creation:
export const useCreateUser = ({ isFromInvitation }: { isFromInvitation: boolean }) => {
const router = useRouter()
const { mutate, isPending } = useMutation({
mutationFn: createUserAction,
onSuccess: (data) => {
toast.success(data.message)
router.refresh()
router.push(data.hasWorkspace
? createRoute("onboarding-collaborate").href
: createRoute("onboarding-workspace").href
)
},
onError: (error: any) => {
toast.error(error.message || GLOBAL_ERROR_MESSAGE)
},
})
return { isPending, server_createUser: mutate }
}
Usage in Components
Example: Using Mutations
Using mutation hooks in React components:
function CreateUserForm() {
const { server_createUser, isPending } = useCreateUser({
isFromInvitation: false
})
const onSubmit = (values: FormData) => {
server_createUser(values)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Button type="submit" disabled={isPending}>
{isPending ? "Creating..." : "Create User"}
</Button>
</form>
)
}
Key Features
Rate Limiting
Built-in rate limiting using Upstash Redis
Error Handling
Standardized error handling with custom error types
Type Safety
Full TypeScript support with Zod validation
State Management
Automatic cache invalidation with React Query
Best Practices
Server Actions
- Use 'use server' directive
- Implement proper validation
- Handle all error cases
- Use transactions for related operations
Client Mutations
- Prefix mutation functions with 'server_'
- Handle loading states
- Provide meaningful error messages
- Update UI optimistically when possible
All server actions should be properly rate-limited and validated to prevent abuse. Always handle errors appropriately and provide meaningful feedback to users.