Kousik Nath

@kousiknath

Designing REST API with Open API Specification (OAS) v2.0 & v3.0 using Swagger

November 18th 2018

Disclaimer: I presume we all have written one or multiple API at certain point in time in our career, otherwise you would not have bumped into this article. The article does not describe what REST API is, rather you should have some basic knowledge about REST API before going through the article.

Introduction:

Generally when we write REST API, we focus a lot on implementation & very little time on designing the proper request / response schema, API resource models. We jot down just all necessary request & response (in most of the cases HTTP Code 200 series response) parameters in a document, get it reviewed quickly, accordingly we create some resource models & jump into implementation. This strategy works in small to mid-size companies or startup companies. But once you start designing API for larger audience in a enterprise company or consumer internet company, you can’t so easily get away with such minimalist design, a lot of stake holders / teams are involved in the process, a lot of systems might get involved, so a homogeneous & consistent design strategy has to be developed which every stake holder in your organization ( even outside organizations as well ) can relate & contribute to. Open API specification (henceforth called OAS) solves that problem. It’s a standard of describing API comprehensively in machine & human understandable format. The idea is the API should be self sufficient to describe itself with enough granular level details.

Major Advantages of having API design standard:

  1. It forces you to get a ‘Design First’ attitude. Don’t delve into implementation quickly, spend enough time on designing the API, rectify all possible mistake first.
  2. It promotes collaboration. Organisation should have a common standard so that all developers understand the API descriptions & documentation without gaining specialised knowledge for each & every API, API design review becomes better when everyone follows the same standard. A tools ecosystem can be built around this standard which can auto validate the API design, auto generates code & documentation, enhance security for all API.
  3. API consumption become easier if both the API creator & consumer (might be different organizations) expresses the API in a common design standard. So you can standardize API design across teams hence cutting down time on developers’ effort to understand each API in a different way.

A Brief About OpenAPI Standard:

In 2009, a guy called Tony Tam started working on creating a API specification standard what would become Swagger API specification. In August 2012, he published version 1.1 of Swagger spec while working at Reverb Technologies. In March 2014, version 1.2 was published which was more inclined towards JSON Schema Draft 4 standard. This was the first version to gain widespread adoption across the API industry, and is still in use by some API providers today. Swagger specification 2.0 was published in September 2014 with lot of changes & improvement. In March 2015, SmartBear Technologies acquired interest in Swagger intellectual property & other open source technologies from Reverb Technologies. In December 2015, Swagger specification was donated by SmartBear to a new open-governance organization, set up under Linux foundation: the OpenAPI Initiative. These ten companies were the founding member of OpenAPI initiative: 3Scale, Apigee, CapitalOne, Google, IBM, Intuit, Microsoft, PayPal, Restlet and SmartBear. Currently around 30 companies are active member of OpenAPI initiative. In July 2017, the OpenAPI initiative announced the release of OAS v3.0.0, this specification conforms to JSON Schema Draft 5 & it introduced some changes around the schema, introduced new concepts like links, callbacks etc.

A Brief About Swagger:

Swagger is a tools ecosystem built around OpenAPI specification. Following are some capabilities of different Swagger tools:

  1. Swagger Editor: Swagger provides both online ( called SwaggerHub ) & offline (downloadable UI) interface where developers can write API specification in YAML format & the editor validates the design in real time, checks compatibility with OAS standard, detects errors on the fly & shows them visually. So it simplifies API development & helps developers to accurately model resources & APIs.
  2. Swagger Codegen: This tool can generate API boilerplate code & API models (server stubs) etc from the specification in 20+ languages. This greatly reduces developers’ effort to manually write those code. Swagger Codegen can generate client side code to consume the APIs. This tool can generate client side SDK as well in-case developers want to use SDK at the client app.
  3. Mocking API: SwaggerHub provides the capability to mock the APIs defined in the specification on the fly. This facility greatly helps to test APIs in a cleaner way without spending extra money & time to create mock servers during the development life cycle. The offline Swagger UI does not provide this capability.
  4. API Documentation: Maintaining API documentation manually is hard as API keeps evolving & for every minor change you don’t need to create a new API version. Swagger has out of the box capability to create & sync documentation from OAS. In case you want to generate documentation for already existing API, you can use Swagger Inflector to create documentation in run time using annotations or using Swagger Inspector , you can hit an API end point & generate open API specification from this interface & generate documentation from that specification as well.
  5. API Testing: Using Swagger Inspector, you can hit end point with proper request & check out the response.

