actions
Call server code straight from a component, like a form submit, without writing an API route.
What it is
An action is a server function you define with defineQuery or defineMutation and call from any component by name. kitr sends the request, validates the input with your schema and returns the result or a typed error.
Use actions when only your own app calls the code. For mobile apps, partners or webhooks, use API routes.
Install
kitr add actionsFirst time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.
Adds @kitr/actions and app/actions/, and runs kitr types once. Adds server and query first if missing. Every change.
Quick start
Define a mutation with its input schema and handler:
// app/actions/projects/rename.ts
import { defineMutation } from '@kitr/actions'
import { z } from 'zod'
export const renameProject = defineMutation('projects.rename', {
input: z.object({ id: z.string(), name: z.string().min(2) }),
effects: (project) => ({
set: [{ key: ['projects', 'detail', project.id], data: project }],
invalidate: [['projects', 'list']],
}),
handler: async ({ input }) => {
// Update the project in your database and return it.
return { id: input.id, name: input.name }
},
})
Call it from a component by name:
// app/components/rename-button.tsx
import { useMutation } from '@kitr/query'
export function RenameButton({ id }: { id: string }) {
const rename = useMutation('projects.rename')
return (
<button disabled={rename.isPending} onClick={() => rename.mutate({ id, name: 'Acme site' })}>
Rename
</button>
)
}
One click sends one request. The server validates, runs the handler and returns the project; the cache effects update every screen that shows it. A missing name is a type error. Next in the guide: Your first mutation.
Built on
kitr adds four things on top:
| What kitr adds | Why |
|---|---|
| Input validation with any Standard Schema library (zod, valibot, arktype). | The server never trusts the browser, and the schema is also the TypeScript type. |
kitr's one error shape, KitrError. |
A validation failure lands on the right form field in forms, and every other failure looks the same as it does from api or http. |
| Cache effects on mutations. | A mutation declares which query keys it changes, and every screen showing them updates. |
| A generated registry. | useQuery('projects.list') is typed from the action's name. |
kitr adds the input schema, the typed error, the generated registry and the cache effects.
Problems it solves
- Writing an endpoint for every button. Your first mutation.
- Reading data without a REST layer. query.
- Data that arrives after the page. Load before the page renders.
- Untrusted input reaching server code. Throw an error the caller understands.
- Server code anyone can call. Protect an action with middleware.
- Actions scattered across the codebase. Organize actions by feature.