π Spring Framework-style route wrapper for Next.js 15 App Router
Transform your Next.js API routes with Spring Framework-inspired patterns, automatic validation, type safety, and built-in OpenAPI documentation generation.
- Provider Agnostic: Works with NextAuth.js, Clerk, Supabase, custom JWT, and more
- Role-based Access Control: Fine-grained permission system
- Automatic Token Validation: Built-in authentication middleware
- Request Validation: Query params, body, and path parameters
- Response Validation: Development-mode response schema validation
- Zod Integration: Leverages Zod for schema definition and validation
- Type Inference: Full TypeScript support with automatic type inference
- Spring-style API: Familiar patterns for Java developers
- Zero Configuration: Works out of the box with sensible defaults
- Error Handling: Standardized error responses with custom handlers
- Hot Reload: Full Next.js development experience
- OpenAPI Generation: Automatic OpenAPI 3.0 spec generation
- Swagger UI: Built-in API documentation interface
- Type-safe: Generated types match your runtime behavior
npm install next-openapi-router zod
# or
yarn add next-openapi-router zod
# or
pnpm add next-openapi-router zodCreate your first API route:
// app/api/users/route.ts
import { NextResponse } from 'next/server';
import { createRoute } from 'next-openapi-router';
import { z } from 'zod';
// Define your schemas
const GetUsersQuerySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
});
const UserResponseSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
createdAt: z.string(),
});
// Create type-safe route
export const GET = createRoute({
querySchema: GetUsersQuerySchema,
responseSchema: z.object({
users: z.array(UserResponseSchema),
totalCount: z.number(),
}),
auth: false, // No auth required
}, async ({ query }) => {
// query is automatically validated and typed!
const { page, limit } = query!;
const users = await getUsersFromDB(page, limit);
return NextResponse.json({
users,
totalCount: users.length,
});
});Set up authentication and create protected routes:
// lib/auth.ts
import type { AuthProvider } from 'next-openapi-router';
const authProvider: AuthProvider<User> = {
async authenticate(request) {
const token = request.headers.get('authorization')?.slice(7);
return await verifyToken(token);
},
async authorize(user, request) {
return user.role === 'admin';
},
getRoles(user) {
return [user.role];
},
};
// lib/controller.ts
import { createController } from 'next-openapi-router';
export const controller = createController({
auth: authProvider,
onAuthError: () => NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
),
});
// app/api/admin/users/route.ts
export const POST = controller.post({
bodySchema: CreateUserSchema,
responseSchema: UserResponseSchema,
auth: 'required', // Automatic authentication
}, async ({ body, user }) => {
// body is validated, user is authenticated!
const newUser = await createUser(body!, user!.id);
return NextResponse.json(newUser, { status: 201 });
});export async function POST(request: Request) {
// Manual auth check
const token = request.headers.get('authorization');
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const user = await verifyToken(token);
if (!user) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
// Manual validation
const body = await request.json();
if (!body.email || !isValidEmail(body.email)) {
return NextResponse.json({ error: 'Invalid email' }, { status: 400 });
}
try {
const result = await createUser(body);
return NextResponse.json(result);
} catch (error) {
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
}
}export const POST = controller.post({
bodySchema: z.object({
email: z.string().email(),
name: z.string().min(1),
}),
responseSchema: UserSchema,
auth: 'required',
}, async ({ body, user }) => {
const result = await createUser(body!, user!.id);
return NextResponse.json(result);
});β 90% less boilerplate code β Automatic validation and type safety β Built-in error handling β Zero configuration authentication
Works seamlessly with popular authentication solutions:
NextAuth.js |
Clerk |
Supabase |
π Custom JWT |
| View Example | View Example | View Example | View Example |
| Resource | Description |
|---|---|
| Getting Started | Step-by-step setup guide |
| API Reference | Complete API documentation |
| Migration Guide | Migrate from existing Next.js APIs |
| Best Practices | Production-ready patterns |
| Examples | Real-world implementation examples |
import { createController, createRoute } from 'next-openapi-router';
// Individual route
export const GET = createRoute({
querySchema: QuerySchema,
auth: 'required',
}, handler);
// Controller with shared configuration
const controller = createController({
auth: authProvider,
onAuthError: () => NextResponse.json({ error: 'Unauthorized' }, { status: 401 }),
});
export const POST = controller.post({ /* config */ }, handler);
export const PUT = controller.put({ /* config */ }, handler);
export const DELETE = controller.delete({ /* config */ }, handler);import { z } from 'zod';
// Request validation
const CreatePostSchema = z.object({
title: z.string().min(5).max(100),
content: z.string().min(10),
tags: z.array(z.string()).max(5).optional(),
});
// Response validation (development mode)
const PostResponseSchema = z.object({
id: z.string().uuid(),
title: z.string(),
content: z.string(),
createdAt: z.string().datetime(),
});
export const POST = createRoute({
bodySchema: CreatePostSchema,
responseSchema: PostResponseSchema,
validateResponse: true,
}, async ({ body }) => {
const post = await createPost(body!);
return NextResponse.json(post);
});// app/api/docs/route.ts
import { generateOpenAPI } from 'next-openapi-router';
export async function GET() {
const spec = generateOpenAPI({
info: {
title: 'My API',
version: '1.0.0',
},
});
return NextResponse.json(spec);
}Automatically generates OpenAPI 3.0 documentation from your route definitions!
const controller = createController({
auth: authProvider,
onValidationError: (errors) => {
return NextResponse.json({
error: 'Validation failed',
details: errors.map(e => ({
field: e.field,
message: e.message,
})),
}, { status: 400 });
},
onInternalError: (error) => {
console.error('API Error:', error);
return NextResponse.json({
error: 'Internal server error',
requestId: generateRequestId(),
}, { status: 500 });
},
});export const GET = createRoute({
responseSchemas: {
200: UserSchema,
404: z.object({ error: z.string() }),
500: z.object({ error: z.string(), code: z.string() }),
},
}, async ({ params }) => {
const user = await getUserById(params!.id);
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
return NextResponse.json(user); // Validates against UserSchema
});// middleware.ts
import { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Add request ID
request.headers.set('x-request-id', crypto.randomUUID());
// Rate limiting
if (isRateLimited(request)) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429 }
);
}
}
export const config = {
matcher: '/api/:path*',
};// tests/api/users.test.ts
import { describe, it, expect } from 'vitest';
import { testApiRoute } from 'next-openapi-router/testing';
describe('/api/users', () => {
it('should create a user', async () => {
const response = await testApiRoute(POST, {
method: 'POST',
body: { name: 'John', email: 'john@example.com' },
headers: { authorization: 'Bearer valid-token' },
});
expect(response.status).toBe(201);
expect(response.body).toMatchObject({
name: 'John',
email: 'john@example.com',
});
});
});| Feature | next-openapi-router | tRPC | Fastify | Express |
|---|---|---|---|---|
| Next.js 15 App Router | β | β | β | β |
| Zero Config | β | β | β | β |
| Spring-style API | β | β | β | β |
| Auto Type Inference | β | β | β | β |
| Request Validation | β | β | π§ | π§ |
| Response Validation | β | β | β | β |
| OpenAPI Generation | β | β | π§ | π§ |
| Auth Provider Agnostic | β | π§ | π§ | π§ |
| Learning Curve | π Easy | π Medium | π Medium | π Easy |
β Built-in, β Not supported, π§ Requires setup
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/your-org/next-router.git
cd next-router
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Build the package
pnpm build
# Run examples
cd examples/basic-usage
pnpm devMIT Β© Your Name
Give a βοΈ if this project helped you!
- π¦ Follow us on Twitter
- π¬ Join our Discord
- π§ Subscribe to our Newsletter
Documentation β’ Examples β’ Changelog β’ Roadmap
Made with β€οΈ by the next-openapi-router team