In many cases, your company might not allow you to use SwaggerHub to maintain API because SwaggerHub is not free (free till a very limited use) & your organization might not trust it. So in order to facilitate the development process, you might need to install Swagger UI in your local machine. Follow the instructions below to create a local Swagger environment where you can non-restrictively create & maintain APIs:

  1. We will install Swagger UI & Swagger Editor using docker. So install docker first. Download docker here, follow the instructions according to your operating system.
  2. Install Swagger UI, run: docker pull swaggerapi/swagger-ui
  3. Run Swagger UI: docker run -p 8000:8080 swaggerapi/swagger-ui .
    This will run the UI on port 8000. Go to browser & type: http://localhost:8000. You will see Swagger UI running.
  4. Install Swagger Editor: docker pull swaggerapi/swagger-editor
  5. Run Swagger Editor: docker run -p 81:8080 swaggerapi/swagger-editor. This will run the editor on port 81.
  6. Create a project folder in any favourable location. I have created under /home/kousik/gitrepo folder ( I have Ubuntu machine). My project name is: DummyApiSpec. So the complete directory path of the project is: /home/kousik/gitrepo/DummyApiSpec. You can clone my project & see the code: https://github.com/kousiknath/OpenAPISpecExample.git
  7. Swagger UI needs a URL to fetch API specification. Since we are hosting Swagger locally, we have to serve the specification file from localhost server. Hence we will install a Simple HTTP file Server in python & use that to serve any file which resides in either /home/kousik/gitrepo or any of its child directories. Go to the folder /home/kousik/gitrepo & create a file called server.py & paste the following code:
#!/usr/bin/env python
try:
# Python 3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test as test_orig
import sys
def test (*args):
test_orig(*args, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
except ImportError: # Python 2
from BaseHTTPServer import HTTPServer, test
from SimpleHTTPServer import SimpleHTTPRequestHandler
class CORSRequestHandler (SimpleHTTPRequestHandler):
def end_headers (self):
self.send_header('Access-Control-Allow-Origin', '*')
SimpleHTTPRequestHandler.end_headers(self)
if __name__ == '__main__':
test(CORSRequestHandler, HTTPServer)

You can go to the folder containing server.py file & run this simple server issuing the following command:

python server.py

This will by default run the server at port 8000. In order to run the server at any particular port, you can run it like:

python server.py 8100

The above command will run the server at port 8100. This creates a local HTTP file server that can serve any file residing under its parent or any nested sub-directory. The parent directory acts as the base directory of web server, other paths must be relative to this base directory. Since my project DummyApiSpec & server.py reside under /home/kousik/gitrepo , I can access the API spec from localhost like: http://localhost:8100/DummyApiSpec/openapi-3.0/schema/spec.json. Arrange the directories accordingly & make sure that you are able to access the API spec file from localhost.

Hands-On with OpenAPI specification:

We will create a API specification to better understand different aspects of OAS v2.0 & v3.0. 
Let’s say you want to create a User service (micro service) which owns all user data. Say this service has a functionality to create a random user which only works in a sandbox / testing environment. You can also get specific user details by querying the service with the corresponding user id. User details contains id, name, date of birth, gender, contact details, user home location, user device preferences etc. We will create the Open API specification for creating & getting the user details using Swagger.

Go to the location where you created the project. In my case the project name is DummyApiSpec & location is: /home/kousik/gitrepo/DummyApiSpec.
The directory structure should look like as shown in the below image:

I am using IntelliJIDEA, any IDE should have the same structure. The schema folder under openapi-3.0 contains the specification file spec.json defined in accordance with OAS v3.0. The file spec.json defined inside swagger-2.0 is defined according to Swagger-2.0 specification, remember OAS v3.0 is derived from & improved version of Swagger Specification v2.0. The components folder contains all reusable API resource models in separate JSON files. For the time being you can just create the folders as shown in picture. You can ignore gradle related folder & files.

We will see how to create OAS specification in both v2.0 & v3.0 below & compare them.

Creating Random User Generation API with OAS v2.0:

OAS v2.0 is the most popular OAS version used today. It has the following schema structure. All the colourful rectangular blocks represents different component at the global / root level in the specification.

Following gist is JSON representation of OAS v2.0 of our random user generation API. Let’s decode different components.

The first section in the JSON file is 'swagger' which represents which specification version does the file represent.

The key info maps to a object that contains basic information about the API like API version, title, basic description, developer contact details etc. Put your own details accordingly.

The keys schemes, host, basePath together represents the API server URL where the API is supposed to be hosted. So according to the above spec, the API server URL is: https://your-domain.com/api/1.0.0. When you use Swagger UI or SwaggerHub to test any API mentioned in the specification file, they internally use this API server URL for testing & all API requests hit this address to get data.

The section securityDefinitions represents all security schemes which are supported by our API, it does not apply any of these scheme to any API, it only defines the available schemes. Swagger / OAS v2.0 standard supports the Basic Authorization, API key based authentication through header or query parameter & OAuth 2.0 authentication. It’s up to you which scheme you use for your API, you can use a mix of authentication schemes for different API defined in the same specification file, I have shown here all schemes for example purpose.

securityDefinitions mapped object contains many keys like BasicAuth, ApiKey, AppId, SessionKey, OAuth2 — these are random names, you can put any name to represent the security schemes, the main thing that matters is the objects represented by those keys: 
BasicAuth represents Basic Authorization through built in HTTP mechanism. HTTP supports sending an Authorization header that contains ‘Basic’ word followed by a space & base 64 encoded username:password string. Example Authorization header: Authorization: Basic 63jYu7uu38uqt356q=. This mechanism is not secure as base 64 encoded strings can be decoded as well, use HTTPS to make it more secure.

ApiKey / AppId / SessionKey maps to a JSON objects that represent authentication key value pair passed through header keys:

"ApiKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-KEY"
},
"AppId": {
"type": "apiKey",
"in": "header",
"name": "X-APP-ID"
},
"SessionKey": {
"type": "apiKey",
"in": "query",
"name": "SESSION-ID"
}

