This is Part II of a multi-part series. Below are the links to other parts of this tutorial!
- OpenAPI Tutorial Part I: Introduction to OpenAPI
- OpenAPI Tutorial Part II: Common API Example
- OpenAPI Tutorial Part III: Paths and Basic Request Data
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 petPUT
- Similar toPOST
but used to update en existing record, like changing the name of an existing petGET
- Get an existing record, like searching for an existing petDELETE
- 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!
- Previous →
OpenAPI Tutorial Part I: Introduction to OpenAPI - ← Next
OpenAPI Tutorial Part III: Paths and Basic Request Data