Skip to content

oworthylab/anzen

 
 

Repository files navigation

npm size license

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/anzen

Usage

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.

About

Fast, flexible, framework validation agnostic, type‑safe factories for creating route handlers, page and layout Server Component files in Next.js.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 76.8%
  • MDX 20.3%
  • CSS 2.4%
  • JavaScript 0.5%