Here, the key type has the value apiKey , it’s a Swagger / OAS defined type, the key in represents where the key has to be passed — either header or query parameter section, name represents the name of the key.

OAuth2 represents OAuth2 standard authorization scheme:

"OAuth2": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://your-auth-domain.com/oauth/authorize",
"tokenUrl": "https://your-auth-domain.com/oauth/token",
"scopes": {
"read": "Grants read access to user resources",
"write": "Grants write access to user resources",
"admin": "Grants read and write access to administrative information"
}
}

OAuth2 requires some scope based on clients will be granted permission to access the corresponding resource.

The security section defines which authentication / authorization scheme is going to be imposed on which API. We have defined global security section at the root level that is inherited by all API defined in the spec, we will see how we can override authorization mechanism at individual API level later. security is a list of schemes wrapped in JSON objects like:

"security": [
{
"OAuth2": ["read"]
}
]

When multiple JSON objects are present inside the list, schemes represented by any of the JSON object will work. Multiple Schemes represented as separate JSON objects maintain logical OR relationship. Example: Look at the security section of the GET /users/{user_id} API:

"security": [
{
"OAuth2": ["read"]
},
{
"ApiKey": [],
"ApiId": []
},
{
"BasicAuth": []
},
{
"SessionKey": []
}
]

In the above list, four authentication schemes are applicable for this API for demonstration purpose. All these schemes are in logical OR relationship. So proving proper data to any of these schemes will pass the authorization check. For logical AND relationship, you can put multiple schemes inside the same JSON object. Like consider the following portion in the above code snippet:

{
"ApiKey": [],
"ApiId": []
}

You need to provide both the API key & API Id to pass this combined authorization since both are part of the same JSON object — this is logical AND relationship.

In the JSON object, the key name should be exactly equal to any of the scheme name defined in the securityDefinition section i.e; in this example, the key name is OAuth2 which is registered inside securityDefinitions , the value is a list of scopes i.e; in the above case, read scope is specified.

More on Swagger / OAS v2.0 authentication here.

The keys consumes & produces maps to list of MIME Types that the APIs can consume in a request body ( not for GET request obviously ) & produce as a response. The top level MIME Types are inherited by all defined APIs although individual API can override these types.

