Skip to content

[feature request]: Auto-Infer response types from route handler return values #54

@SimonGolms

Description

@SimonGolms

Summary

Automatically infer and generate OpenAPI response schemas by analyzing the actual return type of Next.js route handler functions, eliminating the need for explicit @response JSDoc annotations.

Motivation

Currently, next-openapi-gen requires developers to manually specify response types using JSDoc comments:

/**
 * Get post by ID
 * @response PostResponseSchema  // ← Manual annotation required
 * @openapi
 */
export async function GET(request: NextRequest) {
  const post = {
    id: 1,
    title: "My Post",
    slug: "my-post",
    published: true,
  };
  
  return NextResponse.json(post);
}

This approach has several limitations:

  1. Duplication of Truth: The actual response structure is defined in the function body, but we must also manually specify a separate schema
  2. Maintenance Burden: When the response structure changes, developers must remember to update both the implementation AND the JSDoc annotation
  3. Type Safety Gap: There's no compile-time guarantee that the @response annotation matches what the function actually returns
  4. Verbosity: Simple endpoints require extra boilerplate just for documentation

Proposed Solution

Automatically infer response types from the route handler's return type by analyzing:

  1. Direct NextResponse.json() calls with inline objects or variables
  2. Function return type annotations (e.g., Promise<NextResponse>)
  3. TypeScript type inference from return statements

Example 1: Direct Inference from Return Value

/**
 * Get post by ID
 * @openapi
 */
export async function GET(request: NextRequest) {
  const post = {
    id: 1,
    title: "My Post",
    slug: "my-post",
    published: true,
  };
  
  return NextResponse.json(post);
}

Example 2: Inference from Function Return Type

type PostResponse = {
  id: number;
  title: string;
  slug: string;
  published: boolean;
};

/**
 * Get post by ID
 * @openapi
 */
export async function GET(
  request: NextRequest
): Promise<NextResponse<PostResponse>> {
  // Implementation...
  return NextResponse.json(post);
}

Example 3: Mixed Approach (Backwards Compatible)

/**
 * Get post by ID
 * @response PostResponseSchema  // ← Explicit annotation takes precedence
 * @openapi
 */
export async function GET(request: NextRequest) {
  return NextResponse.json(post);
}

If @response is explicitly provided, use it (backwards compatible). Otherwise, attempt auto-inference.

Configuration Options

Add new configuration to next.openapi.json:

{
  "inferResponses": true,  // Enable automatic response inference (default: false for backwards compatibility)
  "inferredSchemaPrefix": "Auto",  // Prefix for auto-generated schema names
  "preferInferredResponses": false  // If true, prefer inferred over @response annotation
}

Benefits

✅ Reduced Boilerplate: No need to write @response for simple endpoints
✅ Single Source of Truth: Response type is derived from actual implementation
✅ Type Safety: Auto-synced with TypeScript types
✅ Better DX: Less manual work, fewer errors
✅ Gradual Adoption: Backwards compatible with existing @response annotations
✅ Maintenance: Response schemas automatically update when code changes

Use Cases

  1. Simple CRUD Operations

    export async function GET() {
      return NextResponse.json({ message: "Hello World" });
    }
    // Auto-generates: { message: string }
  2. Database Query Results

    export async function GET() {
      const users = await db.select().from(usersTable);
      return NextResponse.json(users);
    }
    // Auto-generates schema from Drizzle/Prisma types
  3. Typed Responses

    export async function GET(): Promise<NextResponse<User[]>> {
      const users = await getUsers();
      return NextResponse.json(users);
    }
    // Auto-generates schema from User[] type
  4. Error Responses (Union Types)

    export async function GET() {
      if (error) {
        return NextResponse.json({ error: "Not found" }, { status: 404 });
      }
      return NextResponse.json({ data: post });
    }
    // Auto-generates schemas for both success and error cases

Related Issues

#53

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions