Adonis AutoSwagger

repository·main·Indexed 41 days ago

https://github.com/ad-on-is/adonis-autoswagger

A tool for automatically generating OpenAPI (Swagger) documentation for AdonisJS applications. It scans routes, models, interfaces, and validators to build API specifications, supporting AdonisJS v5 and v6. It provides built-in support for rendering documentation via Swagger-UI, Scalar, RapiDoc, and Stoplight Elements, and allows for detailed API customization using JSDoc-style annotations in controllers and models.

Tokens
8.1K
Snippets
25
Records
31
Agent score
52%

What's inside Adonis AutoSwagger

  1. Extend Controller documentation with comments

    main

    You can add rich metadata to your controller actions using specific comment tags. This allows you to define summaries, tags, descriptions, and complex response/request structures.

    Core Tags:

    • @summary <text>: A short summary of the action.
    • @tag <text>: A custom tag for grouping.
    • @description <text>: A detailed description.
    • @operationId <id>: A unique identifier for the operation.

    Response & Request Tags:

    • @responseBody <status> - <return> - <description>: Defines response bodies. <return> can be a <Schema>, <Schema[]>, or custom JSON {}.
    • @requestBody <body_definition>: Defines the expected request body (e.g., <Model> or <Validator>).
    • @requestFormDataBody <body_definition>: Defines request body for formData format.
    • @paramPath <name> - <desc> - (meta): Defines path parameters.
    • @paramQuery <name> - <desc> - (meta): Defines query parameters.
    • @responseHeader <status> - <name> - <desc> - <meta>: Defines response headers.
  2. Configure Schemas: Models, Interfaces, and Enums

    main

    AutoSwagger automatically generates schemas from your application structure:

    • Models: Automatically generates schema descriptions based on your AdonisJS models. To handle soft deletes, use compose(BaseModel, SoftDeletes) or add the @swagger-softdeletes annotation to the model.
    • Interfaces: You can use custom interfaces in your controller parameters (e.g., param: UserDetails). These interfaces must be located in the app/Interfaces/ directory.
    • Enums: Enums defined in app/Types/ are automatically detected and added to the schema. To use an enum in a response body example, use the .append() syntax: @responseBody 200 - <Model>.with(relations).append(enumFieldExample)
  3. Ensure Controller detection with lazy loading

    main

    For AutoSwagger to correctly detect controllers, you must load them lazily in your routes file. Do not use standard static imports for controllers.

    // ✅ Correct: Lazy loading
    const TestController = () => import('#controllers/test_controller')
    
    // ❌ Incorrect: Static import
    import TestController from '#controllers/test_controller'
  4. Annotate Controller methods for Swagger documentation

    main

    Use JSDoc-style comments directly above your controller methods to define API operations.

    Common Annotations:

    • @index, @show, @update, @create: Define the operation type.
    • @operationId <id>: Sets a unique operation ID.
    • @description <text>: Provides a description of the endpoint.
    • @summary <text>: Provides a short summary.
    • @paramPath <name> - <desc> - @type(<type>) @required: Defines a path parameter. Use @type(string), @type(number), etc.
    • @paramQuery <name> - <desc> - @type(<type>) @required: Defines a query parameter.
    • @responseBody <status> - <schema>: Defines the response body for a specific HTTP status code. You can use model names like <Product> or include relations like <Product>.with(relations).
    • @responseHeader <status> - @use(<commonKey>): Reuses a header definition from the common config section.
    • @responseHeader <status> - <name> - <desc> - @example(<val>): Defines a custom response header.
    • @requestBody <schema>: Defines the expected request body.
    • @paramUse(<commonKey1>, <commonKey2>): Reuses common parameters defined in the config.
    • @myCustomFunction: You can use custom names for operations if needed.
    export default class SomeController {
      /**
       * @index
       * @operationId getProducts
       * @description Returns array of producs and it's relations
       * @responseBody 200 - <Product[]>.with(relations)
       * @paramUse(sortable, filterable)
       * @responseHeader 200 - @use(paginated)
       * @responseHeader 200 - X-pages - A description of the header - @example(test)
       */
      public async index({ request, response }: HttpContextContract) {}
    
      /**
       * @show
       * @paramPath id - Describe the path param - @type(string) @required
       * @paramQuery foo - Describe the query param - @type(string) @required
       * @description Returns a product with it's relation on user and user relations
       * @responseBody 200 - <Product>.with(user, user.relations)
       * @responseBody 404
       */
      public async show({ request, response }: HttpContextContract) {}
    }
  5. Use AutoSwagger in AdonisJS v6

    main

    In AdonisJS v6, import AutoSwagger and use its methods within your routes.ts file. You can serve the YAML specification and render different UI engines like Swagger-UI, Scalar, or RapiDoc.

    import AutoSwagger from "adonis-autoswagger";
    import swagger from "#config/swagger";
    
    // returns swagger in YAML
    router.get("/swagger", async () => {
      return AutoSwagger.default.docs(router.toJSON(), swagger);
    });
    
    // Renders Swagger-UI and passes YAML-output of /swagger
    router.get("/docs", async () => {
      return AutoSwagger.default.ui("/swagger", swagger);
      // Alternatives:
      // return AutoSwagger.default.scalar("/swagger");
      // return AutoSwagger.default.rapidoc("/swagger", "view");
    });
  6. Extend Model property documentation with annotations

    main

    You can add specific Swagger metadata to your Model properties using comments. Important: These comments MUST be placed exactly 1 line above the property.

    Available Annotations:

    • @no-swagger: Prevents the field from being included in the schema (useful for fields where serializeAs: null isn't enough).
    • @enum(val1, val2, ...): Defines an enum for the field (e.g., @enum(pending, active, deleted)).
    • @format(type): Specifies a data format (e.g., uuid, email, binary).
    • @example(value): Provides a specific example value for the field.
    • @props({"key": "value"}): Adds additional JSON properties to the field (e.g., @props({"minLength": 10})).
    • @required: Marks the field as required.
    // SomeModel.ts
    
    @column()
    // @enum(pending, active, deleted)
    public status: string
    
    @column()
    // @example(johndoe@example.com)
    public email: string
    
    @column()
    // @props({"minLength": 10})
    public age: number
    
    @hasMany(() => ProductView)
    // @no-swagger
    public views: HasMany<typeof ProductView>
  7. Generate Swagger docs in a Production environment

    main

    To generate documentation in production or CI/CD pipelines, follow these steps:

    1. Create a Docs Generation Command: Create a new Ace command (e.g., commands/DocsGenerate.ts) using the provided examples from the repository (DocsGenerate.ts.example or DocsGeneratev6.ts.example).

    2. Update Environment Configuration: Modify /start/env.ts to allow the DocsGenerate command to run without manual environment variable injection in CI/CD:

    const isNodeAce = process.argv.some(
      (arg) => arg.endsWith("/ace") || arg === "ace"
    );
    
    export default await Env.create(
      new URL("../", import.meta.url),
      isNodeAce
        ? {}
        : {
            NODE_ENV: Env.schema.enum(["development", "production", "test"] as const),
            PORT: Env.schema.number(),
          }
    );
    1. Execute Generation: Run the following commands to generate the file and move it to your build folder:
    node ace docs:generate
    node ace build --production
    cp swagger.yml build/

    Note: Ensure NODE_ENV=production is set in your environment, or match the value set in your options.productionEnv configuration.

  8. Use AutoSwagger in AdonisJS v5

    main

    In AdonisJS v5, use the Route object and the AutoSwagger package to serve the YAML specification and the Swagger-UI.

    import AutoSwagger from "adonis-autoswagger";
    import swagger from "Config/swagger";
    
    // returns swagger in YAML
    Route.get("/swagger", async () => {
      return AutoSwagger.docs(Route.toJSON(), swagger);
    });
    
    // Renders Swagger-UI and passes YAML-output of /swagger
    Route.get("/docs", async () => {
      return AutoSwagger.ui("/swagger", swagger);
    });
  9. Configure the swagger configuration file

    main

    The config/swagger.ts file allows you to customize the generated documentation. Key configuration options include:

    • path: The directory where the output should be placed.
    • title: The title of your API documentation.
    • version: The version of your API.
    • tagIndex: The index used for grouping tags.
    • ignore: An array of path patterns (e.g., ['/swagger', '/docs', '/v1']) to exclude from the documentation.
    • common: An object used to define shared parameters and headers that can be reused across multiple endpoints using the @use annotation.
    export default {
      path: __dirname + "../",
      title: "YourProject",
      version: "1.0.0",
      tagIndex: 2,
      ignore: ["/swagger", "/docs", "/v1", "/", "/something/*", "*/something"],
      common: {
        parameters: {
          sortable: [
            {
              in: "query",
              name: "sortBy",
              schema: { type: "string", example: "foo" },
            },
            {
              in: "query",
              name: "sortType",
              schema: { type: "string", example: "ASC" },
            },
          ],
        },
        headers: {
          paginated: {
            "X-Total-Pages": {
              description: "Total amount of pages",
              schema: { type: "integer", example: 5 },
            },
            "X-Total": {
              description: "Total amount of results",
              schema: { type: "integer", example: 100 },
            },
            "X-Per-Page": {
              description: "Results per page",
              schema: { type: "integer", example: 20 },
            },
          },
        },
      },
    };
  10. Understand the AdonisRoute and AdonisRouteMeta structures

    main

    The library uses AdonisRoute and AdonisRouteMeta to represent the internal structure of AdonisJS routes. This is used to map your application's routing logic to the Swagger specification.

    • AdonisRoute: Represents a single route, including its methods, pattern, params, and handler.
    • AdonisRouteMeta: Contains details about the resolvedHandler (type, namespace, method) and the resolvedMiddleware applied to that route.
    export interface AdonisRouteMeta {
      resolvedHandler: {
        type: string;
        namespace?: string;
        method?: string;
      };
      resolvedMiddleware: Array<{ 
        type: string; 
        args?: any[]; 
      }>;
    }
    
    export interface AdonisRoute {
      methods: string[];
      pattern: string;
      meta: AdonisRouteMeta;
      middleware: string[] | any;
      name?: string;
      params: string[];
      handler?: string | v6Handler;
    }
  11. Configure parameter metadata with @param

    main

    When using @param, you can provide additional metadata in a hyphen-separated format: name - description - metadata.

    Metadata options:

    • @required: Marks the parameter as required.
    • @type <type>: Overrides the inferred type (e.g., string, integer, float).
    • @example <value>: Sets a specific example value.
    • @enum <val1,val2>: Defines an enumeration of allowed values.
    • @paramUse <common_key>: Instead of defining a parameter manually, reuse a common parameter defined in your global configuration using this.options.common.parameters.
    • @paramPath or @paramQuery: Sets the parameter location and marks it as not required.
    /**
     * @paramQuery limit - Number of items to return - @type integer @example 10
     * @paramPath userId - The ID of the user @required
     */