Skip to content
On this page

Descriptions

While tsoa can extract a lot of information from your TypeScript type annotations, that can only get us so far in terms of documenting our code. In order stay true to our efforts to avoid code duplication, tsoa uses JSDoc based annotations whenever we want to describe information which is not part of the type system.

TIP

tsoa does not check if you provide descriptions.

We recommend using a linter (we love Spectral) to ensure your specifications aren't just correct, but also contain descriptions and correct examples.

A great example for this are descriptions. You'd most likely agree that endpoint descriptions either in text or markdown are very helpful for consumers get a better sense of an API Endpoint through a short description as part of a rendered documentation. But developers like you also benefit from JSDoc, which is often displayed directly in your editor when hovering over a method you may not be familiar with. Spoiler: tsoa makes both of these things possible.

Endpoint descriptions

One of the most helpful kind of descriptions are method descriptions, or, in HTTP terminology, endpoint descriptions.

ts
@Route("users")
export class UsersController extends Controller {
  /**
   * Retrieves the details of an existing user.
   * Supply the unique user ID from either and receive corresponding user details.
   */
  @Get("{userId}")
  public async getUser(
    @Path() userId: number,
    @Query() name?: string
  ): Promise<User> {
    return new UsersService().get(userId, name);
  }
}

By hovering over the name of the method, we can already see the result in our editor:

Method description

But that's only half of the benefit:

SwaggerUI endpoint descriptions

The OAS reflects this change as well, and so will the documentation rendered from that spec!

Parameter descriptions

But why stop there? JSDoc also offers parameter descriptions, let's see that in action:

ts
@Route("users")
export class UsersController extends Controller {
  /**
   * Retrieves the details of an existing user.
   * Supply the unique user ID from either and receive corresponding user details.
   * @param userId The user's identifier
   * @param name Provide a username to display
   */
  @Get("{userId}")
  public async getUser(
    @Path() userId: number,
    @Query() name?: string
  ): Promise<User> {
    return new UsersService().get(userId, name);
  }
}

Model descriptions

We can also render descriptions at the model level (models are interfaces or classes or type aliases):

ts
/**
 * User objects allow you to associate actions performed
 * in the system with the user that performed them.
 * The User object contains common information across
 * every user in the system regardless of status and role.
 */
export interface User {
  id: number;
  email: string;
  name: string;
  status?: "Happy" | "Sad";
  phoneNumbers: string[];
}

TIP

With the introduction of Type Aliases in tsoa 3, you can use this as a very powerful pattern. Let's assume for a second that our API handles Users identified by a UUID. Usually, uuids are sent as strings, however, ideally, we want to make sure we say uuid when we want uuids. That said, duplicating the description all over the code is a lot of effort, let's see how we can do better:

ts
/**
 * Stringified UUIDv4.
 * See [RFC 4112](https://tools.ietf.org/html/rfc4122)
 * @pattern [0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}
 * @format uuid
 */
export type UUID = string;
We can reuse that in our User
ts
/**
 * User objects allow you to associate actions performed in the system with the user that performed them.
 * The User object contains common information across every user in the system regardless of status and role.
 */
export interface User {
  id: UUID;
  email: string;
  name: string;
  status?: "Happy" | "Sad";
  phoneNumbers: string[];
}

Now, we define UUID as a reusable type alias. Modern editors will nicely display the information text when we hover over references

JSDoc Type Alias

tsoa will translate this to a reusable component that can be referenced every time you use that type alias:

OpenAPI Spec
yaml
components:
  schemas:
    UUID:
      type: string
      description: "Stringified UUIDv4.\nSee [RFC 4112](https://tools.ietf.org/html/rfc4122)"
      pattern: "[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}"
    User:
      description: "User objects allow you to associate actions performed in the system with the user that performed them.\nThe User object contains common information across every user in the system regardless of status and role."
      properties:
        id:
          $ref: "#/components/schemas/UUID"

Which will look like like this when rendered:

Rendered


Property descriptions

WARNING

You may expect to see a description for the id if you set one. However, since it'll be transformed to a reference to the UUID schema, the description must be ignored, since any properties that are placed next to $ref (OpenAPI's mechanism to link to the UUID schema) must be ignored.

For more info, check out the relevant parts of the OpenAPI Specification and JSON Schema Core

ts
/**
 * User objects allow you to associate actions performed in the system with the user that performed them.
 * The User object contains common information across every user in the system regardless of status and role.
 */
export interface User {
  id: UUID;

  /**
   * The email the user used to register his account
   */
  email: string;

  name: string;
  status?: "Happy" | "Sad";
  phoneNumbers: string[];
}

Summaries

tsoa uses short descriptions provided via the JSDoc annotation and will use it as the summary in the OpenAPI doc:

ts
/**
   * A very long, verbose, wordy, long-winded, tedious, verbacious, tautological,
   * profuse, expansive, enthusiastic, redundant, flowery, eloquent, articulate,
   * loquacious, garrulous, chatty, extended, babbling description.
   * @summary A concise summary.
   */
  @Get('SummaryMethod')
  public async summaryMethod(): Promise<TestModel> {
    return new ModelService().getModel();
  }