nestjs / swagger

OpenAPI (Swagger) module for Nest framework (node.js) :earth_americas:
https://nestjs.com
MIT License
1.69k stars 475 forks source link

Generated JSON missing schemas #1123

Closed willin closed 3 years ago

willin commented 3 years ago

I'm submitting a...


[ ] Regression 
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

QQ20210107-112510

json file:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Cats example",
    "description": "The cats API description",
    "version": "1.0",
    "contact": {}
  },
  "tags": [
    {
      "name": "cats",
      "description": ""
    }
  ],
  "servers": [],
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      }
    },
    "schemas": {
      "CreateCatDto": {
        "type": "object",
        "properties": {}
      },
      "Cat": {
        "type": "object",
        "properties": {
          "age": {
            "type": "number",
            "example": 1,
            "description": "The age of the Cat"
          },
          "breed": {
            "type": "string",
            "example": "Maine Coon",
            "description": "The breed of the Cat"
          }
        },
        "required": [
          "age",
          "breed"
        ]
      }
    }
  },
  "paths": {
    "/cats": {
      "post": {
        "operationId": "CatsController_create",
        "summary": "Create cat",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCatDto"
              }
            }
          }
        },
        "responses": {
          "403": {
            "description": "Forbidden."
          }
        },
        "tags": [
          "cats"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/cats/{id}": {
      "get": {
        "operationId": "CatsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The found record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cat"
                }
              }
            }
          }
        },
        "tags": [
          "cats"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    }
  }
}

Expected behavior

same with: http://localhost:3000/api-json

{
  "swaggerDoc": {
    "openapi": "3.0.0",
    "info": {
      "title": "Cats example",
      "description": "The cats API description",
      "version": "1.0",
      "contact": {}
    },
    "tags": [
      {
        "name": "cats",
        "description": ""
      }
    ],
    "servers": [],
    "components": {
      "securitySchemes": {
        "bearer": {
          "scheme": "bearer",
          "bearerFormat": "JWT",
          "type": "http"
        }
      },
      "schemas": {
        "CreateCatDto": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "age": {
              "type": "number"
            },
            "breed": {
              "type": "string"
            }
          },
          "required": [
            "name",
            "age",
            "breed"
          ]
        },
        "Cat": {
          "type": "object",
          "properties": {
            "age": {
              "type": "number",
              "example": 1,
              "description": "The age of the Cat"
            },
            "breed": {
              "type": "string",
              "example": "Maine Coon",
              "description": "The breed of the Cat"
            },
            "name": {
              "type": "string",
              "description": "The name of the Cat",
              "example": "Kitty"
            }
          },
          "required": [
            "age",
            "breed",
            "name"
          ]
        }
      }
    },
    "paths": {
      "/cats": {
        "post": {
          "operationId": "CatsController_create",
          "summary": "Create cat",
          "parameters": [],
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCatDto"
                }
              }
            }
          },
          "responses": {
            "201": {
              "description": "",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/Cat"
                  }
                }
              }
            },
            "403": {
              "description": "Forbidden."
            }
          },
          "tags": [
            "cats"
          ],
          "security": [
            {
              "bearer": []
            }
          ]
        }
      },
      "/cats/{id}": {
        "get": {
          "operationId": "CatsController_findOne",
          "parameters": [
            {
              "name": "id",
              "required": true,
              "in": "path",
              "schema": {
                "type": "string"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The found record",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/Cat"
                  }
                }
              }
            }
          },
          "tags": [
            "cats"
          ],
          "security": [
            {
              "bearer": []
            }
          ]
        }
      }
    }
  },
  "customOptions": {},
  "swaggerUrl": {}
}

Minimal reproduction of the problem with instructions

https://github.com/nestjs/nest/tree/master/sample/11-swagger

add a generator script:

import { writeFileSync } from 'fs';
import { resolve } from 'path';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  // SwaggerModule.setup('api', app, document);
  writeFileSync(
    resolve(__dirname, '../api.json'),
    JSON.stringify(document, null, 2),
    {
      encoding: 'utf-8',
    },
  );
}
bootstrap();

Environment


Nest version:  7.5.4


For Tooling issues:
- Node version:  14.15.4
- Platform:  Mac

Others:

Tony133 commented 3 years ago

hi, @willin you can leave a minimal reproduction of a clonable git repo so the core team can evaluate the problem you reported.

willin commented 3 years ago

https://github.com/wshow/nest-swagger-generate-demo

npm run build:doc
willin commented 3 years ago

@Tony133

for the timebeing

#!/usr/bin/env bash

# Generate api.json
mv src/main.ts src/main.ts.bak
cp src/doc.ts src/main.ts
nest start
rm -f src/main.ts
mv src/main.ts.bak src/main.ts

