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.
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.
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.
Swagger is a tools ecosystem built around OpenAPI specification. Following are some capabilities of different Swagger tools:
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:
docker pull swaggerapi/swagger-ui
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](http://localhost/):8000
. You will see Swagger UI running.
docker pull swaggerapi/swagger-editor
docker run -p 81:8080 swaggerapi/swagger-editor
. This will run the editor on port 81./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.gitlocalhost
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 pythontry:# Python 3from http.server import HTTPServer, SimpleHTTPRequestHandler, test as test_origimport sysdef test (*args):test_orig(*args, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)except ImportError: # Python 2from BaseHTTPServer import HTTPServer, testfrom 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](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.
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.
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](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.
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.
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.
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](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.
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:
schemas:...
parameters:...
securitySchemes:...
requestBodies:...
responses:...
headers:...
examples:...
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": "[email protected]","phone": "140000","cell": "9090909090" }},"ex2": {"id": "4f7386f6-3c89-11e9-b210-d663bd873d93","gender": "Female","name": {"title": "Mrs.","first": "XYZ","last": "ABC" },"contact": {"email": "[email protected]","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: