# or
yarn run tsoa spec --yaml
```

Doing this will create a `build/openapi.json` or `build/openapi.yaml` document containing the OpenAPI description for the API. The YAML version is easier to read, as shown below.

```yaml filename="build/openapi.yaml"
openapi: 3.1.0
info:
  title: speakeasy-tsoa-example
  version: 2.0.0
  description: Speakeasy Train Travel tsoa API
  license:
    name: Apache-2.0
  contact:
    name: "Speakeasy Support"
    email: support@speakeasy.com
components:
  schemas:
    Booking:
      properties:
        id:
          type: string
          description: Unique identifier for the booking.
          example: 3f3e3e1-c824-4d63-b37a-d8d698862f1d
          format: uuid
        trip_id:
          type: string
          description: Identifier of the booked trip.
          example: 4f4e4e1-c824-4d63-b37a-d8d698862f1d
          format: uuid
        passenger_name:
          type: string
          description: Name of the passenger.
          example: John Doe
        has_bicycle:
          type: boolean
          description: Indicates whether the passenger has a bicycle.
          example: true
          default: false
        has_dog:
          type: boolean
          description: Indicates whether the passenger has a dog.
          example: false
          default: false
      required:
        - id
        - trip_id
        - passenger_name
      type: object
      additionalProperties: false
paths:
  /bookings:
    get:
      operationId: ListBookings
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                items:
                  $ref: "#/components/schemas/Booking"
                type: array
      tags:
        - Bookings
      security: []
      parameters:
        - in: query
          name: page
          required: false
          schema:
            format: double
            type: number
        - in: query
          name: limit
          required: false
          schema:
            format: double
            type: number
    post:
      operationId: CreateBooking
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"
      tags:
        - Bookings
      security: []
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Omit_Booking.id_"

```

Not a bad start, but this can be improved by learning more about the decorators and options available in tsoa.

### Step 7: Improving the OpenAPI Output

Improvements may well be an ongoing process, but the first and most important step is to make sure the TypeScript types representing "models" in the application are well defined. Adding comments to the properties of interfaces and types will help tsoa generate better descriptions in the OpenAPI document. Using a combination of JSDoc comments and tsoa decorators, developers can provide additional metadata for each property, such as examples, formats, and constraints.

```typescript filename="src/app/models/booking.ts"
export interface Booking {
  /**
   * Unique identifier for the booking.
   * @format uuid
   * @example "3f3e3e1-c824-4d63-b37a-d8d698862f1d"
   * @readonly
   */
  id: string;

  /**
   * Identifier of the booked trip.
   * @format uuid
   * @example "4f4e4e1-c824-4d63-b37a-d8d698862f1d"
   */
  trip_id: string;

  /**
   * Name of the passenger.
   * @example "John Doe"
   */
  passenger_name: string;

  /**
   * Indicates whether the passenger has a bicycle.
   * @default false
   * @example true
   */
  has_bicycle?: boolean;

  /**
   * Indicates whether the passenger has a dog.
   * @default false
   * @example false
   */
  has_dog?: boolean;
}
```

Adding this extra context is not just beneficial for generating a more informative OpenAPI document, but it will power real runtime functionality too. Types will be checked and validated at runtime, preventing invalid requests getting anywhere near the application logic. 

![](/assets/openapi/tsoa/tsoa-type-checking.png)

All sorts of things can happen with this extra metadata:

- Types will be checked and validated at runtime.
- Additional properties in JSON will trigger validation errors.
- Default values will actually be used when creating resources.
- Any `@readonly` properties will be ignored when sent from request bodies.

Anyone up for a challenge can even add regex patterns to string properties using the `@pattern` decorator.

```ts
  /**
   * Identifier of the booked trip.
   * @format uuid
   * @example "4f4e4e1-c824-4d63-b37a-d8d698862f1d"
   * @pattern ^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$
   */
  trip_id: string;
```

![](/assets/openapi/tsoa/tsoa-regex-validation.png)

Tweak models like this and regenerate the OpenAPI to see the improvements reflected in the output.

### (Optional) Step 8: Add a /docs endpoint to Serve OpenAPI Documentation

To make it easier to explore and share API documentation, add a `/docs` endpoint to the Express application that serves the OpenAPI document.

```ts filename="src/app.ts" focus=5:17
export const app = express();

// ... other middleware and route registrations

// Serve the OpenAPI spec
app.use("/openapi.json", (req: ExRequest, res: ExResponse) => {
  res.sendFile("openapi.json", { root: __dirname + "/../build" });
});

