import { NextResponse } from "next/server"
import type { Handler } from 'typed-route-handler'
type ResponseData = {
result: string
over: number
}
export const GET: Handler<ResponseData> = async (req) => {
return NextResponse.json({
result: "this response is type-checked",
over: 9000
})
}
- ✅ Type-safe route handler responses
- ✅ Type-safe route handler parameters
- ✅ Full zod (v3 and v4) and valibot compatibility
- ✅ Production ready
npm i typed-route-handler
Note
This library may be installed as a devDependency when only used for types. If you'd like to use the param parsers or the route handler wrapper function, you can install it as a regular dependency.
The typed handler is easy to use: In the simplest case, just add the type Handler
to your route handler and it will automatically assign types.
+ import type { Handler } from 'typed-route-handler'
- export const GET = async (req: NextRequest) => {
+ export const GET: Handler = async (req) => {
// ...
}
The real magic comes when you add typing to your responses.
import { NextResponse } from "next/server"
import type { Handler } from 'typed-route-handler'
type ResponseData = {
name: string
age: number
}
export const GET: Handler<ResponseData> = (req) => {
return NextResponse.json({
name: "Bluey",
age: "seven", // <-- this will cause a type error
})
}
We can also add type verification to our parameters:
// app/api/[name]/route.ts
import { NextResponse } from "next/server"
import { type Handler } from "typed-route-handler"
type ResponseData = {
name: string
}
type Params = {
name: string
}
export const GET: Handler<ResponseData, Params> = async (req, { params }) => {
const { name } = await params // <-- this will mark `name` as a string
return NextResponse.json({
name
})
}
Adding compile-time param checking is nice, but it does not perform any runtime type-checking which is more important for parameters. To do that, you can use the included zod (v3 and v4) or valibot parseParams
method:
import { NextResponse } from "next/server"
import { parseParams } from 'typed-route-handler/zod'
import * as z from 'zod/v4'
import { type Handler } from "typed-route-handler"
type ResponseData = {
id: number
url: string
}
const paramsSchema = z.object({
id: z.coerce.number(),
url: z.string().url()
})
export const GET: Handler<ResponseData> = async (req, ctx) => {
const { id, url } = await parseParams(ctx, paramsSchema)
return NextResponse.json({
id, // a number, coerced from a string
url // a url
})
}
You can also use the safeParseParams
method to prevent throwing exceptions if the params are not valid.
For different schema validators you can use the following imports:
Library | Import Path |
---|---|
zod v4 | import { parseParams } from 'typed-route-handler/zod' |
zod v3 | import { parseParams } from 'typed-route-handler/zod/v3' |
valibot | import { parseParams } from 'typed-route-handler/valibot' |
In addition, the library also provides a convenience wrapper function handler
which simply applies the Handler type to the function. Since this is a no-op, it is recommended to use the Handler
type directly.
import { handler } from "typed-route-handler"
import { NextResponse } from 'next/server'
type ResponseBody = {
balance: number
}
export const GET = handler<ResponseBody>(async (req) => {
return NextResponse.json({
balance: 9_000
})
})
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, representing modified Request object type. For example:
import { auth } from '@/auth'
import { type NextAuthRequest } from 'next-auth'
import { handler } from 'typed-route-handler'
type Params = { id: string }
export const GET = auth(
handler<ResponseBody, Params, NextAuthRequest>((req, ctx) => {
if (!req.auth?.user) {
unauthorized()
}
// ...
})
)
Already widely used in high-traffic production apps at Catena Labs, songbpm, jog.fm, usdc.cool, as well as all StartKit next.js projects.
This project is MIT-licensed and is free to use and modify for your own projects.
It was created by Matt Venables.