More on Swagger / OAS v2.0 MIME Types here.

paths section describes all API end points & operations. Let’s explore the /user API to understand all aspects.

"/users": {
"post": {
"description": "User provides some basic details & this api creates a random user with those details",
"summary": "POST api to create a random user",
"operationId": "generate_user.post",
"deprecated": false,
"produces": [
"application/json"
],
"security": [
{
"OAuth2": ["read", "write"]
}
],
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"description": "The user provided basic details will be used to create a random user",
"schema": {
"$ref": "#/definitions/UserBasicDetails"
}
},
{
"$ref": "../../components/header_request_id.json"
},
{
"$ref": "../../components/header_client_id.json"
}
],
"responses": {
"201": {
....
},
"400": {
....
},
"409": {
....
},
"tags": [
"users"
],
"externalDocs": {
"url": "https://your-domain.com/api/docs/users",
"description": "See more on user operations."
}
}
}

In OAS terminology, API end point like /user is called path, all paths are relative to the API server URL as mentioned earlier & associated HTTP verbs are called operations. Since we want to create a random user, our /user end point is associated with post operation ( HTTP verb POST equivalent).

Swagger 2.0 supports get, post, put, patch, delete, head, and options.
Swagger defines a unique operation as a combination of a path and an HTTP method. This means that two GET or two POST methods for the same path are not allowed — even if they have different parameters (parameters have no effect on uniqueness).

The keyword operationId is optional, but if provided, it should be unique for each operation.

deprecated indicates whether the API should be decommissioned soon, it’s useful if the API is already being used by some client, but you have a new version or a alternative API.

We have used produces to specify a list of MIME Types in global / root level already, we have again specified the same in the post operation of /user API just to show how to override properties inherited from root level. Once overridden, the root level features have no impact on this operation. You can override consumes also in a similar way.

We have overridden security schemes as well inside this operation, since we are creating user here, we have used OAuth2 security scheme with read & write scope only. Once overridden, the global level security schemes will not be imposed on this operation any more.

The parameters section is a list of parameters represented as JSON objects. The in key of the parameter object indicates the location where the parameter is expected. Based on location, following are the type of parameters:

Body Parameters: This kind of parameter is expected as part of request body, the location is signified by "in": "body" inside the parameter object. Not applicable for HTTP GET request.

Query Parameter: If you want to expose parameters in URL like: /api/users?attributes=location,devices, here attributes is a query parameter, it’s signified by "in": "query" inside the parameter object.

Path Parameter: If you want to represent API path as a URL like this: /api/user/{user_id}, here user_id wrapped inside {} is a mandatory path parameter. It’s signified by "in": "path" in the parameter object.

More on Swagger v2.0 parameters can be found here.

required means whether the parameter is mandatory, it’s mandatory when the key’s value is set to true.

schema is used to describe primitive values, as well as simple arrays and objects serialized into a string.

$ref lets you refer to content defined in different section like global definitions section in the same specification file or another JSON file in a different directory in the same server or file hosted in another server etc. In our specification, the following code snippet describes that the request body of this particular API operation exists under global definitions section as a JSON object mapped to the key UserBasicDetails.

"schema": {
"$ref": "#/definitions/UserBasicDetails"
}
The value of $ref uses the JSON Reference notation, and the portion starting with # uses the JSON Pointer notation. This notation lets you specify the target file or a specific part of a file you want to reference.

In #/definitions/UserBasicDetails , the resolving starts from the root of the current specification file, then definitions section is found under the root, UserBasicDetails is found inside this definition section. So it’s similar to file system directory structure resolution.

$ref can also refer to any JSON file lying under any directory, you just need to provide the relative path of that JSON file with respect to the current JSON file. Example:

{
"$ref": "../../components/header_request_id.json"
}

The above snippet means, there exists a file called header_request_id.json in the directory called components which exists in parent of parent of our specification file, refer to the directory structure image to understand properly.

Caution: When $ref is used in a section, all it’s siblings are ignored, this is because $ref replaces itself & all it’s siblings by the value it’s pointing to. So in the following code snippet, the description, title or any attribute residing in the same level of $ref is ignored.

"schema": {
"$ref": "#/definitions/UserBasicDetails",
"description": "A reusable user basic details component",
"title": "xyzab"

}