// Serve API reference documentation using dynamic import (ESM-only package)
(async () => {
  const { apiReference } = await import("@scalar/express-api-reference");
  app.use("/docs", apiReference({ url: "/openapi.json" }));
})();
```

## Improving OpenAPI output further

There are many more ways to improve the OpenAPI output generated by tsoa:

### Set OpenAPI info section

By default tsoa will take values from `package.json` to popular the `info` section of the OpenAPI document, but the team maintaining the codebase might not the best point of contact to help public/external API consumers.

To manually configure the OpenAPI `info` section for contact and any other values, configure pop them in the "spec" portion of the `tsoa.json` file:

```json filename="tsoa.json" focus=8:15
{
  "entryFile": "src/app.ts",
  "noImplicitAdditionalProperties": "throw-on-extras",
  "controllerPathGlobs": ["src/app/*Controller.ts"],
  "spec": {
    "outputDirectory": "build",
    "specFileBaseName": "openapi",
    "specVersion": 3.1,
    "name": "Custom API Name",
    "description": "Custom API Description",
    "license": "MIT",
    "version": "1.1.0",
    "contact": {
      "name": "API Contact",
      "email": "help@example.com",
      "url": "http://example.com"
    }
  }
}
```

### Reusable component schemas

This section shows how to help tsoa generate separate and reusable component schemas for a request body.

Consider the following Trip model:

```typescript filename="src/app/models/trip.ts"
export interface Trip {
  /**
   * Unique identifier for the trip.
   * @format uuid
   * @example "ea399ba1-6d95-433f-92d1-83f67b775594"
   */
  id: string;

  /**
   * The origin station ID.
   * @format uuid
   * @example "efdbb9d1-02c2-4bc3-afb7-6788d8782b1e"
   */
  origin: string;

  /**
   * The destination station ID.
   * @format uuid
   * @example "b2e783e1-c824-4d63-b37a-d8d698862f1d"
   */
  destination: string;

  /**
   * Departure time in ISO 8601 format.
   * @format date-time
   * @example "2024-02-01T10:00:00Z"
   */
  departure_time: string;

  /**
   * Arrival time in ISO 8601 format.
   * @format date-time
   * @example "2024-02-01T16:00:00Z"
   */
  arrival_time: string;

  /**
   * The operator running the trip.
   * @example "Deutsche Bahn"
   */
  operator: string;

  /**
   * The cost of the trip.
   * @example 50
   */
  price: number;

  /**
   * Indicates whether bicycles are allowed on the trip.
   * @default false
   * @example true
   */
  bicycles_allowed?: boolean;

  /**
   * Indicates whether dogs are allowed on the trip.
   * @default false
   * @example true
   */
  dogs_allowed?: boolean;
}
```

The goal is to write a controller that updates the `operator` and `price` fields. The controller should take both fields as body parameters.

The example controller below is a starting point. Note how the body parameters `operator` and `price` are defined by passing the `@BodyProp` decorator to the controller function multiple times.

```typescript filename="src/app/tripsController.ts" mark=6:7
@Route("trips")
export class TripsController extends Controller {
  @Put("{tripId}")
  public async updateTrip(
    @Path() tripId: string,
    @BodyProp() operator?: string,
    @BodyProp() price?: number
  ): Promise<Trip> {
    const trip = new TripsService().updateTrip(
      tripId,
      operator,
      price
    );

    return trip;
  }
}
```

This would generate inline parameters without documentation for the `UpdateTrip` operation in OpenAPI, as shown in the snippet below:

```yaml filename="build/openapi.yaml"
requestBody:
  required: true
  content:
    application/json:
      schema:
        properties:
          operator:
            type: string
          price:
            type: number
        type: object
```

While perfectly valid, this schema is not reusable and excludes the documentation and examples from our model definition.

It is recommended to pick fields from the model interface directly and export a new interface. The TypeScript utility types `Pick` and `Partial` can be used to pick the `operator` and `price` fields and make both optional:

```typescript filename="src/app/tripsService.ts"
export interface TripUpdateParams
  extends Partial<Pick<Trip, "operator" | "price">> {}
