OpenAPI Tutorial Part II: Common API Example

Does This Look Familiar?

Posted on Feb 05, 2022
api, openapi, webdev, tutorial, swagger

This is Part II of a multi-part series. Below are the links to other parts of this tutorial!

In the first part of this series we

  • lightly touched on the differences between Swagger and OpenAPI
  • mentioned some things we can do with an OpenAPI definitions file
  • recommended tools for
    • definitions file generation
    • documentation generation
    • SDK generation
  • decided to implement an API similar to the common Pet Store API
  • created annotations for generating an almost empty definitions file
  • viewed our empty documentation via Redoc

Today, I want to begin with an already created, simple API. The purpose of this tutorial series is not to show you how to build an API from scratch, but rather to learn how you can take your existing API and use OpenAPI to make the whole process better.

jtreminio/openapi-tutorial

I want to introduce you to my repo, jtreminio/openapi-tutorial. I will use this repo to follow along with the progress made in this tutorial series.

This repo contains a (very) basic API example and I highly suggest you clone it locally to follow along. For this article we will begin with branch part-ii.

1
2
3
4
$ git clone git@github.com:jtreminio/openapi-tutorial.git
$ cd openapi-tutorial
$ git checkout part-ii

Let us take a look at what is going on with this API!

Controllers

We will have three basic controllers, one each for managing pets, customers, and orders. You have probably seen code like this before, it is a basic CRUD web application! Each controller will have the most common functionality:

  • Create
  • Retrieve
  • Update
  • Delete

Controllers represent data being sent from the user to our API. OpenAPI calls these requests.

Please note that this is a basic implementation of only the API structure. We are not going to implement database functionality like retrieving or persisting data. Anywhere that you see // persist $pet here or // find pet in DB here assume that we would have the logic for performing those actions there. However, for the purposes of this tutorial series we can just hand-wave those concerns away!

Pet Controller

The PetController class is used for managing pets. Our Pet Store will have an inventory of pets, and these endpoints are what will be used. Users can add new pets, update existing pets (including uploading a photo of a pet), search for pets by a unique id value or by some terms, and delete pets from the store.

Here is the list of actions and a short description for each:

endpoint description
POST /pet Add a new pet to the store
GET /pet/{id} Gets a single pet by {id}
GET /pet/findBy Finds one or more pets by name, type, or status
GET /pet/list Returns paginated list of pets
PUT /pet/{id} Update a single pet by {id}
PUT /pet/{id}/photo Update a single pet’s photo
DELETE /pet/{id} Delete a single pet by {id}

Customer Controller

The CustomerController class is used for managing customers. The actions available will be very similar to those found in the PetController.

Here is the list of actions and a short description for each:

endpoint description
POST /customer Add a new customer to the store
GET /customer/{id} Gets a single customer by {id}
GET /customer/findBy Finds one or more customers by name, email_address, address, or phone_number
GET /customer/list Returns paginated list of customers
PUT /customer/{id} Update a single customer by {id}
DELETE /customer/{id} Delete a single customer by {id}
PUT /customer/{id}/phone Add a phone number to an existing customer
DELETE /customer/{id}/phone Delete a phone number from an existing customer

Order Controller

The OrderController class is used for managing orders. The actions available will be very similar to those found in the PetController.

Here is the list of actions and a short description for each:

endpoint description
POST /order Add a new order to the store
GET /order/{id} Gets a single order by {id}
GET /order/findBy Finds one or more orders by customer_id, pet_id, or status
GET /order/list Returns paginated list of orders
PUT /order/{id} Update a single order by {id}
DELETE /order/{id} Delete a single order by {id}

Models

We have three root models, and a few extra models that will be nested within the root models.

These models contain no logic, they are strictly for holding data in a known shape (in other words, a DTO).

Pet Model

The Pet Model has some interesting properties:

property description
photo A photo of a pet. It is nullable. The photo comes from a user upload
type An enum with an allowed value of dog, cat, or fish
info Depending on the type value above, a different sub-class will be used. DogInfo for dog, CatInfo for cat, FishInfo for fish
status Another enum with an allowed value of available, pending, or sold

Customer Model

The Customer Model has some interesting properties:

property description
address Address of the customer, represented by the Address model
phone_numbers An array of Phone models

Order Model

The Order Model is pretty boring, with only one note-worthy property:

property description
status An enum with an allowed value of placed, shipped, or delivered

Why This API?

So the above API is pretty boring, right? Nothing exciting about it, at all. That is the point. Start off with something you have seen a thousand times before and apply some new tooling to end up with an amazing new result.

Looks can be deceiving, though. Yes, this API is boring, but it actually has some very interesting properties I would like to point out.

Verbs

We saw the use of several REST verbs above:

  • POST /pet
  • PUT /pet/{id}
  • GET /pet/{id}
  • DELETE /pet/{id}

There are a few more, but these four will meet almost all your needs. Their purpose is pretty clear and different from the other:

  • POST - Create a new record, like creating a new pet
  • PUT - Similar to POST but used to update en existing record, like changing the name of an existing pet
  • GET - Get an existing record, like searching for an existing pet
  • DELETE - Delete an existing record, like deleting an existing pet

Enums

If you are a PHP developer you know that Enums are available as of version 8.1.

However, you can define properties as enum in OpenAPI easily. The endpoint GET /customer/findBy has a type string property that can only be one of the following values:

  • name
  • email_address
  • address
  • phone_number

We will learn how to define this using OpenAPI.

Nested Objects

The Customer Model’s address property is a single non-nullable instance of the Address model.

Arrays

The Customer Model’s phone_numbers property is defined as an array of Phone objects, (Phone[]).

Polymorphism

Feels like you are back in college, right?

The Pet Model has a very special info property that can be one of three types. The value of this info property completely depends on the value of the type property:

scenario info instance type
type === "dog" info is an instance of DogInfo
type === "cat" info is an instance of CatInfo
type === "fish" info is an instance of FishInfo

In OpenAPI this is called a discriminator. Its purpose is to figure out the value of a property or properties, depending on the value of another property.

We will take a deeper look at discriminators as we go further along.

Wrapping It Up

We now have our starter API. It currently has no OpenAPI functionality, other than the setup we performed in OpenAPI Tutorial Part I: Introduction to OpenAPI. However, it provides a solid foundation that should be very familiar to most web developers, and focuses on what I believe to be the most common use-case for OpenAPI - working with an existing API codebase.

In the next part of this series we will begin adding OpenAPI to our API using the zircote/swagger-php annotations library. We should begin seeing both the power of OpenAPI and the benefits of using annotations for writing our OpenAPI definitions.

We still have a ways to go and much to learn!

Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!