Package Exports
- @alan910127/next-api-builder
- @alan910127/next-api-builder/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@alan910127/next-api-builder) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Next.js REST API Builder
A simple, tRPC-like Next.js RESTful API builder based on Zod validation
Table of Contents
Installation
Zod is now removed from the dependency, as a result, you need to install zod manually or choose any validation library as you desired.
⚠️ This package is still built with zod, the other libraries are not tested ⚠️
npm
npm install @alan910127/next-api-builder zodyarn
yarn add @alan910127/next-api-builder zodpnpm
pnpm add @alan910127/next-api-builder zodExample Usage
Caveat
If you're using zod to define validation schemas, you should always use z.coerce.{type}() for non-string types instead of using z.{type}() directly, or the requests will be rejected due to typing issues.
Static Routes
// pages/api/hello/index.ts
import { createEndpoint, procedure } from "@alan910127/next-api-builder";
import { randomUUID } from "crypto";
import { z } from "zod";
export default createEndpoint({
get: procedure
.query(
z.object({
text: z.string(),
age: z.coerce.number().nonnegative().optional(),
})
)
.handler(async ({ query: { text, age } }) => {
// ^? (property) query: { age?: number | undefined; text: string; }
return {
greeting: `Hello ${text}`,
age,
};
}),
post: procedure
.body(
z.object({
name: z.string(),
age: z.coerce.number().nonnegative(),
})
)
.handler(async ({ body: { name, age } }, res) => {
// ^? (property) body: { age: number; name: string; }
// Create some records in datebase...
res.status(201);
return {
id: randomUUID(),
name,
age,
};
}),
// ...put, delete etc.
});Example Response
GET without parameters:
GET http://localhost:3000/api/helloStatus: 422 Unprocessable Entity
{ "message": "Invalid request", "errors": [ { "text": "Required" } ] }
GET with required parameters:
GET http://localhost:3000/api/hello?text=Next.jsStatus: 200 OK
{ "greeting": "Hello Next.js" }
GET with incorrect optional paramters:
GET http://localhost:3000/api/hello?text=Next.js&age=testStatus: 422 Unprocessable Entity
{ "message": "Invalid request", "errors": [ { "age": "Expected number, received nan" } ] }
GET with correct parameters:
GET http://localhost:3000/api/hello?text=Next.js&age=18Status: 200 OK
{ "greeting": "Hello Next.js", "age": 18 }
GET with extra parameters:
http://localhost:3000/api/hello?text=Next.js&age=18&extra=paramStatus: 200 OK
{ "greeting": "Hello Next.js", "age": 18 }
POST with empty body
POST http://localhost:3000/api/helloStatus: 422 Unprocessable Entity
{ "message": "Invalid request", "errors": ["Expected object, received string"] }
POST with correct body
POST http://localhost:3000/api/hello{ "name": "Next.js", "age": "18" }
Status: 201 Created
{ "id": "8a9ebe33-f967-4e6d-8780-eb992e8ddd24", "name": "Next.js", "age": 18 }
Dynamic Routes
// pages/api/hello/[userId].ts
import { createEndpoint, procedure } from "@alan910127/next-api-builder";
import { z } from "zod";
const routeProcedure = procedure.query(
z.object({
userId: z.string().uuid(),
})
);
export default createEndpoint({
get: routeProcedure
.query(
z.object({
name: z.string().optional(),
})
)
.handler(async ({ query: { userId, name } }, res) => {
// ^? (property) query: { userId: string; } & { name?: string | undefined; }
const username = name ?? userId;
return `Hello ${userId}, your name is ${username}.`;
}),
});Example Response
GET with correct fields
GET http://localhost:3000/api/hello/8a9ebe33-f967-4e6d-8780-eb992e8ddd24?name=Next.jsStatus: 200 OK
Hello 8a9ebe33-f967-4e6d-8780-eb992e8ddd24, your name is Next.js.
TODO
- Add support openapi generation
- Add support for middlewares
- Automatic coercion for primitives