Building APIs with OpenAPI: Continued

Ratros Y.
7 min readFeb 25, 2019

--

This document discusses how to further develop the HTTP RESTful API service created in Building APIs with OpenAPI. It introduces:

  • Common patterns for implementing CREATE, LIST, UPDATE, and DELETE methods in HTTP RESTful API services
  • Batching in HTTP RESTful API services
  • Common patterns for error handling and pagination in HTTP RESTful API services

The document is a part of the Build API Services: A Beginner’s Guide tutorial series.

Note: This tutorial uses Python 3. OpenAPI generator, of course, supports a variety of programming languages.

About the API service

In this tutorial you will develop the simple one-endpoint HTTP RESTful API service created earlier into a full-fledged photo album service where users can create (upload), list, get, and delete their photos. The service has two resources, User and Photo, and provides the following methods (endpoints):

Before you begin

  1. Get started with Building APIs with OpenAPI.
  2. Download the source code. Open /openapi/photo_album/.

Understanding the code

As introduced in Building APIs with OpenAPI, the HTTP RESTful API service in this tutorial is built from a OpenAPI specification file, openapi.yaml. The specification contains the input (request) and output (response) schemas for the API service, and the paths and methods that associate the inputs and outputs together. OpenAPI generator can compile the specification into server-side and client-side artifacts, which you will use to build your HTTP RESTful API service and its client libraries.

Resources and their fields

This gRPC API service features two resources: User and Photo. User is the parent resource of Photo.

The resource name of User is of the format //myapiservice.com/users/USER-ID. User features 3 fields:

The resource name of Photo is of the format //myapiservice.com/users/USER-ID/photos/PHOTO-ID. Photo features 3 fields:

OpenAPI has built-in support for field types. Mark reserved fields with keyword readOnly and required fields required. Fields are optional by default.

CREATE methods

openapi.yaml specifies two CREATE methods, create_user and create_photo:

create_user is a CREATE method associated with /users, a collection of Users . It takes a User JSON object in the request body as input and returns HTTP status code 200 with the newly created User JSON object in the response body if everything works.

create_photo is a CREATE method associated with /users/USER_ID/photos, a collection of Photos. It takes a Photo JSON object in the request body as input and returns HTTP status code 200 and the newly created Photo JSON object (minus the binary data) in the response body if everything works.

It is highly recommended that you use multipart form to transfer (large quantities of) binary data instead.

The two methods will be compiled into create_user and create_photo in the server-side and client-side artifacts. Modify the server-side artifact to create the methods in the API service, as seen in default_controller.py (/openapi/photo_album/openapi_server/controllers/default_controller.py):

Recommended Practices for CREATE methods

CREATE methods usually take the resource to create as input in the request body. If you have additional parameters associated with the methods, it is recommended that you ask for them in the query string. To specify them in the OpenAPI specification, use query parameters. For example, if your API service supports custom resource identifiers, you should add the resource ID as a query parameter in your OpenAPI specification.

As discussed in Designing APIs and earlier sections, the name fields of resources are always reserved; clients should not be able to declare custom identifiers via name fields in the resource. Instead, ask for resource ID in the query string, and generate the full resource name in the server end.

Also, as discussed in Designing APIs, CREATE methods are non-idempotent by nature and you should check for duplicate resources wherever possible.

Additionally, CREATE methods should return the newly created resource in the response body instead of a status message (“Resource created.”), so as to help API consumers easier perform subsequent operations on the new resource. This is especially crucial when your resources have reserved or optional fields.

DELETE methods

openapi.yaml specifies one DELETE method, delete_photo.

delete_photo is a DELETE method associated with /users/USER_ID/photos/PHOTO_ID, a single Photo resource. It does not take any input and returns HTTP status code 200 if everything works.

The method will be compiled into delete_photo in the server-side and client-side artifacts. Modify the server-side artifact to create the methods in the API service, as seen in default_controller.py (/openapi/photo_album/openapi_server/controllers/default_controller.py):

Best Practices for DELETE methods

In most cases, DELETE methods do not require any additional parameter. If you absolutely need to use additional parameters, you should ask for them in the query string. Always leave the request body empty.

DELETE methods are non-idempotent as well. However, different from CREATE methods, calling DELETE methods repeatedly by mistake has few side effects: except for the first one, all the calls will fail, as the resource has been removed at the first attempt.

