So, you have written a large OpenAPI spec. At this point, you may have considered dividing it into smaller separate files, but you don't know where to start. While there are a few articles on the internet on how to split an OpenAPI specification, there aren't many practical recommendations on how to structure the whole project logically.

In this article, I break the typical PetStore example into smaller files so that you can apply the same principles to your specification:

  1. Reusing response objects
  2. Reusing parameters objects
  3. Importing definitions from a separate file
  4. Dividing, even more, the specification
  5. Organizing the specification
  6. From many files to one

Note: Are you starting a new documentation project from scratch? At the end of the post, you will find a boilerplate ready to be adapted 😊

1. Reusing response objects

It is easier to start splitting an OpenAPI spec if you are already reusing objects. Imagine that you have documented a couple of endpoints when you notice that you are defining a response object that could be reused in other endpoints.

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: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string

Following this example, if you were to create another endpoint to list pets, you could define a separate Pet object under component/schemas and reuse it in both endpoints.

The keyword $ref does the trick, allowing us the reference other components within the specification.

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

2. Reusing parameters objects

Now that you're comfortable reusing objects in the same file, let's make the petId parameter reusable by following the same approach.

Move the parameter from the path to the components/parameters section and use $ref to import the object. 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

3. Importing definitions from a separate file

Now that you are reusing definitions, the file will be shorter without having started even to split it yet. At this moment, you might already be pleased with the organization of the file, so you could decide to end reading the article here. However, if you want to really split the OpenAPI specification by moving parts of the spec into other files, keep reading!

Do you remember the keyword we have been using to reuse content? $ref does not only permits us to import objects from the same file, but also from other sources.

Create a new file schemas/Pet.yaml for Pet object. Then, move the definition to the new file as follows.

// 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.

4. Dividing, even more, the specification

Until now, we have seen how mainly responses objects and parameters can be reorganized. But if you have several endpoints, you might continue having a huge file.

What we'll do next is to organize the different endpoints into multiple files.

Create a file per path, taking/pets/{petId} path like in the example:

//paths/showPeyById.yaml get: summary: Info for a specific pet operationId: showPetById parameters: - $ref: '#/components/parameters/petId'

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

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

5. Organizing the specification

Let's go one step further. But first, I have to warn you before continuing that:

Warning: This section does not follow the OpenAPI specification recommendations.

It is possible to split even more our project to achieve a better organization, to end having a tiny main file like 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: $ref: "./paths/_index.yaml" components: parameters: $ref: "./parameters/_index.yaml" schemas: $ref: "./schemas/_index.yaml" responses: $ref: "./responses/_index.yaml"

This could be achieved by defining _index.yaml files are containing the definitions for each section. For instance, ./schemas/_index.yaml would look like:

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

The official docs consider this approach as wrong because the $ref keyword should only be used to import objects that support the use of the keyword explicitly, and this is not the case. However, there should not be any issues with other tools that follow the specification strictly because, in the next section, we are compiling all the files back into one.

6. From many files to one

Some of the OpenAPI based tools support just 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 help of a command-line tool.

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 openapi3.yaml --outfile _build/openapi3.yaml --type yaml

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

Putting all together

No one really wants to do a change in a file with +5000 lines of code without tests 🙈. 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. Feel free to reuse the project to define your OpenAPI spec and review how the explanations from this article have been put into practice.

Repository: openapi-boilerplate