Skip to content

[Bug]: basePath causes duplicated '/api' prefix in OpenAPI spec #1913

Description

@ZifRoot

Which middleware has the bug?

@hono/zod-openapi

What version of the middleware?

^1.4.0

What version of Hono are you using?

^4.12.19

What runtime/platform is your app running on? (with version if possible)

Node.js v22.13.0

What steps can reproduce the bug?

import { OpenAPIHono } from '@hono/zod-openapi';
import { swaggerUI } from '@hono/swagger-ui';

const app = new OpenAPIHono().basePath('/api');

app.get('/documents', (c) => c.text('ok'));

app.doc('/openapi.json', {
openapi: '3.0.0',
info: { title: 'API', version: '1.0.0' },
});
app.get('/docs', swaggerUI({ url: '/openapi.json' }));

// Откройте /docs в браузере — пути будут содержать двойной /api

What is the expected behavior?

basePath('/api') should not modify the paths in the OpenAPI specification. Instead, the value should be added to the servers array (e.g., { url: '/api' }). This follows the OpenAPI 3.0 specification and avoids duplicated prefixes in the generated documentation.

What do you see instead?

The generated paths object contains the basePath prefix for every route (e.g., /api/documents instead of /documents). When viewed in Swagger UI, this results in duplicate prefixes like /api/api/documents. The servers array remains empty or unchanged.

Additional information

The issue is likely in packages/zod-openapi/src/generator.ts – the addBasePathToDocument function currently modifies paths directly. A fix would move the basePath to servers instead. I have a working patch ready (adds basePath to servers with duplicate check) and can submit a PR if this is the desired behavior.

//packages/zod-openapi/src/index.ts
/**
 * Modifies OpenAPI document: moves basePath from paths to servers array.
 * @param document - The OpenAPI document object.
 * @param basePath - The base path to add (e.g., '/api').
 * @returns Updated document with basePath in servers, not in paths.
 */
function addBasePathToDocument(document: Record<string, any>, basePath: string) {
  if (!basePath || basePath === '/') return document;

  const newServer = { url: basePath };
  const servers = document.servers || [];

  // Avoid duplicate server entries
  if (!servers.some(s => s.url === newServer.url)) {
    document.servers = [newServer, ...servers];
  }

  return document;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions