OpenAPI tips
5 minute read
Model your API around resources
A “RESTful” API is one that is stateless and utilizes an expected pattern of Create, Retrieve, Update, and Delete operations on resources (CRUD operations). A good REST API would have a predictable, uniform interface. If you are familiar with this, you can skip to the example REST API which utilizes the best practices noted from this document.
Resources
Typically, entities would have a plural root, e.g. /users
, under which all methods would relate. Resources are nouns, and the actions taken on the resources are verbs (for example, to “retrieve” a resource). Below is a typical pattern for REST API for managing a resource collection for a “User” resource:
HTTP method | HTTP path (plural) | Success code | Response body | REST API |
---|---|---|---|---|
POST | /users |
201 Created | ¹ | Create user |
GET | /users |
200 OK | [ Users ] | Retrieve users |
GET | /users/{id} |
200 OK | User | Retrieve user by ID |
PUT | /users/{id} |
204 No Content | Update user by ID | |
DELETE | /users/{id} |
204 No Content | Delete user by |
POST
operations to create a resource would typically return a Location
header that is the URL for the newly created resource, but no response body.
In OpenAPI, resource values are described by JSON schema. You may want to read our JSON schema tips before designing resources and writing your OpenAPI specification.
Paging and filtering
A good API should allow querying, paging, and filtering to reducing the amount of data returned as much as possible. For example, return only specific field(s) instead of entire entities. Expect that the developer needs to build a fast, scalable application under many different OS and mobile environments where bandwidth might be very limited.
Version your API
A good API should have a version strictly managed. We recommend using Semantic versioning (“semver”), following a major.minor.patch convention. In OpenAPI 3.0, you can add a major version to your server paths, for example:
servers:
- url: https://api.banana.io/v1
description: Banana API v1
In API Builder, you can manage the servers
using apidoc.overrides.
Use examples
OpenAPI examples can greatly assist the developer in using your API because JSON schema is hard to read. They are understood by the UI and assist in being able to try your API with little effort.
Reusable components
JSON schema can take a lot of time to get right and to document correctly. You really want to limit the number of changes required to make changes/updates. JSON schema references are a powerful tool and can greatly reduce the size of your OpenAPI document. Smaller documents will be easier to manage, validate faster, and download faster.
It is generally a good idea to use OpenAPI components, and then refer to these components using JSON schema references ($ref
).
Tip
Reusable OpenAPI components will save time and make the document easier to adapt.JSON Schema tips
The following sections provide some guidance and recommendations when writing JSON schema. You should make your JSON schema as constrained as possible so that, by the time you need to handle the request in the flow, you can be sure that the data is exactly as you would expect. It means less bugs later and less checking later.
Required properties
Use required properties if fields should be provided. While this is good for input validation, separate, less constrained schema may be required for filtering fields.
Default value
You can annotate an optional property to have a default value. This is convenient for clients as it documents what to expect when the value is not provided. However, it is for documentation purposes only. Optional values would need to be checked in the flow, and appropriate defaults handled manually.
Formats
JSON schema formats can be used to additionally constrain string
properties. It is very useful if a string property is expected to have a particular structure, for example, an email address.
additionalProperties
One aspect of JSON schema that is easily overlooked are additionalProperties
. Take this schema for example:
User:
type: object
required:
- name
properties:
name:
type: string
email:
type: string
format: email
It looks good, and would require the “name” property, and would pass JSON schema validation with this input: {"name":"bob"}
, but it would also pass validation with this input {"name":"bob", "age": 12}
. That is because JSON schema allows additional properties by default. It is better to restrict the inputs to exactly what you expect from the user, or it could cause problems (e.g. security or compatibility).
It is recommended you disable additionalProperties
, for example:
User:
type: object
required:
- name
additionalProperties: false
properties:
name:
type: string
email:
type: string
format: email
Tip
The more constrained you make your schema, the less bugs you will have later, so useadditionalProperties: false
whenever possible.
Composition with allOf
Composing schemas from sub-schemas seems logical, but can be tricky. We are not necessarily recommending that you use schema composition, but if you wan to, you should read extending closed schemas. It is debatable if this kind of composition and extra level of complexity is worth it, but it can help reduce the size of the schema.
Filtering fields
It is very beneficial for clients to be able to filter fields from the results, instead of trying to pull back the entire result, which can be very large and degrade performance for both the client and server. If you follow these tips, then your JSON schema used for inputs will be very constrained, and require specific properties. Unfortunately, the same schema that is used for inputs cannot be re-used for filtering because filtering fields can remove these required properties and validation will fail. So, you may need JSON schema specifically for filtering.
UserFields:
type: object
additionalProperties: false
properties:
name:
type: string
email:
type: string
format: email
Example REST API
The Bookstore API is an example REST API in OpenAPI 3.0, utilizing some of the best-practices mentioned in this document.