More details on $ref here.

The responses section under any API operation defines all the possible response schema for that operation. It’s a JSON object where key names are HTTP status codes like 200, 400 etc & mapped values define the schema & metadata of that response object. Your API response for that particular HTTP status code will exactly follow the same structure. Let’s see the response structure for HTTP status 201 when the user object gets created:

"201": {
"description": "",
"headers": {
"X-RateLimit-Limit": {
"description": "Number of request per time window (hour / 5 min / 15 min)",
"schema": {
"type": "number",
"format": "integer"
}
},
"X-RateLimit-Remaining": {
"description": "Number of request remaining per time window (hour / 5 min / 15 min)",
"schema": {
"type": "number",
"format": "integer"
}
}
},
"schema": {
"$ref": "../../components/user.json"
}
}

Response structure has general description about the response, header & actual response schema. In our API, X-RateLimit-Limit & X-RateLimit-Remaining headers are defined with their own description & schema, you can put any suitable header key & their type as shown above.

Again the actual response body resides in user.json file in the components folder & we use $ref to refer to that file relative to the current file (here our spec.json file path) path.

The tags section inside an API operation contains list of tag names which are used by tools like Swagger UI to logically group operations in the UI. It’s nothing but a grouping of operation in the UI for better clarity. It’s an optional field.

In the end of the specification file, there is a global tags sections defined which is array of JSON objects:

"tags": [
{
"name": "users",
"description": "User related operations exposed by this service",
"externalDocs": {
"url": "https://some-domain.com/users/operations"
}
}
]

This section is also an optional field which describes all tags used in the individual API operation level as we saw earlier. We used users tag in individual API operations as shown already, here we describe those tags with name, description & externalDocs for further information if any. The tag name in these objects should exactly match the individual API level operations’ tags. If any tag name mismatch is there in the global tags objects, an unnecessary group will be created & shown in the UI.

The section externalDocs define extra documentation for API operations & tags. This is optional field.

Representation of reusable Objects / API components:

In real life, many of our API which are correlated to the same business problems or domain end up using many common components or resource models. So it does not make sense to write those component again & again for each specification, rather you can place them in a common module or a git repository & reuse them. In our directory structure, the component folder holds all reusable components. Reusable components can be defined in the following ways:

1. “definitions” section: Swagger / OAS 2.0 defines a global definitions section where you can define all resource models. Example: in our specification, we defined UserBasicDetails model just for demonstration. You can define any number of models as per your need.

"definitions": {
"UserBasicDetails": {
"title": "UserBasicDetails",
"type": "object",
"properties": {
"name": {
"example": "Tony Stark",
"type": "string"
},
"gender": {
"example": "Male",
"type": "string"
}
},
"required": [
"name",
"gender"
]
}
}

We referred to this section from the parameters section of POST /users api like following:

"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"description": "The user provided basic details will be used to create a random user",
"schema": {
"$ref": "#/definitions/UserBasicDetails"
},
......
]

#/definitions/YourModelName refers to YourModelName under the global definitions section.

2. Accessing models residing in a different directory: We have already talked about placing models in any directory like components in our case & accessing JSON files residing in those locations through relative path like:

"schema": {
"$ref": "../../components/user.json"
}

This method is particularly useful when you don’t want to put all models in the same specification file rather maintain a modular directory structure.

Some Important Data Types:

Let’s see the content of the file components/dob.json which represents date of birth of a person:

{
"required" : [ "age", "date" ],
"type" : "object",
"properties" : {
"date" : {
"maxLength" : 50,
"minLength" : 1,
"type" : "string",
"format" : "date-time"
},
"age" : {
"type" : "number",
"format" : "integer",
"minimum": 10,
"maximum": 60
}
}
}

We have two properties here. The date property represents date of birth of the person. Type of this field is string , we have specified minLength & maxLength attributes of this field, those are optional, but it’s good to have boundary. The format field is a hint how to show the data in the UI or treat the data.

The age is of type number with integer format. Other available format for type number are: float, double, int32, int64. The attributes minimum & maximum are the boundary in which age should exist.

Defining array:

Our components/devices.json file looks like below:

{
"type" : "array",
"items" : {
"$ref" : "./device.json"
},
"minItems": 0,
"maxItems": 20
}

