This chapter covers
In the previous chapters, we explored how to design a basic programming interface using the information collected in the API goals canvas. We identified the resources, actions, parameters, and responses for our example Shopping API. We also designed the API’s data. But we did all of this using box-and-arrow diagrams and some tables.
Such figures and tables are always useful for brainstorming and getting an overall idea of how the API’s goals can be transposed into a programming interface. But when it comes to describing precisely a programming interface, and especially its data, it is simpler and more efficient to use a structured tool like an API description format. Being code-like and a standardized description of an API, this offers many advantages:
The OpenAPI Specification (OAS) is a popular REST API description format. In this chapter, we will walk through its basics in order to uncover the benefits of using such a format.
An API description format is a data format whose purpose is to describe an API. Figure 4.1 shows how the programming interface for the add product to catalog goal can be described in a simple text file using such a format.
This text file uses data to tell the same story as our tables and hand-drawn box-and-arrow diagrams. According to the description at the top of the document, the /products
resource represents the catalog. It contains a POST
HTTP method that we can use to add a product to catalog. This POST
operation needs a requestBody
containing a name
and a price
. The description even provides additional information, such as example values for the properties. Such a file can be written quickly using a simple text editor. But, most importantly, because this file contains structured data, programs can read it and easily transform it into something else. A basic use is to generate reference documentation that describes all of an API’s goals. You can share this with all the people involved in the project so they have a good overview of the design being created.
That sounds interesting, and it’s only the tip of the iceberg. This simple text file uses the OAS. Let’s take a closer look at this popular REST API description format before discussing the usefulness of such a format and when to use it while designing APIs.
The OpenAPI Specification (OAS) is a programming language-agnostic REST API description format. This format is promoted by the OpenAPI Initiative (OAI), which “…was created by a consortium of forward-looking industry experts who recognize the immense value of standardizing on how REST APIs are described. As an open governance structure under the Linux Foundation, the OAI is focused on creating, evolving and promoting a vendor neutral description format.” The OAS (https://www.openapis.org) is a community-driven format; anyone can contribute to it through its GitHub repository (https://github.com/OAI/OpenAPI-Specification).
Figure 4.2 shows a very basic OAS 3.0 document. This OAS document is written using the YAML data format.
Such a basic OAS document provides general information about the API, such as its name and version. It describes the available resources (identified by their paths) and each resource’s operations (or actions, as you saw in the previous chapter) identified by HTTP methods, including their parameters and responses.
An OAS document can be written in YAML or JSON, so which one should you use? As you will be writing the documents yourself, I recommend using the YAML format, which is, in my opinion, easier to read and write.
Though this book focuses on OAS, other REST API description formats exist; the most notable OAS competitors are RAML and Blueprint. I choose to focus on OAS, not only because I use it everyday, but also because it is community-driven and widely used. Note, however, that this book is not called OpenAPI Specification in Action.
OAS and its ecosystem offer many features and, while we will discover some of them throughout this book, it won’t be possible to cover them all. Once you are familiar with what is presented in this book, I recommend you read the OAS documentation (https://github.com/OAI/OpenAPI-Specification/tree/master/versions) and use the OpenAPI Map (https://openapi-map.apihandyman.io), a tool I created to help you find your way through the specification.
You’ve seen that an API description format such as the OAS lets you describe an API using a text file containing some kind of structured data. But you could also use a word processor or spreadsheet document to do the same thing. I urge you to avoid doing so; let’s see why.
Indeed, you could describe a programming interface using a word processor or spreadsheet document. You could also easily share such a document with others. But can you version it easily? Can you generate documentation from it? Can you generate code from it? Can you configure API-related tools with it? Can you … ? I could probably cover an entire page with such questions. Using an API description format has benefits throughout the API lifecycle, and especially during the design phase. It benefits not only the API providers but also API consumers.
An OAS document is a simple text file that can be easily stored in a version control system such as Git, just like code. It is therefore simple to version it and track modifications while iterating on the API design.
An OAS document has a structure that helps to describe a programming interface more efficiently. You have to describe resources, operations, parameters, and responses. You can define reusable components (such as a data model, for example), avoiding the painful and risky art of maintaining copy/pasted pieces of API descriptions.
Speaking of writing an OAS document, you can use your favorite text editor, but I recommend using an editor that specifically handles this format. You can use the online Swagger Editor (http://editor.swagger.io) shown in figure 4.3.
Because OAS is a machine-readable format, this editor offers features like autocompletion and document validation, and the right-hand panel shows a useful rendering of the edited OAS document. To see a human-friendly representation of the data structures used as parameters or responses, the Model and Example Value views are especially useful. This editor is an open source project, and its source code is available on GitHub (https://github.com/swagger-api/swagger-editor).
This online editor is great because you only need a browser to run it, but it can be cumbersome to constantly download or copy and paste the edited file to actually save or open it. I personally use the Microsoft Visual Studio Code editor along with the Swagger Viewer extension (https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer), which supplies a SwaggerUI-based preview panel, and the openapi-lint extension (https://marketplace.visualstudio.com/items?itemName=mermade.openapi-lint), which provides autocompletion and validation. This configuration provides the same experience as the online editor, and you work directly with your files.
Note that there are API design tools that let you describe a programming interface without writing any code. Some of them propose interesting features like collaborative working. If you want to use such a tool, that’s fine; just check that your work can be exported to a known and used API description format. But even though such tools exist, it’s still worthwhile to know how to write an OAS document. You need almost nothing to use such a format, and one day you might want to build your own tooling around this format.
An OAS document can be easily shared with others even outside your team or company to get feedback on your design. Unlike a specific internal format known only by a few, the OAS format is widely adopted. People can import the document into the online Swagger Editor or many other API tools. Alternatively, to avoid bothering them with the OAS document itself, you can provide access to a ready-to-use, human-friendly rendering. An OAS document can be used to generate API reference documentation that shows all the available resources and operations. You can use the Swagger UI (https://github.com/swagger-api/swagger-ui) for this, which shows the OAS document as in the right-hand pane of the Swagger Editor.
There are non-Swagger OpenAPI tools, too. For example, as an alternative to Swagger UI, you can use a tool such as ReDoc (https://github.com/Rebilly/ReDoc), which is also open source. Figure 4.4 shows the ReDoc OpenAPI tool.
And one final note: to create API documentation, you will discover advanced uses of OAS in chapter 12.
Once an API is described with an API description format, the implementation code can be partially generated from it. You will get an empty skeleton of source code that you can also use to generate a working mockup. Consumers can also take advantage of such machine-readable API descriptions to generate code to consume the API. And such a format can also be used by API testing or security tools, and many other API-related tools. For example, most API gateway solutions (proxies made to expose and secure APIs) can be configured using an API description file such as an OAS document.
These examples alone show how an API description format is more efficient than a word processor or spreadsheet document. And OAS documents can be used in many other ways.
Before starting to describe an API using an API description format, however, you must be sure you’re doing so at the right time, as shown in figure 4.5.
An API description format is made to describe a programming interface. Therefore, it must not be used while identifying the API’s goals. As you learned in chapter 3, designing the programming interface first without having a clear view of what the API is supposed to do is a terrible idea! And an API description format must not be used while identifying the concepts behind the goals; that’s still a little bit too early. Sure, it’s the first step of designing the programming interface, but during this phase you are still not dealing with a real programming interface. An API description format definitely must be used, however, when designing the programmable representation of goals and concepts, and the data.
When you design a REST API, you can start to use the OAS when you design the resource paths and choose the HTTP methods describing actions. It is possible to create a minimal file containing only these elements. Once that is done, you can complete the document by describing the API’s data. As you will discover in the following section, describing all of this will be far simpler and more efficient using an API description format than drawing tables, arrows, and boxes. But do not forget that all people involved in the project and who need to actually see the API design might not be familiar with a code-like format such as the OAS, so always provide a way to get a human-friendly representation of the file you are working on. With that in mind, let’s see how we can describe REST API resources and actions using the OAS.
As shown in figure 4.6, when we transposed API goals into a programming interface in chapter 3, we identified resources and actions and represented them with paths and HTTP methods.
We used boxes and arrows to describe this programming interface, but this can also be done in a more structured way using the OAS. The resulting document will contain exactly the same information as the corresponding figure, but this time the information will be presented as data in a structured document. Let’s get started now on our document describing the Shopping API.
The following listing shows a minimal but valid OAS document. This document is written using OAS version 3.0.0. It describes an API named Shopping API in its 1.0 version.
openapi: "3.0.0" ①
info: ②
title: Shopping API
version: "1.0"
paths: {} ③
① OAS version
② API’s general information
③ Empty paths
The structure of OAS documents can evolve from one version to another, so parsers use the openapi
version to adapt their parsing accordingly. Note that both the specification (openapi
) and API (info.version
) version numbers must be surrounded by quotes. Otherwise, OAS parsers will consider these as numbers and document validation will fail because these two properties are supposed to be strings.
The listing shows the paths
property only to make the document valid. (If it’s not present, the parser reports an error.) The paths
property contains the resources available to this API. We can set its value to {}
for now—this is how you describe an empty object in YAML. A nonempty object (like info
) does not need the curly braces. Next, we will start to fill the paths
property by adding a resource.
As shown in figure 4.7, while working on the goals search for products and add product to catalog, we identified a catalog resource. We chose to represent it with the /products
path.
In order to describe this resource in the OAS document, we must add the /products
path into the paths
property as shown in figure 4.7 (don’t forget to remove the empty curly braces!). We’ll also describe what this resource is (The products catalog
) using the description
property of the resource, as shown in the following listing.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths: ①
/products: ②
description: The products catalog ③
① API’s resources
② Resource’s path
③ Resource’s description
The description
is not mandatory, but providing descriptions of your API’s resources will be useful throughout the API lifecycle. It’s like when you code: the code can be understandable on its own, but comments or JavaDoc, PHPDoc, JSDoc, or <your favorite language>Doc annotations about its use will always be welcomed by other people who read your code or the documentation generated from it.
Because an API by definition will be used by others, it is really important to take advantage of the documentation possibilities of API description formats. During this design phase it is especially useful to keep a link between your earlier work, like the API goals canvas or concept identification, and the programmable representation. It might also help people with whom you share this design to understand it more easily (/products
being a catalog might not be obvious to everyone).
A resource described within an OAS document must contain some operations. Otherwise, the document is not valid. The catalog resource is used by two goals: search for products and add product. Let’s see how we can describe them as operations in the OAS document.
We can add to our document in order to provide all the information for each goal we identified by the end of section 3.2. For each one, we know which HTTP method it uses, and we have a textual description of its inputs and outputs, as shown on the left side of figure 4.8.
To represent search for products, we chose to use the GET
HTTP method on the catalog resource represented by the /products
path. This action uses a free query parameter and returns products matching the query. To add this action to the /products
resource, we use a get
property. We can also use documentation features to provide more information about this GET /products
operation. We’ll set the summary
property to Search for products
and the description
to Search for products in catalog using a free query parameter
, as shown in the following listing.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products: ①
description: The products catalog
get: ②
summary: Search for products ③
description: | ④
Search for products in catalog
using a free query parameter
① Resource
② Action’s HTTP method
③ Action’s short description
④ Action’s long description
The summary
property is a short description of the action, without details. The goal defined in the API goals canvas is usually perfect to use here. There is also a description
property, which can be used to provide a more detailed description of the action. Here we use it to indicate that this action is using a free query parameter
. Note that the description
property is multiline. This is a YAML feature: to be multiline, a string
property must start with a pipe (|
) character.
In an OAS document, an operation must describe at least one response in its responses
property, as shown in listing 4.4. For now, we will use this mandatory response to provide an informal description of the output of the search for products action. We’ll add a responses
property containing a "200"
response (for HTTP status code 200 OK
), whose description
is Products matching free query parameter
.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products:
description: The products catalog
get:
summary: Search for products
description: |
Search for products in catalog
using a free query parameter
responses: ①
"200": ②
description: | ③
Products matching free query parameter
① Action’s response list
② 200 OK HTTP status response
③ Response’s description
As mentioned, an action’s possible responses are described in its responses
property. Each response is identified by its HTTP status code and must contain a description
property. The "200"
property stands for the 200 OK
HTTP status, which tells the consumer that everything went fine. (Did you notice the quotes around the status code? They are needed because YAML property names must be strings and 200 is a number.) The response’s description
property states that if everything went OK, the Products matching free query parameter
are returned. We will explore the possible responses and HTTP status codes returned by an action in more depth in chapter 5.
Now that we have added this action to the /products
resource, our OAS document is valid. But we’re not done yet. There is a second action on this resource: add product. We chose the HTTP method POST
to represent the add product action on the catalog resource. It takes some product information and returns the product added to the catalog (see figure 4.9).
To add this action, we proceed exactly like we did previously. The following listing shows how to do this.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products: ①
description: The products catalog
get:
summary: Search for products
description: |
Search for products in catalog
using a free query parameter
responses:
"200":
description: |
Products matching free query parameter
post: ②
summary: Add product ③
description: | ④
Add product (described in product info
parameter) to catalog
responses: ⑤
"200": ⑥
description: | ⑦
Product added to catalog
① Resource
② Action’s HTTP method
③ Action’s short description
④ Action’s long description
⑤ Action’s response list
⑥ 200 OK response
⑦ 200 OK response’s description
We add a post
property inside the object describing the catalog resource identified by the /products
path. We set its summary
property to Add product
and its description
to Add product (described in product info parameter) to catalog
. We add a "200"
property inside responses
and set its description
to Product added to catalog
.
The /products
resource’s post
operation is now described within the OAS document. Anyone looking at this document can tell what this operation does by reading its summary, description, and response description. As shown in figure 4.10, this document contains the same information we identified in section 3.2.
Congratulations! You now know the basics of how to describe a resource and its actions using the OAS. Even if it is not yet complete, such a description already provides interesting information about the API. We have a formal and structured description of the resource’s path and HTTP methods, and we are able to determine which goal corresponds to which action and how they work, thanks to the description
properties.
But this document only provides a vague description of each operation’s inputs and outputs. In the previous chapter, we designed these inputs and outputs in detail. Let’s see now how to complete this document by describing those.
In the previous chapter, when we designed the programming interface matching the identified goals, we did not stop after designing the resource paths and choosing the HTTP methods. In section 3.3, we fully described the actions' parameters and responses, including descriptions of the data organization and properties (figure 4.11).
The OAS relies on the JSON Schema specification (http://json-schema.org) to describe all data—query parameters, body parameters, or response bodies, for example. JSON Schema aims to describe data formats in a clear human-readable and machine-readable way. It can also be used to validate JSON documents against a JSON schema, a data description made with JSON Schema. This format can be used independently from OAS to describe and validate any type of JSON data.
In this chapter, JSON Schema refers to the JSON Schema specification, whereas a JSON schema is an actual schema, a description of data. Note the difference in capitalization. Let’s see how we can describe API data using the OAS and JSON Schema. We’ll start with the search for products query parameter.
To search for products, API users have to provide a free query parameter to indicate what they are looking for (figure 4.12). In the previous chapter, we decided that this parameter would be a query parameter named free-query
. To search for products using the API, a consumer would issue a GET /products?free-query={free query}
request (for example, GET /products?free-query=book
).
To describe this parameter, we add a parameters
property inside the get
operation of the /products
resource, as shown in the following listing.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products: ①
get: ②
summary: Search for products
description: |
Search for products in catalog
using a free query parameter
parameters: ③
[...]
responses:
"200":
description: |
Products matching free query parameter
① Resource
② Action
③ Action’s parameters list (except body)
When an action on a resource needs parameters other than body parameters, they are described in the action’s parameters
property. In this case, to describe the parameter, we set its name
to free-query
, as shown in the following listing.
parameters:
- name: free-query ①
description: | ②
A product's name, reference, or
partial description
in: query ③
required: false ④
schema: ⑤
type: string ⑥
① Parameter’s name
② Parameter’s description
③ Parameter’s location
④ Whether a parameter is mandatory
⑤ Parameter’s data structure description
⑥ Parameter’s type (string)
We indicate that the parameter is located in
the query
but is not required
, and that its data structure is described in schema
. This schema simply indicates that this parameter’s type is string
. We also provide some additional information in its description
to tell that its value could be A product’s name, reference, or partial description
.
The parameters
property is a list or array. In YAML, each element of a list or array starts with a dash (-
). To describe a parameter, we need at least three properties: name
, in
, and schema
. This parameter’s description also contains two optional properties: required
and description
.
The parameter’s name
is the name that will be shown in the path (/products?free-query={free query}
). The in
property indicates the location of the parameter. Here, it’s a query
parameter, so it’s located after a ?
in the path.
The required
property, which indicates if the parameter must be provided, is not mandatory. If required
is not set, its default value is false
, indicating that the parameter is optional. You are under no obligation to set it unless you need to define a parameter as required
. But even though the parameter is optional, it’s always better to explicitly specify required: false
. This way, you are sure that you’ve considered whether each parameter is mandatory or not.
The parameter’s data structure described in the schema
property is a JSON schema. As mentioned earlier, JSON Schema is used in an OAS document to describe the API’s data—from a simple string
query parameters to more complex structures used as body parameters and responses. Using JSON Schema, let’s see how we can describe a product, such as the one designed in section 3.3.1.
We’ll start with a very basic version of the product, as shown in figure 4.13. It’s composed of a reference
, a name
, and a price
. The reference
and name
properties are of type string
and price
is a number
.
Earlier, to describe a simple string
query parameter with JSON Schema, we used type: string
. Now, to describe such a product object, we have to use the type object
and list its properties. Each property is identified by its name and type, as seen in the following listing.
type: object ①
properties: ②
reference: ③
type: string ④
name: ③
type: string ④
price: ③
type: number ④
① This schema describes an object.
② It contains properties.
③ Property’s name
④ Property’s type
But when we discussed designing the API’s data in the previous chapter, you learned that we must also identify which properties are required. Because the reference
, name
, and price
properties are all mandatory, we’ll add an optional description
for the sake of example (see figure 4.14).
Now the product is composed of mandatory reference
, name
, and price
properties and an optional description
. To indicate that in the corresponding JSON Schema, we add reference
, name
, and price
entries into the object’s required
properties list, as shown in the following listing.
type: object
required: ①
- reference
- name
- price
properties:
reference: ②
type: string
name: ②
type: string
price: ②
type: number
description: ⑤
type: string
① Required properties list
② Required properties
⑤ Optional property
The JSON Schema allows us to indicate which properties are required in an object with its required
list. Any property whose name is included in this list is mandatory. Any property whose name is not in this list is considered optional. In this case, description
is the only optional property.
Our schema is getting pretty accurate, but when we designed the product resource, we found that we sometimes needed to add some descriptions because the property names were not sufficient to explain their nature. Let’s add a description to the object to show that it describes A product
. We can also add a description to explain what a reference
is. We can even add an example to show what a product’s reference looks like. The next listing shows this.
type: object
description: A product ①
required:
- reference
- name
- price
properties:
reference:
type: string
description: Product's unique identifier ②
example: ISBN-9781617295102 ③
name:
type: string
example: The Design of Web APIs ③
price:
type: number
example: 44.99 ③
description:
type: string
example: A book about API design ③
① Object’s description
② Property’s description
③ Property’s value example
Just like the OAS, the JSON Schema comes with useful documentation features. An object and all of its properties can be described, and an example value can be provided for each property.
We’re still missing something, though. The product resource we designed in the previous chapter didn’t only have literal properties (strings or numbers), as shown in figure 4.15.
Indeed, it also had a complex supplier
property, which is mandatory. A supplier
is defined by its reference
and its name
, which are both required. The following listing shows the updated JSON schema.
type: object
description: A product
required:
- reference
- name
- price
- supplier ①
properties:
reference:
type: string
description: Product's unique identifier
example: ISBN-9781617295102
name:
type: string
example: The Design of Web APIs
price:
type: number
example: 44.99
description:
type: string
example: A book about API design
supplier: ②
type: object
description: Product's supplier
required: ③
- reference
- name
properties: ④
reference:
type: string
description: Supplier's unique identifier
example: MANPUB
name:
type: string
example: Manning Publications
① The supplier is required.
② The supplier object property
③ The supplier’s required properties
④ The supplier property descriptions
To add the supplier
property to the product JSON schema, we add it to the properties
list and set its type
to object
. We supply a description for the property and a list of its required properties (reference
and name
); then we describe those properties. For the reference
property, we supply a type
, description
, and example
. For the name
property, we only set a type
and example
. Finally, we add the supplier
property to the product’s required
list.
As you can see, describing data using the JSON Schema is simple; you can describe any data structure using this format. Unfortunately, this book does not cover all of its features. To learn more, I recommend you read the Schema Object description in the OAS (https://github.com/OAI/OpenAPI-Specification/tree/master/versions), and then the JSON Schema specification (http://json-schema.org/specification.html). Now that you know how to describe data structures using the JSON Schema, let’s describe the search for products response.
When users search for products, they are supposed to get the products matching the provided free query (figure 4.16). The corresponding GET /products?free-query={free query}
API request returns a 200 OK
HTTP response whose body will contain an array of products.
In an OAS document, the data returned by an operation in the body of an HTTP response is defined in its content
property, as shown in listing 4.12. When describing the content of a response, you have to indicate the media type of the document contained in the response’s body. The media type is provided in the HTTP response. For now, as stated earlier, we’ll take it for granted that our API returns JSON documents, so we indicate application/json
in the response. (We talk about this HTTP feature later in chapter 6.)
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products:
get:
summary: Search for products
description: |
Search using a free query (query parameter)
parameters:
[...]
responses:
"200":
description: Products matching free query
content: ①
application/json: ②
schema: ③
[...]
① Response body’s definition
② Response body’s media type
③ Response body’s JSON schema
Once that is done, we can describe the schema of the returned JSON document using the JSON Schema, as shown in listing 4.13. The GET /products
action returns an array of products. We already know how to describe a product using the JSON Schema, but how do we describe an array of products? As you can see in the next listing, an array is described using the array
type. The items
property contains the schema of the array’s elements. This array
contains some products; you should recognize the product JSON Schema we created earlier.
responses:
"200":
description: Products matching free query
content:
application/json: ①
schema: ②
type: array ③
description: Array of products
items: ④
type: object
description: A product
required:
- reference
- name
- price
- supplier
properties:
reference:
description: Unique ID identifying a product
type: string
name:
type: string
price:
description: Price in USD
type: number
description:
type: string
supplier:
type: object
description: Product's supplier
required:
- reference
- name
properties:
reference:
type: string
name:
type: string
① Response body’s media type
② Response body’s JSON schema
③ Response type is an array
④ Array’s items schema
The next listing contains an example of a JSON document returned in the response’s body corresponding to the JSON Schema.
[
{
"reference": "123-456",
"name": "a product",
"price": 9.99,
"supplier": {
"reference": "S789",
"name": "a supplier"
}
},
{
"reference": "234-567",
"name": "another product",
"price": 19.99,
"supplier": {
"reference": "S456",
"name": "another supplier"
}
}
]
As you can see, describing the response’s data is pretty simple. And you know what? Describing body parameters is just as easy.
Let’s take a look at the add product action. To add a product to the catalog, the API user has to provide some product information in the body of their request, as illustrated in figure 4.17.
Describing the body parameter of add product is done almost the same way as describing the response of search for products, as you can see in the following listing.
openapi: "3.0.0"
info:
title: Shopping API
version: "1.0"
paths:
/products:
description: The products catalog
[...]
post:
summary: Add product
description: Add product to catalog
requestBody: ①
description: Product's information ②
application/json: ③
schema: ④
[...]
responses:
"200":
description: Product added to catalog
① Body parameter’s definition
② Body parameter’s description
③ Body parameter’s media type
④ Body parameter’s schema
The body parameter of an HTTP request is described in its requestBody
property. Like a response’s body, a body parameter has a media type (application/json
), and its content is described with a JSON Schema. The complete description of this parameter is shown in the following listing.
requestBody:
description: Product's information
content:
application/json:
schema: ①
required:
- name
- price
- supplierReference
properties:
name:
type: string
price:
type: number
description:
type: string
supplierReference:
type: string
① Body parameter’s schema
The body parameter’s schema (or request body’s schema) is described like any other data in OAS using the JSON Schema. As previously designed, the mandatory information needed to add a product to the catalog is its name
, price
, and supplierReference
. The description
is optional. The following listing shows what the JSON document might look like.
{
"name": "a product",
"price": 9.99,
"supplierReference": "S789"
}
See? It was as easy as anticipated. Why? Because describing the request’s and response’s body is done the same way. Providing a common way of doing different things is a basic principle of design. This approach can be used when creating anything, from a door to an API description format, to make it user-friendly. We will investigate this from the API design perspective later in chapter 6.
We only need to describe add product’s response to finish the catalog resource’s description. We have learned all we need to do that. We know how to describe an action’s response and its data. And, fortunately, search for products and add product return the same type of data, a product, so we already have the JSON Schema describing the response’s data. But if we do that as we have learned, we will duplicate the product JSON Schema in the OAS document. Let’s see how we can handle this more efficiently.
It’s always useful to dig into an API description format’s documentation to learn all its tips and tricks, just like you would do when learning a programming language. There are two basic things that you need to know when writing OAS documents:
Both search for products and add product return product resources. It would be a pity to describe the same thing twice. Fortunately, OAS allows us to describe reusable components such as schemas, parameters, responses, and many others, and use them where needed using a reference (figure 4.18).
All we have to do to avoid describing a product JSON Schema twice is to declare it as a reusable schema. Reusable components are described in the components
section that’s at the root of the OAS document. Within this section, reusable schemas are defined in schemas
; each reusable schema’s name is defined as a property of schemas
. This property contains the reusable component, a JSON Schema. In the following listing, product
contains the product JSON Schema we created earlier.
openapi: "3.0.0"
[...]
components: ①
schemas: ②
product: ③
type: object ④
description: A product
required:
- reference
- name
- price
- supplier
properties:
reference:
description: |
Unique ID identifying
a product
type: string
name:
type: string
price:
description: Price in USD
type: number
description:
type: string
supplier:
type: object
description: Product's supplier
required:
- reference
- name
properties:
reference:
type: string
name:
type: string
① Reusable components
② Reusable schemas
③ Reusable schema’s name
④ JSON Schema
Now, instead of redefining the product JSON Schema, we can use a JSON reference to access this predefined schema when we need it. A JSON reference is a property whose name is $ref
and whose content is a URL. This URL can point to any component inside the document, or even in other documents. Because we are only referencing local components, we use a local URL containing only a fragment describing the path to the needed element, as shown in figure 4.19. Here, product
is located in schemas
, which is located in components
at the root of the document.
The following listing shows how we can use a reference for the POST /products
response.
post:
summary: Add product
description: Add product to catalog
[...]
responses:
"200":
description: Product added to catalog
content:
application/json:
schema: ①
$ref: "#/components/schemas/product" ②
① Response’s schema
② Reference to predefined schema
When users add a product to the catalog, they get the created product in return. So when defining the response schema, instead of redescribing the product JSON Schema, we can just use a $ref
property whose value is a reference to the predefined schema. We can do the same for search for product, which returns an array of products, as shown in the next listing.
get:
summary: Search for products
description: |
Search using a free query (query parameter)
parameters:
[...]
responses:
"200":
description: Products matching free query
content:
application/json:
schema: ①
type: array ②
items: ③
$ref: "#/components/schemas/product" ④
① Response’s schema
② An array
③ Array’s items schema
④ Reference to predefined schema
The schema we have predefined only describes a product, not an array of products, so here we use the predefined schema to describe the array’s items
schema. To do that, as before, we simply replace the schema with a reference ($ref
) to the predefined schema. This means that we can combine inline and predefined definitions. Note that we can also use multiple predefined definitions when needed.
We’re now done with the catalog resource identified by the /products
path and its two actions, get
and post
. These elements are fully and efficiently described thanks to the OAS and JSON Schema. There is one last thing to investigate with the OAS in order to be able to fully describe a basic REST API—how to describe a resource with a variable path.
The product resource, which can be deleted, updated, or replaced, is identified by a variable path (figure 4.20). Note that the get, update, and replace actions return the same product and also that the update and replace actions use the same parameter. Note also the /products/{productId}
path contains a productId
variable, which is called a path parameter.
You already learned how to define parameters on the action level with GET /products?free-query={free query}
, so let’s do this again with DELETE /products/{productId}
. The following listing shows how we can define this in our OAS document for the delete action.
paths:
/products:
[...]
/products/{productId}: ①
description: A product
delete: ②
summary: Delete a product
parameters: ③
- name: productId ④
in: path ⑤
required: true ⑥
description: Product's reference
schema:
type: string
① Product resource path with parameter
② Delete product action
③ Delete product action’s parameters
④ Path parameter’s name
⑤ Parameter is located in path
⑥ Parameter is required
First we add a new /products/{productId}
path to define the product resource. As you can see, the path parameter is identified with curly braces ({productId}
) in paths
. Then, we define this path parameter in the delete action’s parameters
list. It is defined almost like any other parameter that goes in the parameters
section: we need to set its name
, location
, and schema
. The name
must match the name inside the curly braces in the path, so we set it to productId
. The location (in
) is obviously path
, and this parameter’s type
, defined in its schema
, is string
.
We’re almost done, but there is one last thing we must not forget to do: because it is a path parameter, we also have to make this parameter mandatory by setting required
to true
. If we don’t do that, the parser throws an error.
That wasn’t so different from defining a query parameter. Now, what if we wanted to describe the product’s update and replace actions? We could describe the path parameter the same way, but that would mean duplicating this description in each new action. How can we do that more efficiently?
Earlier, we discovered the components
section of the OAS document. This section allows us to define reusable components such as schemas and responses, and we can also describe reusable parameters here. The following listing illustrates how we do this.
components: ①
parameters: ②
productId: ③
name: productId
in: path
required: true
description: Product's reference
schema:
type: string
① Reusable components
② Reusable parameters
③ Reusable parameter’s name
To define a reusable parameter, we do exactly what we did for our reusable schema. In the components
section at the root of the OAS document, each reusable parameter is defined as a property of parameters
and identified by name
. The productId
property contains the definition of the productId
path parameter as we have defined it for DELETE /products/{productId}
.
Pretty simple, isn’t it? Like the JSON Schema, we use this predefined parameter with a JSON reference, as shown in the following listing.
components:
parameters:
productId: ①
[...]
paths:
/products:
[...]
/products/{productId}: ②
delete:
parameters:
- $ref: #/components/parameters/productId ③
[...]
put:
parameters:
- $ref: #/components/parameters/productId ③
[...]
patch:
parameters:
- $ref: #/components/parameters/productId ③
[...]
① Path parameter definition
② Product resource’s path with parameter
③ Reference to predefined parameter
Instead of defining the same parameter three times, we simply use a $ref
property pointing to the unique and reusable productId
definition.
That’s much better! The productId
parameter is defined once and used in three different places. But do you know what? We can do even better. Strictly speaking, the productId
parameter is not an action’s parameter; it’s a resource’s parameter.
In an OAS document, parameters can be defined not only at the action level but also at the resource level, again in a parameters
section. The structure of this section is exactly the same as at the action level. All parameters defined on the resource level are applied to all actions on the resource. Therefore, as the next listing demonstrates, to simplify our document even more, we can simply define the productId
path parameter in the parameters
section of the /products/{productId}
path.
components:
parameters:
productId: ①
[...]
paths:
/products:
[...]
/products/{productId}: ②
parameters: ③
- $ref: #/components/parameters/productId ④
delete: ⑤
[...]
put: ⑤
[...]
patch: ⑤
[...]
① Path parameter definition
② Product resource’s path with parameter
③ Resource-level parameters
④ Reference to predefined parameter
⑤ No more path parameter definitions
Congratulations! With all that you have now discovered about OAS, you should be able to finish the descriptions of the product resource’s actions. But more importantly, you will now be able to create a formal description of any basic REST API using OAS and share it with all the people involved in your project. Don’t hesitate to dig into the OAS documentation (https://github.com/OAI/OpenAPI-Specification/tree/master/versions), use my OpenAPI Map (https://openapi-map.apihandyman.io), and experiment to discover other features.
This chapter concludes the first part of this book. You have acquired a basic set of API design skills, and you now know
In the next part, we will improve on these skills so you can create APIs that anybody can use easily, without even thinking about it. In the next chapter, we dig into the API’s usability by investigating how to design straightforward APIs.