So, you have written a large OpenAPI spec. At this point, you may have considered making the document modular by dividing it into smaller separate files.

In this article, we will break the Petstore example from the official OpenAPI documentation into smaller files.

This guide will be more helpful if you have documented an API project before following the OpenAPI specification since you can apply the actions described to guide the organization of your project. Nevertheless, if you are starting a new OpenAPI Specification project from scratch, you will find a skeleton project ready to be adapted at the end of the post 😊

Prerequisites

This guide assumes that you are familiar with the OpenAPI Specification 3.0 (previously known as Swagger). If you haven't worked with the standard, I recommend you read first What is OpenAPI?. Then, try to write your first OpenAPI document.

Step 1 - Reusing responses

It is easier to start splitting an OpenAPI document if you are already reusing schemas.

Imagine that you have to document two endpoints: one to retrieve a group of pets, and a second to retrieve a single pet. Suddenly, you notice that you would need to define twice the response object Pet.

paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: A paged array of pets [...] content: application/json: schema: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string

Following this example, you could define a separate schema named Pet under the section component/schemas, and reuse the object in both endpoints.

The keyword $ref does the trick. The keyword allows us to reference other definitions within the same document.

Here's how the definition looks like using $ref:

paths: /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "#/components/schemas/Pet" components: schemas: Pet: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string

Step 2 - Reusing parameters

Now that you're comfortable reusing schemas in the same file let's reuse the parameter petId from the operation showPetById.

As in the previous step, move the parameter from the path to the components/parameters section and use $ref to reference the component. Here is an example:

paths: /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById parameters: - $ref: '#/components/parameters/petId' [...] components: parameters: petId: name: petId in: path required: true description: The id of the pet to retrieve schema: type: string

Step 3 - Importing definitions from a separate file

Now that you are reusing definitions, the file should be shorter without even starting to split the file. If you are already pleased with the file organization, you could decide to end reading the article here. However, if you want to make it more modular, keep reading!

Do you remember the keyword we have been using to reuse content? $ref not only allows us to import objects from the same file but from other sources like a separate file or a remote URL.

Create a new file named schemas/Pet.yaml for the Pet schema. Then, move the definition to the new file. Here's how the resulting file should look like:

// schemas/Pet.yaml type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string

Now, point $ref to the new file's location.

// openapi.yaml $ref: "./schemas/Pet.yaml"

Finally, do the same for the other objects that you might want to split into separate files.

Step 4 - Dividing, even more, the specification

Until now, we have seen how to organize response objects and parameters. However, if the document defines several endpoints, you might continue having a large file that's challenging to maintain.

Next, we are going to organize the resource paths into multiple files.

For example, you could create the file path/pet.yaml. This file should define all the available operations, which their associated parameters and responses for the endpoint/pets/{petId}:

//paths/pets.yaml get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - $ref: "../parameters/path/petId.yaml" responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "../schemas/Pet.yaml" default: $ref: "../responses/UnexpectedError.yaml"

Then, import each path definition from the main OpenAPI document as we have been doing with the schemas and parameters.

//openapi.yaml ... paths: /pets/{petId}: $ref: "./paths/pet.yaml

Finally, repeat the process for every other resource you want to import from a separate file.

Step 5 - Organizing the specification

Let's go one step further! We can split even more the project to achieve a better organization. Our goal is to end having a main OpenAPI document as tiny as the following one:

// openapi.yaml openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore description: Multi-file boilerplate for OpenAPI Specification. license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets: $ref: "./paths/pets.yaml" /pets/{petId}: $ref: "./paths/pet.yaml" components: parameters: $ref: "./parameters/_index.yaml" schemas: $ref: "./schemas/_index.yaml" responses: $ref: "./responses/_index.yaml"

To achieve so, create the following _index.yaml files:

  • parameters/_index.yaml
  • schemas/_index.yaml
  • responses/_index.yaml

Then, move to every file its definitions. For instance, ./schemas/_index.yaml for the Petstore example would look like:

Pet: $ref: "./Pet.yaml" Pets: $ref: "./Pets.yaml" Error: $ref: "./Error.yaml"

Step 6 - From many files to one

Some of the OpenAPI based tools support only a single file as an input. To continue using the spec with those tools, we will compile all the different files we have created with the command-line tool swagger-cli.

1. Open a new terminal. Then, install the package swagger-cli globally:

npm install -g swagger-cli

2. Run the command to merge all the files into one:

swagger-cli bundle openapi.yaml --outfile _build/openapi.yaml --type yaml

3. If everything goes well, you should see a single OpenAPI file compiled under the _build directory.

Putting all together

By splitting a large OpenAPI spec into multiple files, your project becomes much more maintainable and the documentation journey enjoyable. In my case, I have also noticed that other developers are more willing to contribute and propose changes to the document when this is properly organized.

As promised, I'm sharing with you a repository with the PetStore example divided into multiple files.

Repository: openapi-boilerplate

Feel free to reuse the project to define your OpenAPI document and review how the explanations from this article have been put into practice. If you succeed, please let me know about it at @dgarcia360 and share the article if it helped you out!