@sugardarius/anzen
v2.1.2
Published
Fast, flexible, framework validation agnostic, type‑safe factories for creating route handlers, page and layout Server Component files in Next.js.
Maintainers
Readme
Fast, flexible, framework validation agnostic, type‑safe factories for creating route handlers, page and layout Server Component files in Next.js.
- 🔧 Framework validation agnostic, use a validation library of your choice supporting Standard Schema.
- 🧠 Focused functionalities, use only features you want.
- 🧹 Clean and flexible API.
- 🔒 Type-safe.
- 🌱 Dependency free. Only Next.js and TypeScript are required as peer dependencies.
- 🪶 Lightweight. Less than 140kB unpacked.
Install
npm i @sugardarius/anzenUsage
Route Handlers
import { object, string, number } from 'decoders'
import { createSafeRouteHandler } from '@sugardarius/anzen'
import { auth } from '~/lib/auth'
export const POST = createSafeRouteHandler(
{
authorize: async ({ req }) => {
const session = await auth.getSession(req)
if (!session) {
return new Response(null, { status: 401 })
}
return { user: session.user }
},
body: object({
foo: string,
bar: number,
}),
},
async (
{
auth, // Auth context is inferred from the authorize function
body, // Body is inferred from the body validation
},
req
): Promise<Response> => {
return Response.json({ user: auth.user, body }, { status: 200 })
}
)Page Server Components
import { object, string, number } from 'decoders'
import { unauthorized } from 'next/navigation'
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
import { auth } from '~/lib/auth'
export default createSafePageServerComponent(
{
authorize: async ({ segments }) => {
const session = await auth.getSession()
if (!session) {
unauthorized()
}
return { user: session.user }
},
segments: {
id: string,
},
searchParams: {
page: number,
},
},
async ({
auth, // Auth context is inferred from the authorize function
segments, // Segments are inferred from the segments validation
searchParams, // Search params are inferred from the searchParams validation
}) => {
return <div>Hello {auth.user.name}!</div>
}
)Layout Server Components
import { z } from 'zod'
import { createSafeLayoutServerComponent } from '@sugardarius/anzen/server-components'
import { auth } from '~/lib/auth'
import { notFound, unauthorized } from 'next/navigation'
export default createSafeLayoutServerComponent(
{
segments: {
accountId: z.string(),
},
authorize: async ({ segments }) => {
const session = await auth.getSession()
if (!session) {
unauthorized()
}
const hasAccess = await checkAccountAccess(
session.user.id,
segments.accountId
)
if (!hasAccess) {
notFound()
}
return { user: session.user }
},
},
async ({ auth, segments, children }) => {
return (
<div>
<header>Account: {segments.accountId}</header>
{children}
</div>
)
}
)Framework validation agnostic
By design the factories are framework validation agnostic 🌟. When doing your validations 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 Zod, Validbot or decoders.
// Route handler example
import { z } from 'zod'
import { object, string, number } from 'decoders'
import { createSafeRouteHandler } from '@sugardarius/anzen'
export const POST = createSafeRouteHandler(
{
// `zod` for segments dictionary validation
segments: { id: z.string() },
// `decoders` for body object validation
body: object({
id: number,
name: string,
}),
},
async ({ segments, body }) => {
return Response.json({ segments, body })
}
)// Page server component example
import { z } from 'zod'
import { string, number } from 'decoders'
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
export default createSafePageServerComponent(
{
// `zod` for segments dictionary validation
segments: { id: z.string() },
// `decoders` for search params dictionary validation
searchParams: {
page: number,
},
},
async ({ segments, searchParams }) => {
return (
<div>
Race {segments.id} - Page {searchParams.page}
</div>
)
}
)Synchronous validations
The factories do not support async validations. As required by the Standard Schema common interface we should avoid it. In the context of route handlers and server components it's not necessary.
If you define an async validation then the factory will throw an error.
Fair use note
Please note that if you're not using any of the proposed options in the factories it means you're surely don't need them.
// Route handler
// Calling 👇🏻
export const GET = createSafeRouteHandler({}, async () => {
return new Response(null, { status: 200 })
})
// is equal to declare the route handler this way 👇🏻
export function GET() {
return new Response(null, { status: 200 })
}
// except `createSafeRouteHandler` will provide by default a native error catching
// and will return a `500` response. That's the only advantage.// Page server component
// Calling 👇🏻
export default createSafePageServerComponent({}, async () => {
return <div>Hello</div>
})
// is equal to declare the page server component this way 👇🏻
export default async function Page() {
return <div>Hello</div>
}// Layout server component
// Calling 👇🏻
export default createSafeLayoutServerComponent({}, async ({ children }) => {
return <div>{children}</div>
})
// is equal to declare the layout server component this way 👇🏻
export default async function Layout({
children,
}: {
children: React.ReactNode
}) {
return <div>{children}</div>
}Feel free to open an issue or a PR if you think a relevant option could be added into the factories 🙂
Requirements
The factories require Next.js v14, v15 or v16 and typescript v5 as peer dependencies.
Contributing
All contributions are welcome! 🙂 Feel free to open an issue if you find a bug or create a pull request if you have a feature request.
Credits
- Thanks to @t3-oss/env-core for opening the implementation of
StandardSchemaDictionary🙏🏻 - Thanks to frimousse for opening the release & publish workflow 🙏🏻
License
This project is licensed under the MIT License.
