What is Anzen?
Fast, flexible, framework validation agnostic, type‑safe factories for creating server actions, route handlers, page and layout Server Component files in Next.js.
Why?
Anzen means safe in Japanese.
Without a shared pattern, server actions, route handlers, and page or layout Server Components often accumulate one-off checks, inconsistent error handling, and types that drift from runtime behavior. The examples below contrast plain Next.js with the same flows using Anzen — validation, authorization, and structured results in one place.
'use server'
importfrom '~/lib/auth'
importfrom '~/lib/db'
export async function createThread(formData: FormData) {
// Read fields from FormData — shape is implicit, not validated as one unit
const spaceId =get'spaceId'
const commentRaw =get'comment'
// Ad-hoc guards; each failure path throws a different Error
iftypeof!== 'string' || !spaceId) {
throw new Error'Invalid spaceId'
iftypeof!== 'string'
throw new Error'Invalid comment'
// Auth and domain logic live in the same function as parsing
const session = await auth
if!session.user) {
throw new Error'unauthorized'
if!session.access.includes
throw new Error'forbidden'
// Nested JSON parsed and asserted by hand
let:createdAt: string; content: string
try
= JSON.parseascreatedAt: string; content: string
catch
throw new Error'Invalid comment JSON'
returncreateThread
}'use server'
importfrom 'decoders'
importfrom '@sugardarius/anzen'
importfrom '~/lib/auth'
importfrom '~/lib/db'
// Factory wires validation, auth, and handler with a stable result shape
export const createThread = createSafeServerAction
'create-thread-action',
// One Standard Schema object — input is inferred + validated together
object
object
// Authorization separated from the business handler
authorize: asyncinput=>
const session = await auth
if!session.user) {
throw new Error'user is not authenticated'
if!session.access.includes
throw new Error'user has not access'
return
// Typed `input` + `auth`; callers get `{ success, output | error }`
asyncauth, input, id=>
const inserted = awaitcreateThread
...input, authorId: auth.user.id },
return
importfrom '~/lib/auth'
export async function POST(req: Request) {
// Session check + early Response inlined in the handler
const session = awaitgetSession
if!session) {
return new Responsenull, { status: 401
// Body: manual parse + runtime type checks
let: unknown
try
= awaitjson
catch
return new Response'Invalid JSON', { status: 400
iftypeof!== 'object' ||=== null
return new Response'Invalid body', { status: 400
returnjson200
}importfrom '@sugardarius/anzen'
importfrom '~/lib/auth'
// Route is a factory — shared behavior (e.g. auth) lives in one place
export const POST = createSafeRouteHandler
// Same 401 logic, but declared as a reusable authorize step
authorize: asyncreq=>
const session = awaitgetSession
if!session) {
return new Responsenull, { status: 401
return
// `auth` is injected; handler focuses on the response
asyncauth, bodyreq): Promise<Response> =>
returnjson200
importfrom 'next/navigation'
importfrom '~/lib/auth'
importfrom '~/lib/db'
importfrom '~/components/account-summary'
type PageProps =params: Promise<{ accountId: string
export default async function Page({ params: PageProps) {
constaccountId= await
// Segment shape is unchecked until you validate it by hand
iftypeof!== 'string' || !accountId) {
unauthorized
// Auth + data loading interleaved in the page body
const session = awaitgetSession
if!session) {
unauthorized
const account = await getAccount
returnAccountSummary user={session.user} account={account} />
}importfrom 'next/navigation'
importfrom 'decoders'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom '~/lib/db'
importfrom '~/components/account-summary'
// Segments + authorize + page body composed by the factory
export default createSafePageServerComponent
authorize: asyncsegments=>
const session = awaitgetSession
if!session) {
unauthorized
return
// Route params validated as one Standard Schema dictionary
asyncauth, segments=>
const account = await getAccount
returnAccountSummary user={auth.user} account={account} />
import typefrom 'react'
importfrom 'next/navigation'
importfrom '~/lib/auth'
importfrom '~/components/account-header'
type LayoutProps =
params: Promise<{ accountId: string
children: ReactNode
}
export default async function Layout({ params, children: LayoutProps) {
constaccountId= await
// Same manual segment checks repeated as in the page file
iftypeof!== 'string' || !accountId) {
unauthorized
const session = awaitgetSession
if!session) {
unauthorized
return
div>
AccountHeader user={session.user} accountId={accountId} />
div>
}importfrom 'next/navigation'
importfrom 'decoders'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom '~/lib/db'
importfrom '~/components/account-header'
export default createSafeLayoutServerComponent
authorize: asyncsegments=>
const session = awaitgetSession
if!session) {
unauthorized
return
asyncauth, segments, children=>
const account = await getAccount
return
div>
AccountHeader user={auth.user} accountId={segments.accountId} />
div>
Philosophy
While server actions, route handlers, page, and layout Server Component files in Next.js are powerful features, they don't provide (yet) any means to validate the input data or authorize the access to the resource.
Anzen is a library providing factories to help you create safe server actions, route handlers, page and layout Server Component files in Next.js. It's a tool that helps you create code that is safe, flexible and easy to use.
It makes easier to build dynamic applications with proper data validation, authorization, and error handling in a structured way.
Features
Focused functionalities
Use only the features you need.
Clean and flexible API
Make it your own.
Type-safe
Full TypeScript inference.
Lightweight
Your bundle is safe.
Install
npm i @sugardarius/anzenUsage
'use server'
importfrom 'decoders'
importfrom '@sugardarius/anzen'
importfrom '~/lib/auth'
importfrom '~/lib/db'
export const createThread = createSafeServerAction
'create-thread-action',
object
object
authorize: asyncinput=>
const session = await auth
if!session.user) {
throw new Error'user is not authenticated'
if!session.access.includes
throw new Error'user has not access'
return
asyncauth, input, id=>
const inserted = awaitcreateThread
...input, authorId: auth.user.id },
return
importfrom 'decoders'
importfrom '@sugardarius/anzen'
importfrom '~/lib/auth'
export const POST = createSafeRouteHandler
authorize: asyncreq=>
const session = awaitgetSession
if!session) {
return new Responsenull, { status: 401
return
asyncauth, bodyreq): Promise<Response> =>
returnjson200
importfrom 'next/navigation'
importfrom 'decoders'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom '~/lib/db'
importfrom '~/components/account-summary'
export default createSafePageServerComponent
authorize: asyncsegments=>
const session = awaitgetSession
if!session) {
unauthorized
return
asyncauth, segments=>
const account = await getAccount
returnAccountSummary user={auth.user} account={account} />
importfrom 'next/navigation'
importfrom 'decoders'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom '~/lib/db'
importfrom '~/components/account-header'
export default createSafeLayoutServerComponent
authorize: asyncsegments=>
const session = awaitgetSession
if!session) {
unauthorized
return
asyncauth, segments, children=>
const account = await getAccount
return
div>
AccountHeader user={auth.user} accountId={segments.accountId} />
div>
When to use Anzen?
Use Anzen whenever you need to validate untrusted data, and/or authorize the access to the resource. It's a tool that helps you create code that is safe, flexible and easy to use.
You should use it for:
- ✅ Full-stack Next.js applications where you want a unified pattern across all server-side entry points
- ✅ Applications requiring authorization at multiple levels (actions, routes, pages, layouts)
- ✅ Projects needing consistent input validation across the entire stack
- ✅ Type-safe applications where you want compile-time guarantees everywhere
- ✅ Validation library flexibility — use Zod, Valibot, or any decoder you prefer
- ✅ Clean error handling with structured responses throughout your app
Framework validation agnostic
All factories are framework validation agnostic. You can use whatever you want as framework validation as long as it implements the Standard Schema common interface. You can use your favorite validation library like decoders. Zod, or Validbot.
'use server'
importfrom 'zod'
importfrom 'decoders'
importfrom '@sugardarius/anzen'
// Zod
export const updateProfile = createSafeServerAction
object
stringmin1
stringmax500optional
asyncinput=>
// decoders
export const renameSpace = createSafeServerAction
object
asyncinput=>
Synchronous validations
The Standard Schema contract discourages async validation. If a schema resolves validation asynchronously, behavior is undefined and may throw at runtime. So all Anzen's factories do not support async validations by design. An error will be thrown if you try to use async validation.
Learn more
API reference
Last updated on