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
, andDELETE
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
- Get started with Building APIs with OpenAPI.
- 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, Photoclient = 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'}