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 🙏

© 2024 – Pkg Stats / Ryan Hefner

typed-route-handler

v0.2.4

Published

Type-safe route handlers for Next.js

Downloads

116

Readme

type ResponseData = {
  result: string
  over: number
}

export const GET = handler<ResponseData>(async (req) => {
  return NextResponse.json({
    result: "this response is type-checked",
    over: 9000
  })
})

Features

  • Type-safe route handler responses
  • Type-safe route handler parameters
  • ✅ Extended Next.js error handling
  • ✅ Full zod compatibility
  • ✅ Route handler timing
  • ✅ Request logging
  • ✅ Production ready

Installation

npm i typed-route-handler zod next

Usage

Typed handler is easy to use: In the simplest case, just wrap your Route Handler with handler and you're good to go!

+ import { handler } from 'typed-route-handler'

- export const GET = async (req: NextRequest) => {
+ export const GET = handler(async (req) => {
    // ...
- }
+ })

Typed Responses

The real magic comes when you add typing to your responses.

import { NextResponse } from "next"

type ResponseData = {
  name: string
  age: number
}

export const GET = handler<ResponseData>((req) => {
  // ...

  return NextResponse.json({
    name: "Bluey",
    age: 7,
    something: "else" // <-- this will cause a type error
  })
})

Typed Parameters

We can also add type verification to our parameters. Each parameter Context extends from NextRouteContext which is a helper type mapping to: { params?: Record<string, string | string[]> }.

import { NextResponse } from "next"

type ResponseData = {
  name: string
}

type Context = {
  params: {
    userId: string
  }
}

export const GET = handler<ResponseData, Context>((req, context) => {
  // ...
  const userId = context.params.userId // <-- this will be type-safe

  return NextResponse.json({
    name: "Bluey"
  })
})

This can get even more powerful with zod

import { NextResponse } from "next"
import { z } from "zod"

type ResponseData = {
  name: string
}

const contextSchema = z.object({
  params: z.object({
    id: z.string()
  })
})

export const GET = handler<ResponseData, z.infer<typeof contextSchema>>(
  (req, context) => {
    // ...
    const userId = context.params.userId // <-- this will still be type-safe

    // or you can parse the schema:
    const { params } = contextSchema.parse(context)

    return NextResponse.json({
      name: "Bluey"
    })
  }
)

Typed request bodies

Similarly, you can use zod to parse request bodies:

import { NextResponse } from "next"
import { z } from "zod"

type ResponseData = {
  name: string
}

const bodySchema = z.object({
  username: z.string()
})

export const PUT = handler<ResponseData>((req, context) => {
  const body = bodySchema.parse(await req.json())

  // If the body does not satisfy `bodySchema`, the route handler will catch
  // the error and return a 400 error with the error details.

  return NextResponse.json({
    name: body.username
  })
})

Automatic zod issue handling

When a zod error is thrown in the handler, it will be caught automatically and converted to a Validation Error with a 400 status code.

Example:

{
  "error": "Validation Error",
  "issues": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": ["name"],
      "message": "Required"
    }
  ]
}

Extended Next.js errors

This library adds the following convenience methods to Route Handlers.

Similar to how Next.js offers notFound() and redirect(), typed-route-handler offers:

  • unauthorized()
  • forbidden()
  • validationError()

For example:

export const GET = handler(async (req) => {
  const session = await auth()

  if (!session) {
    unauthorized()
  }
})

This will return the following HTTP 401 Unauthorized body:

{
  "error": "Unauthorized"
}

Usage with modified reqs (e.g. next-auth)

When using this library with next-auth or other libraries which modify the req objects, you can pass a 3rd type to the handler call. You may also need to place handler within the other middleware because the other handlers may mask the return types, disabling the type-checking from typed-route-handler For example:

import { auth } from '@/auth'
import { type NextAuthRequest } from 'next-auth'
import { handler, type type NextRouteContext, unauthorized } from 'typed-route-handler'

export const GET = auth(
  handler<ResponseBody, NextRouteContext, NextAuthRequest>((req, ctx) => {
    if (!req.auth?.user) {
      unauthorized()
    }

    // ...
  })
)

Error handling

By default all errors are handled by the handler. However, it is often smart to send an issue to a bug reporting tool like Sentry. To dot his, you can pass a second argument to handler which is an onError callback.

import { handler } from "typed-route-handler"

export const GET = handler(
  () => {
    throw new Error()
  },
  (err) => {
    console.log("onError callback!")
    Sentry.captureException(err)
  }
)

Client-side Usage

typed-route-handler comes with a client library that extends the traditional fetch API with type information.

The typedFetch function will automatically parse the response as JSON, and apply the proper types. On an error response, it will throw.

import { typedFetch } from "typed-route-handler/client"

const data = await typedFetch<{ id: number; username: string }>("/api/user")

data.id // <-- number
data.username // <-- string

If there's an API error, it will be thrown by the client:

import { typedFetch } from "typed-route-handler/client"

try {
  await typedFetch("/api/user")
} catch (e) {
  e.message // <-- Validation Error, etc
}

Roadmap

  • [ ] Add support for streaming responses (generic Response type)
  • [ ] Add support for custom API response formats
  • [ ] Client-side error handling with zod issues

🏰 Production Ready

Already widely used in high-traffic production apps in songbpm, jog.fm, usdc.cool, as well as all StartKit projects.

❤️ Open Source

This project is MIT-licensed and is free to use and modify for your own projects.

It was created by Matt Venables.