The type of the object is array & array objects are placed under items field. You can define objects inside items or can refer to other existing JSON file using $ref as shown above. minItems & maxItems define the limit of total items in an array, these are optional fields, but it’s good to know limits.

More on Swagger (v2.0 & v3.0 both) data types can be found here.

Defining enum:

Have a look at the GET /users/{user_id} API. In the parameters section, we have defined a minimal parameter:

{
"name": "minimal",
"in": "query",
"description": "When set to true, returned user object will have only minimum necessary fields, otherwise verbose object will be returned",
"required": false,
"type": "boolean",
"enum": [true, false]
}

The parameter identifies whether the object representation in the API response should be minimal with extremely necessary data or complete representation. It’s a boolean field, by default set to false. The boolean values are defined as enum list, the values defined inside enum field must match the type defined of the parameter which is boolean in this case.

There is another way to define enum — using vendor extension. Take a look at the components/state.json file:

{
"maxLength" : 50,
"minLength" : 1,
"type" : "string",
"example" : "Karnataka",
"x-enum" : [ {
"value" : "KA",
"description" : "Karnataka"
}, {
"value" : "AP",
"description" : "Andhra Pradesh"
}, {
"value" : "TN",
"description" : "Tamilnadu"
}, {
"value" : "MH",
"description" : "Maharasthra"
} ]
}

Since we have a limited number of states that are supported in our API, for demonstration purpose, we have made state as enum. x-enum is a vendor extension supported by Swagger. It defines enum values as JSON object with value & description hence making the meaning of enum constants meaningful. The enum’s value is of type string here with maximum length of 50.

More on enum can be found here.

Now let’s see how Swagger UI shows the API documentation:
Go to browser & hit the URL where Swagger UI is running. In my machine, the URL is: http://localhost:8000. Once the page loads, put the URL for swagger specification file in the top bar text box & click on ‘Explore’ button. My spec file URL:

http://localhost:8100/DummyApiSpec/swagger-2.0/schema/spec.json
image1

In the above figure, HTTPS appears as http scheme because in our API spec, we defined schemes as ["https"]. The users section lists out all API tagged with users. Since we used same users tag for both the APIs, they appear in the same section. The Models section shows all the API models / resources defined under the global definitions section. Since we defined only UserBasicDetails in the definitions section, that appears here.

Once you click the Authorize green bordered button, it will list out all available authorization schemes that we defined under securityDefinitions section.

Let’s click on the green colour POST /users API section, it will expand & look like:

image2

This green section represents the HTTP verb / operation at the top of this section — here it’s POST, then in the next section, description & externalDocs data is rendered. In the Parameters section of the page, all body, header & query parameters are shown with a red asterisk (*) meaning that the parameter is required. The supported content type of the request body is shown as a drop-down just below the body section.

Let’s click on the Try It Out button on the right side. We see the following UI:

image3

The UI opens up editable text area for body & text fields for other applicable parameters like header & query (not shown here since it’s a POST API). You can insert proper data & click on Execute blue coloured button, the UI will hit the server described in the specification file & show the response. This UI can be used to test API during the development time. Click on red Cancel button & go back the the previous UI — image2.

Now in the image 2, click on the Lock symbol (shown at the top right corner of image 2), it will show you the authorization scheme applicable for this API. This applicable scheme comes from the security section either defined inside the API operation or from the global / root level definition. We overrode the security section inside this API to use OAuth2. So the authorization UI shows only OAuth related text box. Put proper data if you want to test the API & your make sure your OAuth URLs as described in the specification file work.

image4

Close the UI & come back to image2. Scroll down to the Responses section which shows all response defined in the specification file with example values as provided in the spec. If no example value is provided for a field, the type of the field like string, integer will become its value.

image5

In the above image, the left column corresponds to HTTP status code as described in the responses section of the API spec, the right side drop-down shows the corresponding data type of the response object, it comes from the consumes section of the spec file. Just above the black colour section, you can see Example Value is in bold in this image meaning that the black colour response body section only shows sample response, you can click the Model link next to it, the models associated with the response will be shown.

After the body section, all headers with their description & type are shown. In our API spec, we are returning X-RateLimit-Limit & X-RateLimit-Remaining headers. You can return anything.