```

In the controller, `TripUpdateParams` can now be used as follows:

```typescript filename="src/app/tripsController.ts" mark=6
@Route("trips")
export class TripsController extends Controller {
  @Put("{tripId}")
  public async updateTrip(
    @Path() tripId: string,
    @Body() requestBody: TripUpdateParams
  ): Promise<Trip> {
    const trip = new TripsService().updateTrip(tripId, requestBody);

    return trip;
  }
}
```

### Customizing OpenAPI operationId Using tsoa

When generating an OpenAPI spec, tsoa adds an `operationId` to each operation.

The `operationId` can be customized in three ways:

- Using the `@OperationId` decorator.
- Using the default tsoa `operationId` generator.
- Creating a custom `operationId` template.

#### Using the @OperationId decorator

The most straightforward way to customize the `operationId` is to add the `@OperationId` decorator to each operation.

In the example below, the custom `operationId` is `updateTripDetails`:

```typescript filename="src/app/tripsController.ts" mark=7
@Route("trips")
export class TripsController extends Controller {
  @OperationId("updateTripDetails")
  @Put("{tripId}")
  public async updateTrip(
    @Path() tripId: string,
    @Body() requestBody: TripUpdateParams
  ): Promise<Trip> {
    const trip = new TripsService().updateTrip(tripId, requestBody);

    return trip;
  }
}
```

#### Using the default operationId generator

If a controller method is not decorated with the `OperationId` decorator, tsoa generates the `operationId` by converting the method name to title case using the following Handlebars template:

```handlebars
{{titleCase method.name}}
```

#### Creating a custom operationId template

To create a custom `operationId` for all operations without the `@OperationId` decorator, tsoa allows a Handlebars template to be specified in `tsoa.json`. tsoa adds two helpers to Handlebars: `replace` and `titleCase`. The method object and controller name get passed to the template as `method` and `controllerName`.

The following custom `operationId` template prepends the controller name and removes underscores from the method name:

```json filename="tsoa.json"
{
  "spec": {
    "operationIdTemplate": "{{controllerName}}-{{replace method.name '_' ''}}"
  }
}
```

### Add Speakeasy extensions to methods

Sometimes OpenAPI's vocabulary is insufficient for certain generation needs. For these situations, Speakeasy provides a set of OpenAPI extensions. For example, an SDK method may need a name different from the `OperationId`. To cover this use case, Speakeasy provides an `x-speakeasy-name-override` extension.

To add these custom extensions to an OpenAPI spec, it is possible to make use of tsoa's `@Extension()` decorator:

```typescript filename="src/app/tripsController.ts" mark=5
@Route("trips")
export class TripsController extends Controller {
  @OperationId("updateTripDetails")
	@Extension({"x-speakeasy-name-override":"update"})
  @Put("{tripId}")
  public async updateTrip(
    @Path() tripId: string,
    @Body() requestBody: TripUpdateParams
  ): Promise<Trip> {
    const trip = new TripsService().updateTrip(tripId, requestBody);

    return trip;
  }
}
```

## Add retries to Speakeasy SDKs

Speakeasy can generate SDKs that follow custom rules for retrying failed requests. For instance, if a server fails to return a response within a specified time, it may be desirable for client applications to retry their request without clobbering the server.

Add retries to SDKs generated by Speakeasy by adding a top-level `x-speakeasy-retries` schema to your OpenAPI spec. You can also override the retry strategy per operation by adding `x-speakeasy-retries`.

### Adding Global Retries

To add a top-level retries extension to your OpenAPI spec, add a new `spec` schema to the `spec` configuration in `tsoa.json`:

```json filename="tsoa.json"
{
  "spec": {
    "spec": {
      "x-speakeasy-retries": {
        "strategy": "backoff",
        "backoff": {
          "initialInterval": 500,
          "maxInterval": 60000,
          "maxElapsedTime": 3600000,
          "exponent": 1.5
        },
        "statusCodes": ["5XX"],
        "retryConnectionErrors": true
      }
    }
  }
}
```

### Adding retries per method

To add retries to individual methods, use the tsoa `@Extension` decorator.

In the example below, we add `x-speakeasy-retries` to the `updateTrip` method:

```typescript filename="src/app/tripsController.ts" mark=4:14
@Route("trips")
export class TripsController extends Controller {
  @Put("{tripId}")
  @Extension("x-speakeasy-retries", {
    strategy: "backoff",
    backoff: {
      initialInterval: 500,
      maxInterval: 60000,
      maxElapsedTime: 3600000,
      exponent: 1.5,
    },
    statusCodes: ["5XX"],
    retryConnectionErrors: true,
  })
  public async updateTrip(
    @Path() tripId: string,
    @Body() requestBody: TripUpdateParams,
  ): Promise<Trip> {
    const trip = new TripsService().updateTrip(tripId, requestBody);

    return trip;
  }
}
```

## Generate an SDK based on the OpenAPI output

Once an OpenAPI spec is available, use Speakeasy to generate an SDK by calling the following in the terminal:

```bash
speakeasy quickstart
```

Follow the onscreen prompts to provide the necessary configuration details for the new SDK such as the name, schema location and output path. Enter `build/openapi.json` when prompted for the OpenAPI document location and select TypeScript when prompted for which language should be generated.

SDKs can be generated using Speakeasy whenever the API definition in tsoa changes. Many Speakeasy users [add SDK generation to their CI workflows](/docs/workflow-reference) to ensure SDKs are always up to date.

## Summary

This guide explored how to use tsoa to automatically generate OpenAPI specifications from TypeScript applications. It covered setting up a new TypeScript project, defining models, creating a service layer and controllers, and generating the OpenAPI document. It also discussed ways to improve the OpenAPI output using decorators and comments, as well as adding Speakeasy extensions for SDK generation.