@safeform/next
v4.1.0
Published
Next.js App Router adapter for safeform
Maintainers
Readme
@safeform/next
Next.js App Router adapter for @safeform/core.
npm install @safeform/core @safeform/nextPeer dependencies: Next.js 14+, Zod 3+
Table of Contents
Usage
Mount a safeform action as a Next.js App Router route handler with a single line:
// app/api/employees/route.ts
import { createAction } from '@safeform/core'
import { createRouteHandler } from '@safeform/next'
import { upsertEmployeeSchema } from './schema'
const upsertEmployeeAction = authedAction.create({ schema: upsertEmployeeSchema }, async (data, ctx) => {
return { success: true as const, data: { employeeId: 'emp-1' } }
})
export type UpsertEmployeeAction = typeof upsertEmployeeAction
export const POST = createRouteHandler(upsertEmployeeAction)The route handler takes care of:
- Parsing and validating the JSON request body against your action's schema
- Running the middleware chain (auth, logging, etc.)
- Parsing and validating the
payloadif your action defines one - Returning structured JSON responses the client can consume
- Catching unexpected errors and returning a safe 500 response
Request Format
The client (useForm from @safeform/core) posts JSON in this shape:
{ "data": { ...formValues }, "payload": { ...payloadValues } }payload is omitted if the action has no payload schema.
Response Format
All responses are JSON:
// Success
{ "success": true, "data": { ...handlerReturnData } }
// Validation failure
{ "success": false, "fieldErrors": { "fieldName": ["error message"] } }
// Global error
{ "success": false, "error": "error message" }
// Unexpected server error
{ "success": false, "error": "Internal server error" }Multi-Step Forms
createRouteHandler handles both unnamed (tuple) and named (createSteps) multi-step schemas automatically. No extra configuration needed.
Full Example
// app/api/employees/schema.ts
import { z } from 'zod'
export const upsertEmployeeSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
role: z.enum(['Admin', 'Cashier', 'Janitor']),
})
// app/api/employees/route.ts
import { createAction } from '@safeform/core'
import { createRouteHandler } from '@safeform/next'
import { upsertEmployeeSchema } from './schema'
import { z } from 'zod'
const authedAction = createAction().use(async (ctx) => {
const session = await getSession()
if (!session) throw new Error('Unauthorized')
return { ...ctx, user: session.user }
})
const upsertEmployeeAction = authedAction.create({
schema: upsertEmployeeSchema,
payload: z.object({ employeeId: z.string().optional() }),
}, async (data, payload, ctx) => {
const employee = await db.employee.upsert({ ... })
return { success: true as const, data: { employeeId: employee.id } }
})
export type UpsertEmployeeAction = typeof upsertEmployeeAction
export const POST = createRouteHandler(upsertEmployeeAction)License
MIT
