Make type-safe routes that automatically generate OpenAPI in NextJS easy!
- Define endpoints with middleware and have your request objects and responses automatically be typed
- The same zod schemas used for your types will be in the generated
openapi.json
file! - Throw http exceptions and they'll magically be handled
- Have well-typed middleware
yarn add nextlove
nextlove
allows you to create well-typed middleware and routes using utility types and functions. The
two main functions to know are createWithRouteSpec
, which allows you to create a withRouteSpec
function
that can be used with all your endpoints, and the Middleware
utility function which makes middleware type-safe.
Let's take a look at an example project with three files:
- lib/with-route-spec.ts - This file is used to create the
withRouteSpec
middleware. This middleware should be used for all your routes. - lib/middlewares/with-auth-token.ts - This is an authentication middleware we'll be using to make sure requests are authenticating
- lib/middlewares/with-db.ts - A common global middleware that attaches a database client to the request object
- pages/api/health.ts - Just a health endpoint to see if the server is running! It won't have any auth
- pages/api/todos/add.ts - An endpoint to add a TODO, this will help show how we can use auth!
// pages/api/health.ts
import { withRouteSpec } from "lib/with-route-spec"
import { z } from "zod"
const routeSpec = {
methods: ["GET"],
auth: "none",
jsonResponse: z.object({
healthy: z.boolean(),
}),
} as const
export default withRouteSpec(routeSpec)(async (req, res) => {
/* ... */
return res.status(200).json({ healthy: true })
})
// lib/with-route-spec.ts
export const withRouteSpec = createWithRouteSpec({
authMiddlewareMap: { auth_token: withAuthToken },
globalMiddlewares: [globalMiddleware],
// For OpenAPI Generation
apiName: "My API",
productionServerUrl: "https://example.com",
globalSchemas: {
user: z.object({
user_id: z.string().uuid(),
}),
},
} as const)
// lib/middlewares/with-auth-token.ts
import { UnauthorizedException, Middleware } from "nextlove"
export const withAuthToken: Middleware<{
auth: {
authorized_by: "auth_token"
}
}> = (next) => async (req, res) => {
req.auth = {
authorized_by: "auth_token",
}
return next(req, res)
}
export default withAuthToken
// pages/api/todos/add.ts
import { withRouteSpec, UnauthorizedException } from "lib/with-route-spec"
import { z } from "zod"
const routeSpec = {
methods: ["POST"],
auth: "auth_token",
jsonBody: z.object({
content: z.string(),
}),
jsonResponse: z.object({
ok: z.boolean(),
}),
} as const
export default withRouteSpec(routeSpec)(async (req, res) => {
// req.auth is correctly typed here!
if (req.auth.authorized_by !== "auth_token") {
throw new UnauthorizedException({
type: "unauthorized",
message: "Authenticate yourself to get the requested response",
})
}
// TODO add todo
return res.status(200).json({ ok: true })
})
Parameter | Description |
---|---|
authMiddlewareMap |
Object that maps different types of auth to their middleware |
globalMiddlewares |
Middlewares that should be applied on every route |
apiName |
Used as the name of the api in openapi.json |
productionServerUrl |
Used as the default server url in openapi.json |
Parameter | Description |
---|---|
methods |
HTTP Methods accepted by this route |
auth |
none or a key from your authMiddlewareMap , this authentication middleware will be applied |
queryParams |
Any GET query parameters on the request as a zod object |
jsonBody |
The JSON body this endpoint accepts as a zod object |
formData |
The multipart/form-data (todo) or application/x-www-form-urlencoded encoded body |
commonParams |
Parameters common to both the query and json body as a zod object, this is sometimes used if a GET route also accepts POST |
jsonResponse |
A zod object representing the json resposne |
Just run nextlove generate-openapi
in your project root!
Examples:
# Print OpenAPI JSON directly to the command line for the package in the current directory
nextlove generate-openapi --packageDir .
# Write OpenAPI JSON to "openapi.json" file
nextlove generate-openapi . --outputFile openapi.json
# Only generate OpenAPI JSON for public api routes
nextlove generate-openapi . --pathGlob '/pages/api/public/**/*.ts'
Parameter | Description |
---|---|
packageDir |
Path to directory containing package.json and NextJS project |
outputFile |
Path to output openapi.json file |
pathGlob |
Paths to consider as valid routes for OpenAPI generation, defaults to /pages/api/**/*.ts |
import { generateOpenAPI } from "nextlove"
generateOpenAPI({
packageDir: ".",
outputFile: "openapi.json",
pathGlob: "/src/pages/api/**/*.ts",
// Tags improve the organization of an OpenAPI spec by making "expandable"
// sections including routes
tags: ["users", "teams", "workspaces"].map((t) => ({
name: `/${t}`,
description: t,
doesRouteHaveTag: (route) => route.includes(`/${t}`),
})),
mapFilePathToHTTPRoute(fp) {
return fp
.replace("src/pages/api/public", "")
.replace(/\.ts$/, "")
.replace(/\/index$/, "")
},
})
Just run nextlove extract-route-specs
in your project root! It will output a ESM file bundled by esbuild.
Caveats:
- All dependencies and dev dependencies in your
package.json
are automatically marked as external when bundling. This means that you may want to re-bundle the output file if you plan on publishing it as part of a library. - By default, API route files aren't allowed to import anything besides dependencies declared in
package.json
. This is to avoid accidentally polluting the bundle. To allow specific imports, use the--allowed-import-patterns
flag:--allowed-import-patterns '**/lib/**' --allowed-import-patterns '**/models/**'
import { wrappers } from "nextlove"
wrappers(withDatabase, logger.withContext("somecontext"), async (req, res) => {
res.status(200).end("...")
})
import { BadRequestException } from "nextlove"
// Inside a route handler
if (bad_soups.includes(soup_param)) {
throw new BadRequestException({
type: "cant_make_soup",
message: "Soup was too difficult, please specify a different soup",
data: { soup_param },
})
}
This repo bundles NextJS utility modules including...
- nextjs-exception-middleware
- nextjs-server-modules
- nextjs-middleware-wrappers
- openAPI generation utilities