we use this script temporarily

but i have no idea why

Tony133 commented 3 years ago

Hi @willin, i took a look at your repo to generate a json schema i tried to make some changes

try this in the nest-cli.json file change it like this:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "plugins": ["@nestjs/swagger/plugin"]
  }
}

then in the main.ts file add this line:

  writeFileSync("./ swagger.json", JSON.stringify(document));

the complete file becomes like this:

import {NestFactory} from '@nestjs/core';
import {DocumentBuilder, SwaggerModule} from '@nestjs/swagger';
import {AppModule} from './app.module';
import {writeFileSync} from "fs";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  writeFileSync("./ swagger.json", JSON.stringify(document));

  SwaggerModule.setup ('api', app, document);

  await app.listen(3000);
  console.log(`Application is running on: $ {await app.getUrl ()}`);
}
bootstrap();

i called it swagger.json but indifferent i took a cue from this problem https://github.com/nestjs/swagger/issues/158

as soon as you run from terminal npm run start:dev you generate the file automatically.

The json that I generate is the following:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Cats example",
    "description": "The cats API description",
    "version": "1.0",
    "contact": {}
  },
  "tags": [{
    "name": "cats",
    "description": ""
  }],
  "servers": [],
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      }
    },
    "schemas": {
      "CreateCatDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "age": {
            "type": "number",
            "example": 1,
            "description": "The age of the Cat"
          },
          "breed": {
            "type": "string",
            "example": "Maine Coon",
            "description": "The breed of the Cat"
          }
        },
        "required": ["name", "age", "breed"]
      },
      "Cat": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "age": {
            "type": "number"
          },
          "breed": {
            "type": "string"
          }
        },
        "required": ["name", "age", "breed"]
      }
    }
  },
  "paths": {
    "/cats": {
      "post": {
        "operationId": "CatsController_create",
        "summary": "Create cat",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCatDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cat"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden."
          }
        },
        "tags": ["cats"],
        "security": [{
          "bearer": []
        }]
      }
    },
    "/cats/{id}": {
      "get": {
        "operationId": "CatsController_findOne",
        "parameters": [{
          "name": "id",
          "required": true,
          "in": "path",
          "schema": {
            "type": "string"
          }
        }],
        "responses": {
          "200": {
            "description": "The found record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cat"
                }
              }
            }
          }
        },
        "tags": ["cats"],
        "security": [{
          "bearer": []
        }]
      }
    }
  }
}

alternatively to generate the file in json you can also create an endpoint that generates the file in json, the endpoint and the following http://localhost: 3000/api-json

Remember, if you want to view everything in swagger you have to add the @ApiProperty decorator for each field you want to view otherwise swagger doesn't display everything.

however if you have difficulty use the discord channel for support

willin commented 3 years ago

@Tony133 i don't want to start up a swagger service at PRODUCTION ENV, so that i need another script to generate api.json standalone.

the thing confuse me is that nest start the script works well, but ts-node src/ not.

Tony133 commented 3 years ago

@willin Ok I misunderstood, but add a description of your problem to specify it better, so to the eye it seems that your problem is that it does not generate the json file.

willin commented 3 years ago
ts-node src/

search CreateCatDto in the generated json, found:

     "CreateCatDto": {
        "type": "object",
        "properties": {}
      },

nest start

found:

"CreateCatDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "age": {
            "type": "number",
            "example": 1,
            "description": "The age of the Cat"
          },
          "breed": {
            "type": "string",
            "example": "Maine Coon",
            "description": "The breed of the Cat"
          }
        },
kamilmysliwiec commented 3 years ago

This is the expected behavior. CLI plugins run as part of the nest build build pipeline and it's an exclusive feature of Nest CLI - plugins won't be automatically applied when ts-node is being used.

Please, use our Discord channel (support) for such questions. We are using GitHub to track bugs, feature requests, and potential improvements.

javiermuniz commented 3 months ago

For anyone that arrived here, like me, and is just looking for a solution, the nest cli has an --entryFile option. This can be used to set the entry file when doing nest start instead of ts-node.

In the exampe repo changing:

"build:doc": "ts-node src/doc.ts",

to:

"build:doc": "nest start --entryFile doc.js",

Should fix the problem.

bhare1987 commented 2 months ago

For anyone that arrived here, like me, and is just looking for a solution, the nest cli has an --entryFile option. This can be used to set the entry file when doing nest start instead of ts-node.

In the exampe repo changing:

"build:doc": "ts-node src/doc.ts",

to:

"build:doc": "nest start --entryFile doc.js",

Should fix the problem.

This has been plaguing me for a while, thank you so much @javiermuniz!!!