The 400 section errors has the following model & example value:

image6

In a similar way, the Try It Out UI of the GET /users/{user_id} looks like:

image7

Since this is a GET API, there is no body (request body) parameter in the Parameters section, only path, query & header parameters are there. You can see a drop-down containing true / false values for the boolean field minimal, these values come from the enum defined for the minimal query parameter in the specification file.

You have now fair idea about how Swagger documentation looks from UI perspective, once you install Swagger locally or use SwaggerHub, you can play around with the UI & explore more.

OAS v3.0 specification of the same API:

Basic structure of OAS v3.0 specification:

This is how the Open API Specification v3.0 version of our API specification looks like:

OAS v3.0 has made some changes in structuring of the file.

No schemes, basePath or host parameters are used to describe server address or API base URL in OAS v3.0, rather the following is used:

"servers": [
{
"url": "https://your-domain.com/api/1.0.0"
}
]

So instead of one, you can accommodate multiple servers.

OAS v3.0 emphasises on re-usability because multiple API / API operations can share same parameter, request & response body & other metadata. So OAS v3.0 defines a global components section & it puts the following re-usable optional sub-sections inside it:

components:
# Reusable schemas (data models)
schemas:
...
# Reusable path, query, header and cookie parameters
parameters:
...
# Security scheme definitions (see Authentication)
securitySchemes:
...
# Reusable request bodies
requestBodies:
...
# Reusable responses, such as 401 Unauthorized or 400 Bad Request
responses:
...
# Reusable response headers
headers:
...
# Reusable examples
examples:
...
# Reusable links
links:
...

Any model residing in this section can be accessed like:

"$ref": "#/components/requestBodies/YourComponent" 
or
"$ref"
: "#/components/examples/YourComponent"

If you plan to use multiple files describing the models, then you can access as shown already earlier like:

"$ref": "relative/path/to/your/component_file"

More on components here.

OAS v3.0 uses parameters section inside an API operation to describe path, query, header & cookie parameters. Cookie parameter is newly introduced in OAS v3.0. Request body or body type parameter is no more supported in this section. For request body, a new section called requestBody is introduced inside API operation section, this new section can take text based request body data and form data.

More on parameters here.

More on request body here.

There is no securityDefinitions in OAS v3.0, it’s renamed tosecuritySchemes & has been moved under global components section. A new security scheme is introduced as well — cookie based scheme. It’s described as below:

"CookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "SESSION_ID"
}

Like all other security schemes, it also can be used in global or API operation level like below:

"security": [
{
"CookieAuth": []
}
]

Here the name inside the security objects should exactly match the name of the security scheme described inside the securityScheme.

There has been some changes in how basic auth scheme is defined, type: basic has been replaced with type: basic, scheme: http. All http based security mechanisms like basic or bearer token based auth have been moved to type http. This is how basic http mechanism is described now:

"BasicAuth": {
"type": "http",
"scheme": "basic"
}

More on authentication here.

Let’s see the OAS v3,0 specific changes in POST /login API:

The request body section is defined quite differently in OAS v3.0:

"requestBody": {
"content": {
"application/json": {
"oneOf": [
{
"$ref": "#/components/requestBodies/EmailLoginRequest"
},
{
"$ref": "#/components/requestBodies/MobileLoginRequest"
}
]
}
}
}

Actual request body structure is defined inside corresponding content type like application/json or text/plain or something similar. All these content types are contained inside content. So this makes content negotiation very clear to describe, there is no requirement of consumes array any more, you are free to choose content type & their corresponding body & you can describe all of them as above. Open API v3.0 provides support of validating request body schema through a collection of schema, the keywords such as — oneOf, allOf, anyOf are built for those purpose. These keywords take a list of schema & checks accordingly if the request body schema matches the schema. oneOf checks if it given request body schema matches exactly one of the given schema in the list, anyOf check if it matches any of the given schema, allOf checks whether it matches all of the schema. So in our example, the request body schema of /login API should match exactly with only one of the provided schema — EmailLoginRequest or MobileLoginRequest.

More on oneOf, allOf, anyOf support here.

OAS v3.0 introduced callback support. The callback section is defined for POST /login API as below:

"callbacks": {
"loginEvent": {
"{$request.body#/callback/url}": {
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Login happened, please process the event."
}
}
} }
}
}
}
}
}
}

