npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@restdocs/nextjs

v0.3.2

Published

Next.js integration for @restdocs with App Router support

Downloads

13

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/nextjs

Quick 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/__restdocs

You'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('...')   // Description

File 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')          // Description

Multiple 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:

  1. OpenAPI 3.0 Spec: GET /api/__restdocs/openapi.json
  2. Interactive Swagger UI: GET /api/__restdocs
  3. 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