Posted on 9 mins read

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.

Table of Contents

  1. Why OpenAPI Matters
  2. Summary for those short on time
  3. Getting Started with OpenAPI
  4. Defining Endpoints
  5. Structuring Data
  6. Adding Metadata
  7. Handling Errors
  8. Testing and Validation
  9. Conclusion

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 and 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:
Better Fastly API clients with OpenAPI Generator

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.

NOTE: I’ve used version 3.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:

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’:

Refer to the 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:

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

REF: 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:

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

REF: 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:

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.

REF: Operation Object and Request Body Object.

Data Types

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

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.

REF: 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:

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

REF: Info Object.

Tags and Grouping

You can group related endpoints using tags (typically added within the Operation Object):

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 uses the following conventions for its tags which determine how endpoints are documented on Fastly’s Developer Hub (DevHub):

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

REF: Tag Object.

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:

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.

REF: Responses Object.

Error Responses

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

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:

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:

 {
     "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"]
 }

REF: Response Object.

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 and Spectral.

Generating Client SDKs

Using OpenAPI also helps with generating (and maintaining) client SDKs by using tools like Swagger Codegen and OpenAPI-Generator to accelerate development for various programming languages.

I’ve written about this process on the Fastly dev.to blog:
Better Fastly API clients with OpenAPI Generator

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!


But before we wrap up... time (once again) for some self-promotion 🙊