npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

next-route-kit

v0.1.3

Published

Composable Route Handler infrastructure for Next.js App Router

Readme

next-route-kit

The Next.js App Router entry package.

npm install next-route-kit

Start with the English user guide, 简体中文指南, or the repository README.

5-minute integration

Try one JSON endpoint first. No next.config.ts registration is required.

// app/api/resources/route.ts
import { createRoute, jsonBody } from 'next-route-kit'

const route = createRoute()

export const POST = route({
    body: jsonBody<{ name: string }>(),
    handler: (_request, { body }) => ({ resource: { name: body.name } }),
})

request remains the native Web Request, and special endpoints can stay plain Next.js handlers. Migrate one route at a time; see the migration guide for a before/after example.

Production adoption and compatibility feedback

If your App Router project repeats authentication, validation, error mapping, or response-envelope policy, migrate one representative route and extend the shared Factory as the pattern proves useful. For a migration or compatibility report, include your Next.js version, runtime, migrated route shape, and the relevant API or documentation area in the compatibility and migration issue form.

Native route API

import { createRoute, jsonBody, query } from 'next-route-kit'

type ResourceParams = { id: string }
type UpdateInput = { title?: string }

const route = createRoute({
    guards: [requireUser],
})

export const GET = route<ResourceParams>({
    handler: async (request, { params, locals }) => {
        return resourceService.find(params.id, locals.userId)
    },
})

export const PATCH = route<ResourceParams, UpdateInput>({
    body: jsonBody<UpdateInput>(),
    handler: async (_request, { params, body, locals }) => {
        return resourceService.update(params.id, locals.userId, body)
    },
})

The handler is always (request, context). request is the native Web Request; context.params contains Next dynamic params and context.locals contains request-local values written by middleware or guards.

Declare body or query only when automatic resolution is useful:

export const POST = route({
    body: jsonBody<{ name: string }>(),
    query: query<{ preview?: string }>(),
    handler: (_request, { body, query: values }) => ({
        name: body.name,
        preview: values.preview === 'true',
    }),
})

Raw headers, URL, streaming bodies, files, and special responses remain on the native Request/Response boundary.

Factory scopes

const apiRoute = createRoute({ middleware, interceptors, exceptionFilters })
const authenticatedRoute = apiRoute.extend({ guards: [requireUser] })

extend() returns a new immutable scope. It does not mutate the parent and does not require a next.config.ts registration.

Stable API responses

For applications that use a business-code contract, register the optional plugin once:

import { ApiException, apiResponsePlugin, createRoute } from 'next-route-kit'

const ResponseCode = {
    SUCCESS: { code: 'OK', msg: 'Success' },
    QUOTA_EXCEEDED: { code: 'QUOTA_EXCEEDED', msg: 'Quota exceeded', status: 409 },
    INTERNAL_ERROR: { code: 'INTERNAL_ERROR', msg: 'Internal server error' },
} as const

const apiRoute = createRoute({
    plugins: [apiResponsePlugin({ success: ResponseCode.SUCCESS, systemError: ResponseCode.INTERNAL_ERROR })],
})

export const POST = apiRoute({
    handler: async () => {
        if (/* application rule */ false) {
            throw new ApiException(ResponseCode.QUOTA_EXCEEDED)
        }

        return { resourceId: 'resource-demo' }
    },
})

Plain object results and ApiException values are converted to one { code, msg, data } envelope. data is always an object. The code constants remain application-owned, so a client can handle common auth/quota codes globally and feature-specific codes locally. Native Response values pass through unchanged. Unexpected errors use the configured system response and are reported with console.error unless onUnknownError supplies an application reporter.

Validation is not built into this response contract. The main package does not depend on Zod or register a Zod filter. If an application installs the optional @next-route-kit/zod adapter, use apiResponsePlugin({ mapError }) to map ZodValidationError into the envelope. Use zodExceptionFilter() instead only for a route that intentionally uses the adapter's standalone JSON shape.

The pipeline is:

Next params → Middleware → Guard → Interceptor enter
→ declared arguments → Pipe → Handler → Interceptor exit → Response

Errors go through ExceptionFilter.catch(). The package supplies a default filter for HttpError and malformed JSON, plus a default JSON serializer. A native Response returned by a handler passes through unchanged.

See the root README for RESTful examples and the user guides for API details.

Custom plugins

Create a class that implements RoutePlugin and return reusable lifecycle components from install():

import { createRoute, type RoutePlugin } from 'next-route-kit'

class RequestTimingPlugin implements RoutePlugin {
    readonly name = 'request-timing'
    readonly runtime = 'both' as const

    install() {
        return {
            interceptors: [
                {
                    name: 'request-timing',
                    async intercept(_context, next) {
                        const startedAt = Date.now()

                        try {
                            return await next()
                        } finally {
                            console.info('durationMs:', Date.now() - startedAt)
                        }
                    },
                },
            ],
        }
    }
}

const route = createRoute({
    plugins: [new RequestTimingPlugin()],
})

Plugins can contribute middleware, guards, pipes, interceptors, exceptionFilters, or one responseSerializer. Register them on the base Factory, on extend() for a subgroup, or with route-local use: [plugin]. The detailed plugin guide documents the lifecycle order and scope rules.