OpenAPI (and Swagger)
Notes from a LinkedIn Learning Course on Specifying APIs with Swagger tooling and the Open API standard.
Key Links
-Learn the spec by drilling down into the nested objects in the schema on this page.
Swagger is a toolset and structured approach for creating API designs, documentation, and code through the API lifecycle.
The Open API specification is a standard format for metadata used to define RESTful services. The metadata is machine readable.
The Swagger tools include open source tools like Swagger Editor, Swagger UI and Swagger Codegen. The pro tools are made for teams, and publishing, Swagger Hub and Swagger Inspector.
Swagger Editor is an IDE for API definition files. demo
Swagger UI consumes those definition files to build interactive documentation. demo
Swagger Codegen builds SDKs from API definitions. For demo see the top menu of the editor demo above.
The OpenAPI Specification (OAS) defines a standard, programming-language-agnostic interface description for REST APIs.
The problem it’s solving is inconsistent ways of describing APIs makes integration harder. With an industry standard, we can now share our API’s interface with people and machines in a predictable way.
To install Swagger you download the source from github, put them somewhere and then point a server at them. EG the node server could be executed in bash like this:
alias swagger="http-server ~/Apps/swagger-editor/ -a 127.0.0.1 -p 8080"
alias swaggerui="http-server ~/Apps/swagger-ui/dist/ -a 127.0.0.1 -p 8081"
To define an Open API spec with Swagger you need to know the specification
we start with the minimal requirements in the spec. We define the standard of the spec we’re using, and some info and paths:
openapi: 3.0.0
info:
title: My First API
version: 1.0.0
paths:
/product:
get:
responses:
200:
description: This is a list of products in the catalogue
content:
application/json:
schema:
type: array
items:
properties:
id:
type: integer
example: 300
name:
type: string
example: Lemon Water
The most interesting thing here is the paths. The paths list the routes of the API. Each path object has a series of http methods that it supports. Each method has a series of responses. Those responses contain a description of their purpose and then an object describing their media type and schema.
So the tree goes like this:
paths (eg /product)
methods (eg get, post)
responses
description followed by content
media type eg application/json
schema of the response
We can use tags to organize our routes. We define them just ahead of the paths like this:
tags:
- name: Article
description: Articles for the Blog
paths:
/article:
get:
tags:
- Article
responses:
...
The result will be organized blocks of routes.
The OpenAPI spec in more depth
There are seven sections (or objects) that can be contained at the root level:
info
(required)Provides metadata about the API like description, terms of service, contact info, license, version.
servers
Provides metadata about the servers hosting the API. Used to provide urls for the different environments, eg dev, UAT, prod.
security
Describes how the API is secured by specifying auth flows, keys etc.
paths
(required)Describes the endpoints of hte API and the different operations that can be performed on those endpoints. The largest part of the spec.
tags
Provide tags to group the API operations logically.
externaldocs
Supply additional metadata and links to external API documentation.
components
Defines a set of reusable objects that can be referenced throughout the API. EG parameters, response bodies. Prevents repeated defintions.
Within these, objects can be nested to build out the full API spec.
Most commonly you’ll encounter these written in yaml, but JSON is also supported.
The paths
and components
objects are the two most important. Most of the work is done here.
More details can be found by reading the spec on github.
Drilling down into the most important objects in the openapi schema, the schema object itself is really important. Get to know it well, it is described here
Query Parameters
Typically APIs will include paging and other query parameters.
Parameters like this go under the http verb in swagger.
We can specify an array of parameters with the - in: <type>
where type is either query (query parameter), header, path, or cookie.
paths:
/product:
get:
parameters:
- in: query
name: pageNumber
description: Page number to return
required: false
schema:
type: integer
example: 1
- in: query
name: pageSize
description: Number of items in page
required: false
schema:
type: integer
example: 10
maximum: 100
Path/Template Parameters
REST APIs are typically based around path parameters like /product/{productID}
These are listed in the OpenAPI spec as separate paths (so you’d need both /product
and /product/{productID}
in your paths.
Then you can specify the path parameter sa follows, note that the param must be required, and the name must match the name in curly braces in the path itself. Responses are required.
This example also includes an example of a header parameter
An example:
/product/{productId}:
get:
tags:
- Product
parameters:
- in: path
name: productId
required: true
schema:
type: integer
description: The id itself
example: 3
- in: header
name: custom-security-header
required: false
schema:
type: integer
example: 12312-13114141-141414141
responses:
200:
description: This is a product item in the catalogue
content:
application/json:
schema:
type: object
properties:
id:
type: integer
example: 300
name:
type: string
example: Lemon Water
Request Bodies
It is straightforward to define the required request body in operations like POST
, here’s an example:
/product:
post:
description: Add a product to the catalogue
tags:
- Product
requestBody:
content:
application/json:
schema:
type: object
properties:
id:
type: integer
example: 300
name:
type: string
example: Lemon Water
responses:
200:
description: The product has been created.
Components
Already we see that we’re repeating ourselves a lot, every time a product is mentioned it’s the same object essentially. We can stay DRY by using components in the OpenAPI 3.0 spec.
These describe re-usable components like parameters or schemas that can be referred to in our definition. They sit at the top level of the document tree.
Within the components
object we can specify the schemas
of each component, for example:
components:
schemas:
Product:
type: object
required:
- name
properties:
id:
type: integer
example: 300
name:
type: string
example: Lemon Water
Note that we’ve added validation now, we can specify in the required
array which properties are required when the object is used.
Then you can reference the component by its name inside the schema (or items of an array) using the $ref:
property like this:
responses:
200:
description: This is a product item in the catalogue
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
application/xml:
schema:
$ref: '#/components/schemas/Product'
Now you can update the product schema in one place. and support multiple media types.
You can define more than just schema components. You can define standard responses, parameters, headers, requestBodies, examples, securitySchemes, links, and callbacks.
EG for responses, you can define standard errors and then reuse them.
components:
responses:
500ApiError:
description: This is an unexpected error
content:
application/json:
schema:
type: object
properties:
statusCode:
type: string
example: 500
message:
type: string
example: This is an error
Then you can say:
responses:
500:
$ref: '#components/responses/500ApiError'
Parameters are also highly reusable so make sense to include in components:
/product:
get:
parameters:
- $ref: '#/components/parameters/PageSize'
- $ref: '#/components/parameters/PageNumber'
components:
parameters:
PageNumber:
in: query
name: pageNumber
description: Page number to return
required: false
schema:
type: integer
example: 1
PageSize:
in: query
name: pageSize
description: Number of items in page
required: false
schema:
type: integer
example: 10
maximum: 100
Docs Through Swagger UI
Swagger UI download has a dist
folder. To serve your yaml definition in the UI you need to:
make a copy of the dist folder. Rename it to something descriptive
put a copy of your yaml definition in the copy.
open the
swagger-initializer.js
file and change the url of the definition file to the file in the local folder.now serve the folder.