Stop writing API docs twice. Test-driven documentation for Next.js - Generate OpenAPI specs, TypeScript types, and interactive Swagger UI directly from your tests.
If you're building Next.js APIs for production, you know the pain:
// โ THE OLD WAY: Write tests...
it('creates user', async () => {
const res = await POST(request);
expect(res.status).toBe(201);
});
// ...then SEPARATELY maintain OpenAPI docs
/**
* @openapi
* /api/users:
* post:
* summary: Create user
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* email: {type: string, format: email}
* name: {type: string, minLength: 2}
* responses:
* 201:
* description: User created
* content:
* application/json:
* schema:
* type: object
* properties:
* id: {type: string, format: uuid}
* email: {type: string}
* name: {type: string}
*/
Result?
- โ 150+ lines of docs for 10 lines of test code
- โ Docs drift within weeks (guaranteed)
- โ Maintenance nightmare - update both on every API change
- โ No TypeScript types generated from specs
Write your test once. Get everything else automatically.
// โ
THE @restdocs WAY: One test, complete documentation
import { api, field } from '@restdocs/core';
it('creates user', async () => {
const res = await POST(request);
// ๐ This ONE call generates OpenAPI + TypeScript + Swagger UI
api.document('POST /api/users', {
description: 'Create a new user',
request: {
email: field.email().required(),
name: field.string().required().minLength(2).maxLength(50),
},
response: {
id: field.uuid(),
email: field.email(),
name: field.string(),
createdAt: field.datetime(),
},
statusCode: 201,
});
expect(res.status).toBe(201);
});
After running npm test
, you get:
- โ
openapi.json
- Full OpenAPI 3.0 spec - โ
types.ts
- Auto-generated TypeScript interfaces - โ
api.md
- Human-readable documentation - โ
Swagger UI - Interactive API explorer at
/api/__restdocs
70% less code. 100% accuracy. Always in sync.
Pain Point | Traditional Approach | @restdocs |
---|---|---|
API Documentation | Manual OpenAPI YAML/JSON | Auto-generated from tests |
Type Safety | Manually sync types with docs | Types generated automatically |
Doc Accuracy | Drifts after 2-3 sprints | Always matches code (test-driven) |
Developer Experience | Context switch to write docs | Document while testing |
Team Onboarding | Read outdated docs โ confusion | Interactive Swagger UI + accurate specs |
API Contract Validation | Manual or separate tools | Built-in validation against schemas |
- 70% less documentation code - One test vs. test + separate docs
- Zero documentation drift - Docs generated from actual API behavior
- 10x faster onboarding - New devs explore APIs via Swagger UI
- Type-safe integration - Frontend teams use generated TypeScript types
npm install @restdocs/nextjs --save-dev
// next.config.ts
import { withRestDocs } from '@restdocs/nextjs/config';
export default withRestDocs({
restdocs: {
enabled: process.env.NODE_ENV === 'development',
path: '/api/__restdocs',
},
});
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({ dir: './' });
module.exports = createJestConfig({
reporters: [
'default',
[
'@restdocs/jest/reporter',
{
outputDir: './docs/api',
formats: ['openapi', 'markdown', 'typescript'],
},
],
],
});
// app/api/__restdocs/route.ts
import { createDevUIHandler } from '@restdocs/nextjs/dev-ui';
const handler = createDevUIHandler();
export { handler as GET };
// __tests__/api/users.test.ts
import { api, field } from '@restdocs/core';
import { POST } from '@/app/api/users/route';
it('creates user', async () => {
const res = await POST(request);
api.document('POST /api/users', {
description: 'Create a new user',
request: {
email: field.email().required(),
name: field.string().required().minLength(2),
},
response: {
id: field.uuid(),
email: field.email(),
name: field.string(),
},
statusCode: 201,
});
expect(res.status).toBe(201);
});
# Generate documentation
npm test
# Start dev server
npm run dev
# Open Swagger UI
open http://localhost:3000/api/__restdocs
field
.string()
.required()
.minLength(2)
.maxLength(50)
.pattern(/^[A-Z]/)
.description('User name')
.example('John Doe');
field.email().required();
field.uuid();
field.datetime();
field.integer().min(18).max(120);
field.array(field.string()).uniqueItems();
field.object({
name: field.string(),
age: field.integer().optional(),
});
const requestBody = {
email: 'user@example.com',
name: 'John Doe',
};
api.document('POST /api/users', {
request: api.infer(requestBody, { required: ['email', 'name'] }),
response: api.infer(response.body),
});
// Validate response against documented schema
api.validate(response.body).against('POST /api/users', 201);
// Throws descriptive error if validation fails
After running tests, get:
openapi.json
- OpenAPI 3.0 specificationapi.md
- Markdown documentationtypes.ts
- TypeScript type definitions- Dev UI - Interactive Swagger UI (Next.js only)
@restdocs is in active beta (v0.3.0). This means:
- Core DSL API and field builders
- Jest integration and test reporter
- OpenAPI 3.0 generation
- Next.js 15 App Router support
- TypeScript type generation
- 247 tests passing (100% coverage)
- Performance at scale - Tested with ~50 endpoints, not 500+
- Complex schemas - Nested objects work, but edge cases may exist
- Framework support - Next.js is solid, others (Express/Fastify) are lighter
- Production readiness - Use in dev/staging first, production at your own risk
- Vitest native support (currently uses Jest API)
- Advanced schema features (discriminated unions, recursive types)
- Performance optimization for large APIs (100+ endpoints)
- Plugin ecosystem for custom generators
- Fastify/Express deep integration
- โ Greenfield Next.js projects building APIs
- โ Teams tired of doc-test maintenance overhead
- โ Developers who want to experiment with test-driven docs
- โ Projects where API contract accuracy is critical
- โ Mission-critical APIs without fallback documentation
- โ Large-scale production deployments (test in staging first)
- โ Complex OpenAPI 3.1 features (allOf/oneOf/discriminators)
We're being transparent: This tool will save you time, but it's young. Use it, break it, and help us make it production-ready. Star the repo, report issues, and join the journey.
@restdocs/
โโโ packages/
โ โโโ restdocs/ # @restdocs/core - Core DSL engine
โ โโโ generators/ # @restdocs/generators - Output generators
โ โโโ jest/ # @restdocs/jest - Jest integration
โ โโโ nextjs/ # @restdocs/nextjs - Next.js integration
โโโ examples/
โโโ express-jest/ # Express + Jest example
โโโ nextjs-app-router/ # Next.js 15 App Router example
Strings
field.string().required().minLength(2).maxLength(50);
field.email(); // String with email format
field.uuid(); // String with UUID format
field.uri(); // String with URI format
field.datetime(); // ISO 8601 datetime
field.date(); // ISO 8601 date
Numbers
field.number().min(0).max(100);
field.integer().multipleOf(5);
Complex Types
field.boolean().default(true);
field.object({
name: field.string().required(),
age: field.integer().optional(),
});
field.array(field.string()).minItems(1).maxItems(10);
// Document an endpoint
api.document('POST /api/users', { ... });
// Auto-infer schema from data
api.infer(data, { required: ['email'] });
// Validate response against schema
api.validate(response.body).against('POST /api/users', 201);
Check out the Next.js 15 example project with:
- JWT authentication (register + login)
- Users & Posts CRUD APIs
- 19 comprehensive tests
- Full OpenAPI documentation
- TypeScript types generated
@restdocs is experimental - we need your feedback!
- ๐ Found a bug? Open an issue
- ๐ก Have an idea? Start a discussion
- ๐ Want to contribute? See CONTRIBUTING.md
- Spring REST Docs - Test-driven documentation for Spring
- Zod - TypeScript-first schema validation
MIT ยฉ Jae Song Choi
- ๐ซ GitHub Issues
- ๐ฌ GitHub Discussions
Built with โค๏ธ for Next.js teams who hate writing docs twice.
โญ Star this repo if you believe API docs should just work.