---
title: "The Power of OpenAPI: Simplifying API Design and Documentation"
date: 2023-08-18
description: A comprehensive guide to designing, documenting, and validating APIs using the OpenAPI specification.
tags: [architecture]
---

## Introduction

In the rapidly evolving landscape of software development, creating robust and user-friendly APIs has become essential. One tool that has gained immense popularity for designing, documenting, and testing APIs is OpenAPI.

In this comprehensive guide, we'll explore why OpenAPI is so important, how to write an OpenAPI document, and the key sections you need to know. Whether you're a seasoned developer or just getting started, mastering OpenAPI can greatly enhance your API development process.

## Why OpenAPI Matters

OpenAPI, formerly known as Swagger, is an open-standard format for describing APIs. It serves as both a machine-readable and human-friendly documentation that enables developers to understand, visualize, and interact with APIs effortlessly. By defining your API using OpenAPI, you unlock a host of benefits:

- **Clear Documentation**: OpenAPI provides a clear and structured way to document your API, making it easy for both developers and non-developers to understand how to use it.
- **Consistency**: With a standardized format, your API documentation and implementation remain consistent, reducing confusion and enhancing collaboration among teams.
- **Code Generation**: OpenAPI enables automatic code generation for client libraries and server stubs in various programming languages, saving time and effort during development.
- **Testing and Validation**: The OpenAPI specification can be used to validate requests and responses, ensuring that your API adheres to the defined contract.
- **Interactive Documentation**: Tools like [Swagger UI](https://swagger.io/tools/swagger-ui/) and [ReDoc](https://github.com/Redocly/redoc) allow you to create interactive documentation that lets users explore and test your API in real-time.

## Summary for those short on time

OpenAPI is straightforward to write once you understand the high-level structure.

Below is an OpenAPI document to get you going as a basic example\
but read the full post to understand the details.

Also check out the related blog post on [Fastly's dev.to blog](https://dev.to/fastly):\
[Better Fastly API clients with OpenAPI Generator](https://dev.to/fastly/better-fastly-api-clients-with-openapi-generator-3lno)

```yaml
openapi: 3.0.3

info:
  title: Your API
  version: 1.0.0

servers:
  - url: https://api.example.com

paths:
  /teams/{team_id}/members:
    parameters:
      - $ref: "#/components/parameters/team_id"
    get:
      summary: List team members
      description: List all members for the specified team.
      operationId: list-members
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/list_members_response"
              examples:
                body:
                  $ref: "#/components/examples/list_members_response"

components:
  parameters:
    team_id:
      name: team_id
      in: path
      required: true
      style: simple
      schema:
        $ref: "#/components/schemas/team_id"

  schemas:
    team_id:
      type: string
      description: Alphanumeric string identifying a team.
      example: AB1C2defGhijKLMNop3qR

    list_members_response:
      type: array
      description: List of members within the specified team.
      items:
        type: string

  examples:
    list_members_response:
      value:
        - "Andrew"
        - "Bob"
        - "Christine"
```

The above example OpenAPI document describes an API. Specifically it describes:

- The OpenAPI version supported (i.e. `openapi:`)
- Some API Metadata (i.e. `info:`)
- The API address (i.e. `server:`)
- Supported API endpoints (i.e. `paths:`)
- Different 'components' referenced by the `paths:` configuration (i.e.
  `components:`)

In practice the last section `components:` is where most of the 'meat' of the
API configuration happens. You'll see it contains different sections like
`parameters`, `schemas` and `examples`.

The `paths:` section typically doesn't define behaviours _inline_ but instead
will reference objects defined inside of `components:` whenever they need to
describe some behaviour of the endpoint.

Any time you see `$ref` that means we're about to reference an object defined
elsewhere (might be in the same file, under `components:` or it could be from a
separate file).

So in the above example we can see the `paths:` config references a few
different components:

- `#/components/parameters/team_id`: this describes the API path's `team_id` input parameter.
  - This parameter object also references a component (`#/components/schemas/team_id`) for describing the
    team_id.
- `#/components/schemas/list_members_response`: this describes the schema for
  how the response body should look for this API endpoint.
- `#/components/examples/list_members_response`: this demonstrates an example of
  what the schema looks like in practice.

This is the basic _structure_ of an OpenAPI document. Yes, they can become more
complex as the API grows, but at its foundation you will always find this
familiar structure.

OK, we've got the quick "summary" out of the way, let's dig a little deeper...

## Getting Started with OpenAPI

Before diving into the intricacies of writing an OpenAPI document, let's set up the basics.

If at any point throughout this post you are in doubt or you require some additional clarity, then please refer to the [specification document](https://spec.openapis.org/oas/latest.html).

> [!NOTE]
> I've used version [`3.0.3`](https://spec.openapis.org/oas/v3.0.3) for my examples.

### Basic Structure

An OpenAPI document is written in YAML or JSON format (I've used YAML for my examples).

It consists of various sections that collectively describe your API.\
At a high level, here's what the structure looks like:

```yaml
openapi: 3.0.3
info:
  title: Your API
  version: 1.0.0
paths: {}
components: {}
```

The above example is not exhaustive as it only describes three 'objects':

- `info`: [Info Object](https://spec.openapis.org/oas/v3.0.3#info-object)
- `paths`: [Paths Object](https://spec.openapis.org/oas/v3.0.3#paths-object)
- `components`: [Components Object](https://spec.openapis.org/oas/v3.0.3#components-object)

Refer to the [OpenAPI Object](https://spec.openapis.org/oas/v3.0.3#openapi-object) for a complete list of top-level (i.e. root object) fields.

## Defining Endpoints

Now, let's break down the process of defining endpoints in your OpenAPI document.

### Paths and Methods

Endpoints are defined using the `paths` section. Each endpoint is associated with an HTTP method (e.g., GET, POST) and a URL path. Here's an example:

```yaml
paths:
  /users:
    get:
      summary: Get a list of users
      responses:
        '200':
          description: Successful response
```

> [!NOTE]
> **REF:** [Path Item Object](https://spec.openapis.org/oas/v3.0.3#path-item-object).

### Parameters

You can add parameters to your endpoints using the `parameters` section. Parameters can be path parameters, query parameters, headers, and more. Here's an example of a path parameter:

```yaml
paths:
  /users/{userId}:
    parameters:
      - name: userId
        in: path
        required: true
        schema:
          type: integer
```

Possible values for the `in` field are: "query", "header", "path" or "cookie".

> [!NOTE]
> **REF:** [Parameter Object](https://spec.openapis.org/oas/v3.0.3#parameter-object).

## Structuring Data

Defining request and response bodies, along with data types, is crucial for a well-documented API.

### Request and Response Bodies

You can specify request and response bodies using the `requestBody` and `responses` sections. Here's how to define a request body:

```yaml
paths:
  /users:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
```

The use of `$ref` allows us to avoid having an inline schema for one that is defined separately from the path object. This is useful in scenarios where the referenced schema might need to be reused across different paths. We'll take a look at the `components/schemas` section [next](#data-types).

> [!NOTE]
> **REF:** [Operation Object](https://spec.openapis.org/oas/v3.0.3#operationObject) and [Request Body Object](https://spec.openapis.org/oas/v3.0.3#requestBodyObject).

### Data Types

Data types are defined under the `components/schemas` section.\
Here's an example of defining a `User` schema:

```yaml
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
```

The `components` section not only supports defining `schemas` separate from where they should be referenced but also `responses`, `parameters`, `examples` and more. We'll take a look at some of these fields in more detail later.

> [!NOTE]
> **REF:** [Components Object](https://spec.openapis.org/oas/v3.0.3#components-object).

## Adding Metadata

Enhance your API documentation by adding metadata and grouping related endpoints.

### API Information

The `info` section provides high-level information about your API, such as title, version, and description:

```yaml
info:
  title: Your API
  version: 1.0.0
  description: This is a sample API documentation.
```

> [!NOTE]
> **REF:** [Info Object](https://spec.openapis.org/oas/v3.0.3#info-object).

### Tags and Grouping

You can group related endpoints using tags (typically added within the [Operation Object](https://spec.openapis.org/oas/v3.0.3#operation-object)):

```yaml
tags:
  - name: Users
    description: Operations related to users
```

But tags can also be defined at the top-level (i.e. root object).

For example, [Fastly](https://www.fastly.com/) uses the following conventions for its tags which determine how endpoints are documented on Fastly's [Developer Hub](https://developer.fastly.com/reference/api/) (DevHub):

```yaml
tags:
  - name: unlisted # Publish on DevHub at an unlisted URL and exclude from search results
  - name: excludeFromSearch # Publish on DevHub but exclude from search results
  - name: internal  # Do not publish on DevHub or build into API clients
  - name: beta # Display "beta" notice on DevHub
  - name: limited-availability # Display "LA" notice on DevHub
```

> [!NOTE]
> **REF:** [Tag Object](https://spec.openapis.org/oas/v3.0.3#tagObject).

## Handling Errors

Communicating errors is crucial in API design, and OpenAPI helps you to define your error responses.

### Status Codes

Specify status codes and their meanings in your `responses` section:

```yaml
paths:
  /users/{userId}:
    get:
      responses:
        '200':
          description: Successful response
        '404':
          description: User not found
```

The `responses` field is a container for the expected responses of an operation. The container maps a HTTP response code (e.g. `200` or `404` etc) to the expected response.

> [!NOTE]
> **REF:** [Responses Object](https://spec.openapis.org/oas/v3.0.3#responsesObject).

### Error Responses

You can also define error responses with detailed information, and for specific response types:

```yaml
responses:
  '400':
    description: Invalid Credit
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Error'
```

In the above example we describe a `400 Bad Request` error response that will have the `Content-Type` of `application/json` (i.e. the response will use JSON) and we reference an external schema.

Below is an example schema definition that uses the popular "Problem Details" format from [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807):

```yaml
components:
  schemas:
    Error:
      type: object
      properties:
        type:
          type: string
          description: A URI reference that identifies the problem type.
          example: "https://example.com/probs/out-of-credit"
        title:
          type: string
          description: A short, human-readable summary of the problem.
          example: "You do not have enough credit."
        status:
          type: integer
          description: The HTTP status code generated by the origin server for this occurrence of the problem.
          example: 400
        detail:
          type: string
          description: A human-readable explanation specific to this occurrence of the problem.
          example: "Your current balance is 30, but that costs 50."
        instance:
          type: string
          description: A URI reference that identifies the specific occurrence of the problem.
          example: "/account/12345/msgs/abc"
      required:
        - type
        - title
        - status
```

What this schema describes is the following example error JSON that a user might see:

```json
 {
     "type": "https://example.com/probs/out-of-credit",
     "title": "You do not have enough credit.",
     "detail": "Your current balance is 30, but that costs 50.",
     "instance": "/account/12345/msgs/abc",
     "balance": 30,
     "accounts": ["/account/12345", "/account/67890"]
 }
```

> [!NOTE]
> **REF:** [Response Object](https://spec.openapis.org/oas/v3.0.3#responseObject).

## Testing and Validation

Ensure the reliability of your API by testing and validating it using OpenAPI tools.

### Tools for Validation

OpenAPI supports validating your API against the defined schemas using a variety of tools, such as [Swagger Inspector](https://swagger.io/tools/swagger-inspector/) and [Spectral](https://stoplight.io/open-source/spectral).

### Generating Client SDKs

Using OpenAPI also helps with generating (and maintaining) client SDKs by using tools like [Swagger Codegen](https://swagger.io/tools/swagger-codegen/) and [OpenAPI-Generator](https://openapi-generator.tech/) to accelerate development for various programming languages.

I've written about this process on the [Fastly dev.to blog](https://dev.to/fastly):\
[Better Fastly API clients with OpenAPI Generator](https://dev.to/fastly/better-fastly-api-clients-with-openapi-generator-3lno)

## Conclusion

Embracing OpenAPI as a core component of your API development process can lead to more efficient, well-documented, and collaborative projects.

By understanding the key sections of an OpenAPI document and leveraging its capabilities, you can create APIs that are not only robust but also user-friendly.

Whether you're a solo developer or part of a large team, OpenAPI is a powerful tool that simplifies API design and documentation, ultimately contributing to a better developer experience.

In this brief guide, I've explored the importance of OpenAPI in API development, discussed its benefits, and walked you through the process of writing an OpenAPI document.

By breaking down the essential sections and providing practical examples, I hope you feel empowered to leverage OpenAPI for your next API project. Happy API designing!