DELETE methods should return nothing but an HTTP status code. If your API service has a retention policy on deleted resources, though, consider returning the deleted resource in the response body instead.

UPDATE methods

openapi.yaml specifies one UPDATE method, update_user.

update_user is an UPDATE method associated with /users/USER_ID, a single User resource. It takes a User JSON object in the request body as inputs and returns HTTP status code 200 with the updated User in the response body if everything works.

The method will be compiled into update_photo in the server-side and client-side artifacts. Modify the server-side artifact to create the methods in the API service, as seen in default_controller.py (/openapi/photo_album/openapi_server/controllers/default_controller.py):

Best Practices for UPDATE methods

In most cases, UPDATE methods do not require any additional parameter except for the updated resource. If you absolutely need to use additional parameters, ask for them in the query string.

Same as CREATE and DELETE methods, UPDATE methods are also non-idempotent. Fortunately, it is, generally speaking, OK to accept duplicate calls to UPDATE methods; all of them will succeed but the resource stays the same, provided that there are no concurrency issues.

You should return the updated resource in the response body for UPDATE methods.

LIST methods and pagination

openapi.yaml specifies one LIST method, list_photos.

list_photos is an LIST method associated with /users/USER_ID/photos, a collection of Photos. It takes two string parameters, order_by and page_token, in the query string as input and returns HTTP status code 200 with a list of Photos in the response body if everything works.

order_by is, as its name implies, the order of Photos in the result. page_token enables pagination in the LIST method. For more information, take a look at Designing APIs: Design Patterns: Pagination.

The method will be compiled into list_photos in the server-side and client-side artifacts. Modify the server-side artifact to create the methods in the API service, as seen in default_controller.py (/openapi/photo_album/openapi_server/controllers/default_controller.py):

Best Practices for LIST methods

In most cases, you should implement pagination in all the LIST methods of your API service. Pagination parameters should reside in the query string. It might also be a good idea to grant clients finer control over LIST methods, providing additional parameters like order_by and max_results. These parameters should also be query parameters.

LIST methods are idempotent. Naturally, LIST methods return a list of resources in the response body.

Batching

batchget_photos in openapi.yaml is a custom method using the HTTP verb GET. It effectively batches the GET method get_photo so that clients can easily retrieve multiple Photos without repeatedly sending GET requests.

batchget_photos is associated with /users/USER_ID/photos, a collection of Photos. Note that it takes a :batchGet suffix in the resource path. It takes a string array of Photo identifiers in the query string as input and returns HTTP status code 200 with a list of Photos in the response body if everything works.

The method will be compiled into batchget_photo in the server-side and client-side artifacts. Modify the server-side artifact to create the methods in the API service, as seen in default_controller.py (/openapi/photo_album/openapi_server/controllers/default_controller.py):

Best Practices for batch methods

Batch methods are always custom methods. As a reminder, all custom methods must have their custom method names attached after the resource path.

Batch methods should have the same structure as their once-only counterparts. They are nice-to-have features in API services; think carefully about your use cases before adding batch methods.

Error handling

Generally speaking, it is strongly advised that you use HTTP status codes as the error codes in your API service. For example, if a GET method fails because the resource ID provided is incorrect, you should return an HTTP response with the status code 404 (NOT FOUND). View the list of HTTP status codes and their respective meanings here. To add this 404 response in your OpenAPI specification, see the example below:

Additionally, you should add a context-specific error message and error contexts (if applicable) in the response body. You may have noticed that in openapi.yaml every method has a default response in addition to the 200 OK response; this is a fallback, generic response reserved for the case where no HTTP status codes listed in responses apply.

Running the code locally

To run the API service, change to the directory codegen_server/ and run the following commands:

# Install dependencies for the API service
pip install -r requirements.txt
# Start the API service
python -m openapi_server

To use the generated client to access the API service, open a new terminal, go back to the directory photo_album/, and install the client as a local dependency:

pip install -e codegen_client/

Then run the following Python script:

import openapi_client
from openapi_client.models import User, Photo
client = openapi_client.DefaultApi()
user = User(display_name='John Smith', email='user@example.com')
client.create_user(user = user)

You should see the following outputs:

{'display_name': 'John Smith',
'email': 'user@example.com',
'name': '//photos.myapiservice.com/users/95a120cb8c4e469cbec1e7a94a6cedce'}

--

--