Our /login API takes a callback URL, when some user logins, we shall call that callback URL with appropriate data & inform about the login event. This example is for demonstration purpose. Here inside the callbacks section, we define a random event name called loginEvent , you can define any name. The event defines which call back URL will be called & with what data. The {$request.body#/callback/url} signifies that our request body has a section called callback, under that section, url key exists. This expression is evaluated in run time & it retrieves the actual callback URL passed in the request body through that key. The post section under the callback URL describes how to form the request body to invoke the callback URL.

More on callback & run time expression evaluation here.

Let’s consider the responses section for /login API:

"responses": {
"200": {
"headers": {
"Set-Cookie": {
"description": "Session key is set in cookie named SESSION_ID",
"schema": {
"type": "string",
"example": "SESSION_ID=hfuy8747b7gb4dgy466t46; Path=/; HttpOnly"
}
}
},
"description": "Successful login",
"content": {
"application/json": {
"schema": {
"$ref": "../../components/user.json"
},
"examples": {
"ex1": {
"id": "90d640ab-548a-4a72-a89e-b86cdf4f1887",
"gender": "Male",
"name": {
"title": "Mr.",
"first": "Kousik",
"last": "Nath"
},
"contact": {
"email": "you@your-domain.com",
"phone": "140000",
"cell": "9090909090"
}
},
"ex2": {
"id": "4f7386f6-3c89-11e9-b210-d663bd873d93",
"gender": "Female",
"name": {
"title": "Mrs.",
"first": "XYZ",
"last": "ABC"
},
"contact": {
"email": "xyz@abc.com",
"phone": "111200",
"cell": "9090909090"
}
}
}
}
},
"links": {
"GetUserById": {
"$ref": "#/components/links/GetUserById"
}
}
},
...
...
}

In the above code snippet, we are returning"Set-Cookie" header so that for cookie based authentication, this cookie can be used till it’s expired.

There is no produces section in this specification file, rather the content negotiation is made simpler by putting the response schema under particular content type like application-json under content mapped to individual response code like 200 in the above snippet. You can use as many as content-types along with their content description.

Examples can be added to parameter, object, properties etc to make the API specification clear as examples describe what value a field can take. In OAS v3.0, the example is enhanced. You can now use examples — a JSON object of examples. All keys in this JSON object are distinct & their mapped value describe examples. You can define example schema under the global components/examples section and re-use them by referring to them with $ref.

More on examples here.

Links are one of the new features of OpenAPI 3.0. Using links, you can describe how various values returned by one operation can be used as input for other operations. This way, links provide a known relationship and traversal mechanism between the operations. The concept of links is somewhat similar to hypermedia, but OpenAPI links do not require the link information present in the actual responses.

We have defined links section under the global components section:

"links": {
"GetUserById": {
"description": "Retrieve the user with GET /users/{user_id} API using `id` from the response body",
"operationId": "users.get",
"parameters": {
"user_id": "$response.body#/id"
}
}
}

We have defined links in the POST /users also. So once a new user is created through POST /users or user logs in through POST /login API, we expose the operation to retrieve the created / logged in user through GET /users/{user_id API. We have defined GetUserById field inside links section which maps to the a JSON object that declares which operation we are going to expose as a hypermedia link. The operationId here — users.get points to the GET /users/{user_id} API, since both of the APIs are defined in the same specification file, we can use operationId, if APIs are defined in different specification file, we have to use operationRef. More details on the link below. The parameters section inside this JSON GetUserById describes what parameter you need to send while calling the exposed API, in this section, we basically compute any sort of parameter / request body that has to be sent to the exposed API. The $response.body#/id retrieves the id field from the current API response body in run time.

More on links here.

OpenAPI v2.0 is quite a wide spread standard, organizations are moving to OAS v3.0 slowly, but the good part of using such specification is that it scales the API design process, it’s absolutely necessary for big organizations. This post describes API designing both in OAS 2.0 & OAS 3.0, rest is up to you, if you can relate to the benefits of such design, just go for it.

References:

  1. https://swagger.io/specification/
  2. https://swagger.io/blog/news/whats-new-in-openapi-3-0/
  3. https://github.com/kousiknath/OpenAPISpecExample.git

More Related Stories