Repository: nestjs/swagger
Pull request #3185 (2024-11-24 04:12:46)

feat(swagger): add documentsEnabled option to disable JSON/YAML
## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [x] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Other... Please describe:

## What is the current behavior?
Currently, the Swagger module always serves both Swagger UI and JSON/YAML API definitions. There is no way to selectively disable one or the other.

Issue Number: #3157 


## What is the new behavior?
This PR introduces two new options to provide greater control over Swagger endpoints:

swaggerUiEnabled: If false, the Swagger UI will not be served.
documentsEnabled: If false, JSON and YAML API definitions will not be served.

Developers can now:
1. Disable the Swagger UI but retain JSON/YAML definitions.
2. Disable JSON/YAML definitions but retain the Swagger UI.
3. Completely disable all Swagger-related endpoints.

These changes enhance the flexibility and security of Swagger integration, enabling better alignment with deployment needs.

JSON/YAML is not provided as below when documentEnabled is set to FALSE.

![image](https://github.com/user-attachments/assets/b72932fe-c058-46f6-b6e1-23ab1f1b7d38)
![image](https://github.com/user-attachments/assets/72eec0eb-ca30-442b-ba88-441cf96d172f)


## Does this PR introduce a breaking change?
- [ ] Yes
- [x] No





Patch:
diff --git a/lib/interfaces/swagger-custom-options.interface.ts b/lib/interfaces/swagger-custom-options.interface.ts
index aa3c15c4..7fcea3af 100644
--- a/lib/interfaces/swagger-custom-options.interface.ts
+++ b/lib/interfaces/swagger-custom-options.interface.ts
@@ -1,5 +1,5 @@
-import { SwaggerUiOptions } from './swagger-ui-options.interface';
 import { OpenAPIObject } from './open-api-spec.interface';
+import { SwaggerUiOptions } from './swagger-ui-options.interface';
 
 export interface SwaggerCustomOptions {
   /**
@@ -10,12 +10,22 @@ export interface SwaggerCustomOptions {
   useGlobalPrefix?: boolean;
 
   /**
-   * If `false`, only API definitions (JSON and YAML) will be served (on `/{path}-json` and `/{path}-yaml`).
-   * This is particularly useful if you are already hosting a Swagger UI somewhere else and just want to serve API definitions.
+   * If `false`, the Swagger UI will not be served. Only API definitions (JSON and YAML)
+   * will be accessible (on `/{path}-json` and `/{path}-yaml`).
+   * To fully disable both the Swagger UI and API definitions, use `raw: false`.
    * Default: `true`.
    */
   swaggerUiEnabled?: boolean;
 
+  /**
+   * If `true`, raw definitions for all formats will be served.
+   * Alternatively, you can pass an array to specify the formats to be served, e.g., `raw: ['json']` to serve only JSON definitions.
+   * If omitted or set to an empty array, no definitions (JSON or YAML) will be served.
+   * Use this option to control the availability of Swagger-related endpoints.
+   * Default: `true`.
+   */
+  raw?: boolean | Array<'json' | 'yaml'>;
+
   /**
    * Url point the API definition to load in Swagger UI.
    */
@@ -103,5 +113,4 @@ export interface SwaggerCustomOptions {
    * @deprecated This property has no effect.
    */
   urls?: Record<'url' | 'name', string>[];
-
 }
diff --git a/lib/swagger-module.ts b/lib/swagger-module.ts
index 12ee6422..3926b3ec 100644
--- a/lib/swagger-module.ts
+++ b/lib/swagger-module.ts
@@ -86,6 +86,7 @@ export class SwaggerModule {
     documentOrFactory: OpenAPIObject | (() => OpenAPIObject),
     options: {
       swaggerUiEnabled: boolean;
+      raw: boolean | Array<'json' | 'yaml'>;
       jsonDocumentUrl: string;
       yamlDocumentUrl: string;
       swaggerOptions: SwaggerCustomOptions;
@@ -112,7 +113,25 @@ export class SwaggerModule {
         options.swaggerOptions
       );
     }
-    this.serveDefinitions(httpAdapter, getBuiltDocument, options);
+
+    /**
+     * Serve JSON/YAML definitions based on the `raw` option:
+     * - `true`: Serve both JSON and YAML definitions.
+     * - `false`: Skip registering both JSON and YAML definitions.
+     * - `Array<'json' | 'yaml'>`: Serve only the specified formats (e.g., `['json']` to serve only JSON).
+     */
+    if (
+      options.raw === true ||
+      (Array.isArray(options.raw) && options.raw.length > 0)
+    ) {
+      const serveJson = options.raw === true || options.raw.includes('json');
+      const serveYaml = options.raw === true || options.raw.includes('yaml');
+
+      this.serveDefinitions(httpAdapter, getBuiltDocument, options, {
+        serveJson,
+        serveYaml
+      });
+    }
   }
 
   protected static serveSwaggerUi(
@@ -227,33 +246,40 @@ export class SwaggerModule {
       jsonDocumentUrl: string;
       yamlDocumentUrl: string;
       swaggerOptions: SwaggerCustomOptions;
-    }
+    },
+    serveOptions: { serveJson: boolean; serveYaml: boolean }
   ) {
-    httpAdapter.get(normalizeRelPath(options.jsonDocumentUrl), (req, res) => {
-      res.type('application/json');
-      const document = getBuiltDocument();
+    if (serveOptions.serveJson) {
+      httpAdapter.get(normalizeRelPath(options.jsonDocumentUrl), (req, res) => {
+        res.type('application/json');
+        const document = getBuiltDocument();
 
-      const documentToSerialize = options.swaggerOptions.patchDocumentOnRequest
-        ? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
-        : document;
+        const documentToSerialize = options.swaggerOptions
+          .patchDocumentOnRequest
+          ? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
+          : document;
 
-      res.send(JSON.stringify(documentToSerialize));
-    });
+        res.send(JSON.stringify(documentToSerialize));
+      });
+    }
 
-    httpAdapter.get(normalizeRelPath(options.yamlDocumentUrl), (req, res) => {
-      res.type('text/yaml');
-      const document = getBuiltDocument();
+    if (serveOptions.serveYaml) {
+      httpAdapter.get(normalizeRelPath(options.yamlDocumentUrl), (req, res) => {
+        res.type('text/yaml');
+        const document = getBuiltDocument();
 
-      const documentToSerialize = options.swaggerOptions.patchDocumentOnRequest
-        ? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
-        : document;
+        const documentToSerialize = options.swaggerOptions
+          .patchDocumentOnRequest
+          ? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
+          : document;
 
-      const yamlDocument = jsyaml.dump(documentToSerialize, {
-        skipInvalid: true,
-        noRefs: true
+        const yamlDocument = jsyaml.dump(documentToSerialize, {
+          skipInvalid: true,
+          noRefs: true
+        });
+        res.send(yamlDocument);
       });
-      res.send(yamlDocument);
-    });
+    }
   }
 
   public static setup(
@@ -283,6 +309,7 @@ export class SwaggerModule {
       : `${finalPath}-yaml`;
 
     const swaggerUiEnabled = options?.swaggerUiEnabled ?? true;
+    const raw = options?.raw ?? true;
 
     const httpAdapter = app.getHttpAdapter();
 
@@ -293,6 +320,7 @@ export class SwaggerModule {
       documentOrFactory,
       {
         swaggerUiEnabled,
+        raw,
         jsonDocumentUrl: finalJSONDocumentPath,
         yamlDocumentUrl: finalYAMLDocumentPath,
         swaggerOptions: options || {}
diff --git a/package-lock.json b/package-lock.json
index 8b97b804..2d6f7d37 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,7 @@
         "@types/jest": "29.5.14",
         "@types/js-yaml": "4.0.9",
         "@types/lodash": "4.17.13",
-        "@types/node": "22.9.3",
+        "@types/node": "22.9.1",
         "@typescript-eslint/eslint-plugin": "8.15.0",
         "@typescript-eslint/parser": "8.15.0",
         "class-transformer": "0.5.1",
@@ -2777,9 +2777,9 @@
       "license": "MIT"
     },
     "node_modules/@types/node": {
-      "version": "22.9.3",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz",
-      "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==",
+      "version": "22.9.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz",
+      "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
diff --git a/package.json b/package.json
index 2b1fa8df..d4ab6c84 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "@types/jest": "29.5.14",
     "@types/js-yaml": "4.0.9",
     "@types/lodash": "4.17.13",
-    "@types/node": "22.9.3",
+    "@types/node": "22.9.1",
     "@typescript-eslint/eslint-plugin": "8.15.0",
     "@typescript-eslint/parser": "8.15.0",
     "class-transformer": "0.5.1",


Test patch:
diff --git a/e2e/express.e2e-spec.ts b/e2e/express.e2e-spec.ts
index 64c84414..235a0a71 100644
--- a/e2e/express.e2e-spec.ts
+++ b/e2e/express.e2e-spec.ts
@@ -163,6 +163,220 @@ describe('Express Swagger', () => {
     });
   });
 
+  describe('disabled Swagger Documents(JSON, YAML) but served Swagger UI', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: false
+      });
+
+      await app.init();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('disabled Both Swagger UI AND Swagger Documents(JSON, YAML)', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        swaggerUiEnabled: false,
+        raw: false
+      });
+
+      await app.init();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should not serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(404);
+      }
+    );
+  });
+
+  describe('Serve only JSON definition when raw is ["json"]', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: ['json']
+      });
+      await app.init();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should serve only the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(200);
+      expect(Object.keys(response.body).length).toBeGreaterThan(0);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('Serve only YAML definition when raw is ["yaml"]', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: ['yaml']
+      });
+      await app.init();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should serve only the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(200);
+      expect(response.text.length).toBeGreaterThan(0);
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('Serve no definitions when raw is an empty array', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: []
+      });
+      await app.init();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
   describe('custom documents endpoints', () => {
     const JSON_CUSTOM_URL = '/apidoc-json';
     const YAML_CUSTOM_URL = '/apidoc-yaml';
diff --git a/e2e/fastify.e2e-spec.ts b/e2e/fastify.e2e-spec.ts
index fabb58ab..675c8f47 100644
--- a/e2e/fastify.e2e-spec.ts
+++ b/e2e/fastify.e2e-spec.ts
@@ -3,11 +3,11 @@ import {
   FastifyAdapter,
   NestFastifyApplication
 } from '@nestjs/platform-fastify';
+import * as path from 'path';
 import * as request from 'supertest';
 import * as SwaggerParser from 'swagger-parser';
 import { DocumentBuilder, SwaggerModule } from '../lib';
 import { ApplicationModule } from './src/app.module';
-import * as path from 'path';
 
 describe('Fastify Swagger', () => {
   let app: NestFastifyApplication;
@@ -84,11 +84,11 @@ describe('Fastify Swagger', () => {
     beforeEach(async () => {
       const swaggerDocument = SwaggerModule.createDocument(
         app,
-        builder.build(),
+        builder.build()
       );
       SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
         // to showcase that in new implementation u can use custom swagger-ui path. Useful when using e.g. webpack
-        customSwaggerUiPath: path.resolve(`./node_modules/swagger-ui-dist`),
+        customSwaggerUiPath: path.resolve(`./node_modules/swagger-ui-dist`)
       });
 
       await app.init();
@@ -117,6 +117,221 @@ describe('Fastify Swagger', () => {
     });
   });
 
+  describe('disabled Swagger Documents(JSON, YAML) but served Swagger UI', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: false
+      });
+
+      await app.init();
+      await app.getHttpAdapter().getInstance().ready();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('disabled Both Swagger UI AND Swagger Documents(JSON, YAML)', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        swaggerUiEnabled: false,
+        raw: false
+      });
+
+      await app.init();
+      await app.getHttpAdapter().getInstance().ready();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should not serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(404);
+      }
+    );
+  });
+
+  describe('Serve only JSON definition when raw is ["json"]', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: ['json']
+      });
+      await app.init();
+      await app.getHttpAdapter().getInstance().ready();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should serve only the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(200);
+      expect(Object.keys(response.body).length).toBeGreaterThan(0);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('Serve only YAML definition when raw is ["yaml"]', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: ['yaml']
+      });
+      await app.init();
+      await app.getHttpAdapter().getInstance().ready();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it('should serve only the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(200);
+      expect(response.text.length).toBeGreaterThan(0);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
+  describe('Serve no definitions when raw is an empty array', () => {
+    const SWAGGER_RELATIVE_URL = '/apidoc';
+
+    beforeEach(async () => {
+      const swaggerDocument = SwaggerModule.createDocument(
+        app,
+        builder.build()
+      );
+      SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, {
+        raw: []
+      });
+      await app.init();
+      await app.getHttpAdapter().getInstance().ready();
+    });
+
+    afterEach(async () => {
+      await app.close();
+    });
+
+    it('should not serve the JSON definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-json`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it('should not serve the YAML definition file', async () => {
+      const response = await request(app.getHttpServer()).get(
+        `${SWAGGER_RELATIVE_URL}-yaml`
+      );
+      expect(response.status).toEqual(404);
+    });
+
+    it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])(
+      'should serve Swagger UI at "%s"',
+      async (url) => {
+        const response = await request(app.getHttpServer()).get(url);
+        expect(response.status).toEqual(200);
+      }
+    );
+  });
+
   describe('custom documents endpoints', () => {
     const JSON_CUSTOM_URL = '/apidoc-json';
     const YAML_CUSTOM_URL = '/apidoc-yaml';
@@ -158,7 +373,7 @@ describe('Fastify Swagger', () => {
         `${JSON_CUSTOM_URL}?description=My%20custom%20description`
       );
 
-      expect(response.body.info.description).toBe("My custom description");
+      expect(response.body.info.description).toBe('My custom description');
     });
 
     it('yaml document should be server in the custom url', async () => {
@@ -172,7 +387,7 @@ describe('Fastify Swagger', () => {
       const response = await request(app.getHttpServer()).get(
         `${YAML_CUSTOM_URL}?description=My%20custom%20description`
       );
-      expect(response.text).toContain("My custom description");
+      expect(response.text).toContain('My custom description');
     });
   });
 
@@ -310,7 +525,7 @@ describe('Fastify Swagger', () => {
 
     it('should patch the OpenAPI document', async function () {
       const response: Response = await request(app.getHttpServer()).get(
-        "/custom/swagger-ui-init.js?description=Custom%20Swagger%20description%20passed%20by%20query%20param"
+        '/custom/swagger-ui-init.js?description=Custom%20Swagger%20description%20passed%20by%20query%20param'
       );
       expect(response.text).toContain(
         `"description": "Custom Swagger description passed by query param"`
@@ -330,24 +545,30 @@ describe('Fastify Swagger', () => {
       );
 
       SwaggerModule.setup('/:tenantId/', app, swaggerDocument, {
-        patchDocumentOnRequest<ExpressRequest, ExpressResponse> (req, res, document) {
+        patchDocumentOnRequest<ExpressRequest, ExpressResponse>(
+          req,
+          res,
+          document
+        ) {
           return {
             ...document,
             info: {
               description: `${req.params.tenantId}'s API documentation`
             }
-          }
+          };
         }
       });
 
       await app.init();
       await app.getHttpAdapter().getInstance().ready();
 
-      const response: Response = await request(app.getHttpServer()).get('/tenant-1/swagger-ui-init.js');
+      const response: Response = await request(app.getHttpServer()).get(
+        '/tenant-1/swagger-ui-init.js'
+      );
 
       await app.close();
       expect(response.text).toContain("tenant-1's API documentation");
-    })
+    });
 
     afterEach(async () => {
       await app.close();