@restdocs/nextjs
v0.3.2
Published
Next.js integration for @restdocs with App Router support
Downloads
13
Maintainers
Readme
@restdocs/nextjs
Test-driven API documentation for Next.js 14+ App Router.
Features
- ✅ Test-Driven Documentation: Documentation generated from actual tests ensures accuracy
- 🔄 Hybrid Mode: Quick feedback during development, test verification in production
- 🎨 Interactive Dev UI: Built-in Swagger UI for exploring and testing endpoints
- 📦 Zero Config: Minimal setup, works out of the box
- 🎯 Type-Safe: Full TypeScript support with type inference
- 🚀 Next.js 14+ App Router: First-class support for modern Next.js
Installation
npm install @restdocs/nextjs --save-dev
# or
pnpm add -D @restdocs/nextjsQuick Start
1. Configure Next.js
// next.config.ts
import { withRestDocs } from '@restdocs/nextjs/config';
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true,
};
export default withRestDocs({
...nextConfig,
// RestDocs configuration (optional)
restdocs: {
mode: 'hybrid', // 'test-driven' | 'development' | 'hybrid'
enabled: process.env.NODE_ENV === 'development',
path: '/api/__restdocs',
},
});2. Document Your API Routes
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { api, field } from '@restdocs/nextjs';
export async function POST(request: NextRequest) {
const body = await request.json();
// Document the API
api.document('POST /api/users', {
description: 'Create a new user',
tags: ['Users'],
request: {
email: field.email().required(),
name: field.string().required(),
},
response: {
id: field.uuid(),
email: field.email(),
name: field.string(),
},
statusCode: 201,
});
const user = { id: crypto.randomUUID(), ...body };
return NextResponse.json(user, { status: 201 });
}3. Access the Dev UI
Start your Next.js development server and visit:
http://localhost:3000/api/__restdocsYou'll see an interactive Swagger UI with all your documented endpoints.
Documentation Modes
Test-Driven (Recommended)
Documentation is only generated from tests. This ensures documentation always matches actual behavior.
// next.config.ts
restdocs: {
mode: 'test-driven'
}Development Mode
Documentation can be defined in route handlers for rapid prototyping.
// next.config.js
restdocs: {
mode: 'development'
}Hybrid Mode (Default)
Best of both worlds:
- Route-based docs show up immediately in Dev UI (marked as ⚠️ unverified)
- Test-based docs are marked as ✅ verified
- CI requires all endpoints to be verified by tests
// next.config.js
restdocs: {
mode: 'hybrid',
hybrid: {
allowRouteDocumentation: true,
requireTests: true, // CI will fail if docs aren't verified
showDocumentationSource: true
}
}Writing Tests
// __tests__/api/users.test.ts
import { api, field } from '@restdocs/nextjs';
import { POST } from '@/app/api/users/route';
describe('POST /api/users', () => {
it('should create a new user', async () => {
// Document in test (verified documentation)
api.document('POST /api/users', {
description: 'Create a new user',
tags: ['Users'],
request: {
email: field.email().required(),
name: field.string().required(),
},
response: {
id: field.uuid(),
email: field.email(),
name: field.string(),
},
statusCode: 201,
});
const request = new Request('http://localhost:3000/api/users', {
method: 'POST',
body: JSON.stringify({
email: '[email protected]',
name: 'Test User',
}),
});
const response = await POST(request as any);
expect(response.status).toBe(201);
// Test passes → documentation marked as verified ✅
});
});Field Builders
@restdocs provides a fluent API for defining schemas:
import { field } from '@restdocs/nextjs';
field.string() // String field
field.number() // Number field
field.integer() // Integer field
field.boolean() // Boolean field
field.uuid() // UUID string
field.email() // Email string
field.url() // URL string
field.datetime() // ISO 8601 datetime string
field.array(field.string()) // Array of strings
field.object({ // Object with properties
name: field.string(),
age: field.integer()
})
// Modifiers
field.string().required() // Required field
field.string().optional() // Optional field
field.string().minLength(5) // Min length
field.string().maxLength(100) // Max length
field.number().min(0).max(100) // Min/max value
field.string().enum(['a', 'b', 'c']) // Enum values
field.string().default('default') // Default value
field.string().description('...') // DescriptionFile Upload / FormData Support
RestDocs provides first-class support for documenting file upload endpoints with the field.file() builder:
Basic File Upload
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { api, field } from '@restdocs/nextjs';
export async function POST(request: NextRequest) {
api.document('POST /api/upload', {
description: 'Upload a file with metadata',
tags: ['Files'],
headers: {
Authorization: field.string().required(),
},
request: {
file: field.file()
.required()
.acceptedTypes(['image/png', 'image/jpeg', 'application/pdf'])
.maxSize(5 * 1024 * 1024) // 5MB
.description('File to upload'),
title: field.string().required(),
description: field.string().optional(),
},
response: {
id: field.uuid(),
filename: field.string(),
size: field.integer(),
type: field.string(),
uploadedAt: field.datetime(),
},
statusCode: 201,
});
const formData = await request.formData();
const file = formData.get('file') as File;
// ... handle upload
}File Field Options
field.file()
.required() // Mark as required
.optional() // Mark as optional
.format('binary') // binary (default) or base64
.acceptedTypes('image/*') // Single MIME type
.acceptedTypes(['image/png', 'image/jpeg']) // Multiple MIME types
.maxSize(5 * 1024 * 1024) // Max file size in bytes
.minSize(1024) // Min file size in bytes
.description('Upload your profile picture') // DescriptionMultiple File Upload
api.document('POST /api/gallery', {
request: {
images: field.array(field.file())
.minItems(1)
.maxItems(10)
.description('Upload up to 10 images'),
caption: field.string().optional(),
},
response: {
galleryId: field.uuid(),
imageCount: field.integer(),
},
});Base64 Encoding
For base64-encoded files:
api.document('POST /api/upload-base64', {
request: {
file: field.file()
.format('base64')
.acceptedTypes('image/*')
.description('Base64-encoded image'),
},
});Generated OpenAPI Spec
The file fields automatically generate correct OpenAPI 3.0 specifications:
{
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"file": {
"type": "string",
"format": "binary",
"description": "File to upload",
"x-content-type": ["image/png", "image/jpeg"],
"x-max-size": 5242880
}
},
"required": ["file"]
}
}
}
}
}Testing File Uploads
// __tests__/api/upload.test.ts
it('should upload file with FormData', async () => {
api.document('POST /api/upload', {
request: {
file: field.file()
.required()
.acceptedTypes(['image/png', 'image/jpeg'])
.maxSize(5 * 1024 * 1024),
title: field.string().required(),
},
response: {
id: field.uuid(),
filename: field.string(),
},
statusCode: 201,
});
const formData = new FormData();
const blob = new Blob(['content'], { type: 'image/png' });
const file = new File([blob], 'photo.png', { type: 'image/png' });
formData.append('file', file);
formData.append('title', 'Profile Picture');
const request = new NextRequest('http://localhost:3000/api/upload', {
method: 'POST',
body: formData,
});
const response = await POST(request);
expect(response.status).toBe(201);
});Generated Outputs
RestDocs automatically generates:
- OpenAPI 3.0 Spec:
GET /api/__restdocs/openapi.json - Interactive Swagger UI:
GET /api/__restdocs - Statistics:
GET /api/__restdocs/stats
CI Validation
Validate documentation in your CI pipeline:
// scripts/validate-docs.ts
import { api } from '@restdocs/nextjs';
const result = api.validate();
if (!result.valid) {
console.error('Documentation validation failed:');
result.errors?.forEach(err => {
console.error(`- ${err.endpoint}: ${err.message}`);
console.error(` ${err.suggestion}`);
});
process.exit(1);
}Configuration Options
interface RestDocsConfig {
// Documentation mode
mode?: 'test-driven' | 'development' | 'hybrid'; // default: 'hybrid'
// Enable/disable RestDocs
enabled?: boolean; // default: NODE_ENV === 'development'
// Dev UI path
path?: string; // default: '/api/__restdocs'
// Output directory for generated files
outputDir?: string; // default: './docs/api'
// Hybrid mode configuration
hybrid?: {
allowRouteDocumentation?: boolean; // default: true
requireTests?: boolean; // default: true
showDocumentationSource?: boolean; // default: true
};
}License
MIT
