The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.
In this tutorial, you will document your API using the OpenAPI specification (v3). An OpenAPI file is a JSON or YAML file that follows the OpenAPI specification. This specification defines what fields your JSON/YAML file must contain and how it will be reflected on the documentation service you’ll use to host it. Many services support OpenAPI, so you can pick and choose, or even use multiple services, without having to change your API documentation’s format.
To create the documentation, you’ll use Insomnia, a free and open-source application that allows you to test your API and design the documentation with a real-time side-by-side preview. Insomnia doesn’t support JSON, but it does make it easy to write YAML. YAML is a good choice for API documentation because these documents can get very large, and a JSON document would get cluttered and hard to read.
Finally, you’ll host the API documentation with Redoc, an open-source application used by many companies. Redoc takes the OpenAPI document you generated and gives you an HTML page that displays a nice-looking and interactive version of your documentation. You’ll also deploy your Redoc generated site to GitHub Pages, which is a free website hosting solution by GitHub.
In this tutorial, you will learn more about OpenAPI, document your API according to the OpenAPI Spec in Insomnia, and host this documentation on GitHub Pages with Redoc.
To follow this tutorial, you will need:
In this step, you’ll note the routes your API accepts and their relevant parameters and responses. Since you’ll be documenting the API for others, and because you may also refer back to this documentation in the future, it’s important to note everything you need to document. OpenAPI allows you to define request bodies, headers, cookies, and even possible responses for each API route.
This tutorial will use the JSON Placeholder API, which is a free mock API. Since this API is quite large, you will only be documenting the /posts
section in this tutorial.
The below table shows the method, route path, and description of each of the five routes you will document in this tutorial. Making a table or something similar is helpful so that you don’t forget any route (which can happen if the API is big).
Method | Route | Description |
---|---|---|
GET | /posts |
Get all posts |
GET | /posts/:id |
Get a single post |
POST | /posts |
Create a post |
PUT/PATCH | /posts/:id |
Update a post |
DELETE | /posts/:id |
Delete a post |
Now that you know what your API can do, it’s time to begin documenting it with Insomnia.
In this step, you’ll create an Insomnia project. An Insomnia project contains the OpenAPI document, any tests you write for your API, and any requests you’ve created. The interface is split into three tabs: Design, Test, and Debug. You’ll focus on the design tab for this tutorial.
Open the Insomnia app and go to your dashboard. Create a new Design Document by clicking the Create button on the top right of the Insomnia window and give it a name. For this tutorial, you can use json-placeholder-docs.yaml
.
Note: YAML by design only accepts spaces as indentation. Insomnia, however, indents with tabs by default. This will be fixed in a later update, but for now, open your Preferences by clicking the cogwheel icon on the top right, or by pressing Ctrl/Cmd + ,
. In the Font section, uncheck Indent with Tabs and close the Preferences window. This will make Insomnia use spaces instead of tabs.
You should now see three panes, as shown in the following screenshot below. The first pane shows an overview of your document, such as the routes of your API and components you’ve defined (you’ll learn more about those later). The middle pane contains the code editor that you’ll use to write the OpenAPI document in YAML. This editor also detects errors automatically and notifies you of them at the bottom. Finally, the last pane on the right is a real-time preview of the document. You’ll see an error because you still have to tell Insomnia which version of OpenAPI you’ll be using.
In the code editor of the Design tab, add the line: openapi: 3.0.3
. This indicates the version of the OpenAPI spec you will be using. At the time of writing, the latest version is 3.0.3
. Feel free to change this to a later version if you’d like.
Your screen should look similar to this:
Now that you’re familiar with the Insomnia interface, you can begin writing your documentation.
In this step, you’ll learn more about the OpenAPI Specification. An API Specification can be a JSON or YAML file, but Insomnia only supports YAML. It should have a key called openapi
that specifies the version of the OpenAPI Specficiation you’re using.
According to the specification, here are the fields that can be present at the root of the document:
Name | Type | Description |
---|---|---|
openapi |
string |
REQUIRED. Version of the OpenAPI schema. |
info |
Info Object | REQUIRED. An object containing information about the API. |
servers |
Array of Server Objects | An array containing objects that provide connectivity options to an API server. |
paths |
Paths objects | REQUIRED. An object containing the routes provided by the API, methods, request-bodies, parameters and responses. This is the most important part of the document. |
components |
Components Object | Contains reusable components, meant to reduce file size and keep the docs clean. |
security |
Array of Security Objects | Contains a list of authentication mechanisms for the API. Outside the scope of this tutorial. |
externalDocs |
External Documentation Object | Contains any external documentation for the API |
Don’t worry if this is too much to take in. You’ll be diving deeper into each property, except for security
, since that’s outside the scope of this tutorial. The security
field defines authentication methods (e.g., username/password, JSON Web Token, or oauth) for a route, but JSONPlaceholder doesn’t have any authentication features.
info
ObjectIn this step, you’ll use the table from Step 1 to begin write your API’s documentation using Insomnia. You’ll start with the info
object.
The info
object contains information about the API you’re documenting. This includes things like the title
, version
of the API, the API’s description
, links to its knowledge base (documentation
), and its terms-of-service (tos
).
According to the specification, this is what an info
object should look like:
Name | Type | Description |
---|---|---|
title |
string |
REQUIRED. The title of the API. |
description |
string |
A short description of the API. Markdown can be used here. |
termsOfService |
string |
A URL to the Terms of Service for the API. |
contact |
Contact Object | The contact information for the exposed API. |
license |
License Object | The license information for the exposed API. |
version |
string |
REQUIRED. The version of the documentation, not the OpenAPI spec. |
The info
field has two required properties: the title
of the document and the version
of the documentation, which should be equal to the version of your API application. The other fields are present for informing the user about your API.
Now you will add an info
object to your documentation using the three most-used fields: title
, description
, and version
. In the Insomnia app, add the following YAML code to Design tab editor:
info:
title: JSONPlaceholder
description: Free fake API for testing and prototyping.
version: 0.1.0
This is a random version number since JSONPlaceholder doesn’t expose a version number. Feel free to add any other fields to the info
object, following the specification from the previous step.
Warning: YAML is very picky about its indentation. It has to be indented with spaces, and the indent size must be consistent throughout the document.
Now that you’ve added the info
object with basic information about your API, you’ll add the next object: externalDocs
.
externalDocs
ObjectIn this step, you will add the externalDocs
object. This object contains the link to any other documentation the API might have. An OpenAPI document just defines any routes your API has along with its parameters and responses, so it is usually used as a reference. It is recommended to include separate, human-generated docs that explain each action and guides the user. In JSONPlaceholder’s case, there is a guide.
According to the specification, here’s what the externalDocs
object should look like:
Field Name | Type | Description |
---|---|---|
description |
string |
A short description of the target documentation. Markdown can be used. |
url |
string |
REQUIRED. The URL for the target documentation. |
In your YAML document, add an externalDocs
object that points to JSONPlaceholder’s guide:
externalDocs:
description: "JSONPlaceholder's guide"
url: https://jsonplaceholder.typicode.com/guide
You should see the changes reflected in the preview pane on the right side of Insomnia.
You have now linked to external documentation for your API. Next, you’ll add the servers
array.
servers
ArrayIn this step, you’ll add the servers
array, which contains any URLs that the API will be hosted at. The documentation you’re creating will be hosted on a different domain from the placeholder API (that is, your documentation will not be hosted on jsonplaceholder.typicode.com
). Because of this, you can’t implicitly get the URL for the Try It Out buttons next to the API routes shown in the Insomnia preview.
To fix this, OpenAPI provides a servers
field. Add the following lines to your YAML document:
servers:
- url: https://jsonplaceholder.typicode.com
description: JSONPlaceholder
With that, you now have a way to call the API.
paths
ObjectIn this step, you will add the paths
object, which is the heart of your documentation. This object contains all of the routes that are provided by the API. It also contains any parameters, the method, the request body, and all responses of the route.
Each key of the paths object will be a route (/posts
) and the value will be the Path Item object.
According to the OpenAPI specification, this is what the Path Item
object will look like:
Name | Type | Description |
---|---|---|
summary |
string |
An optional summary of this route. |
description |
string |
An optional description of what the route can do. |
get /post /put /patch /delete /etc |
Operation Object | A definition of an operation (method) on this route. |
servers |
Array of Server Objects | An alternative server array to service all operations in this path. |
parameters |
An array of Parameter Object | Parameters that are applicable for all operations on this path. These parameters can be on the querystring, header, cookie, or the path itself. |
The Path Item
object has a number of fields. The summary
and description
fields, as their names suggest, provide a short summary and longer description of the path. The servers
object is the same as the one in the main OpenAPI document. It defines alternative servers. The parameters
object defines any path or query parameters for that path. Each Path Item
object can have an operation
object. The operation
object documents an HTTP method that can be used on this API route.
The operation
object has many items, but for this tutorial, you’ll focus on a smaller set:
Name | Type | Description |
---|---|---|
tags |
Array of string s |
A list of tags for API documentation control. Tags can be used for grouping similar routes. |
summary |
string |
A short summary of what the operation does. |
description |
string |
A description of the operation. Markdown can be used here. |
externalDocs |
External Documentation Object | Additional external documentation for this operation. Same as externalDocs on the main object. |
parameters |
Array of Parameter Objects | Same as parameters in the Path Item object. |
requestBody |
Request Body Object | The body of the request. This can NOT be used when the method GET or DELETE . |
responses |
Responses Object | REQUIRED. The list of possible responses returned by the API for this operation. |
The tags
property groups similar paths. Paths with the same tag will end up in one group. The summary
and description
fields are the same as the ones in the path
object. They allow you to add a short summary and a longer description, respectively. The externalDocs
property is the same as that in the main document: it allows you to define any external documentation for that operation.
The parameters
object is the same as the one in the path
object. It allows you to define path, query, header, or cookie parameters that have to be sent with the request. The requestBody
also allows you to define parameters, but in the body of the request. This requestBody
field is only available in POST
, PUT
and PATCH
requests, as defined in the HTTP/1.1 protocol, RFC7231.
/posts
RouteNow you will document an API route by creating an object in the paths
object. First, you’ll document the /posts
route. Begin by adding these lines to your YAML document:
paths:
"/posts":
Note: /posts
is in quotes because it contains special symbols (/
). This is required by YAML so it doesn’t misinterpret the line.
Next, you need to add a field whose key will be the HTTP method, and whose value will be the Path Item
object. Document the GET /posts
route, which returns an array of all posts, by adding the highlighted lines:
paths:
"/posts":
get:
tags: ["posts"]
summary: Returns all posts.
The tags
field groups similar operations together. (Notice how the accordion in the preview is called posts
.)
Next, document the responses one can get back. The only response you’ll get from this API is a 200
response containing an array of all posts.
An example post that can be returned by JSONPlaceholder will look like this:
{
"userId": 1,
"id": 1,
"title": "A post's title",
"body": "The post's content"
}
Since you’ll be reusing this pretty frequently, you can create a reusable component for this post. This can be done using the components
object. You can define the post
as a schema in the schemas
object, which will be inside the components
object. This schema is similar to the schema in a JSON Schema file.
Add the post schema to your YAML file. Please note that the components
object must be placed in the root of the document (without any indentation), not in the paths
object.
components:
schemas:
post:
type: object
properties:
id:
type: number
description: ID of the post
title:
type: string
description: Title of the post
body:
type: string
description: Body of the post
userId:
type: number
description: ID of the user who created the post
The above schema is an object
, denoted by type: object
. It has four properties
: id
, title
, body
, and userId
. That is how a schema is defined. Now you can use $ref
in any object to reference this schema. This is defined as per the specification for URI syntax, RFC3986.
Now that you have the schema, you can add the responses
object. This object has items whose value is the status code returned, or default
, to catch all other statuses, and the value is a response
object. This object contains the description
of the response, any headers that are returned, and the response body, along with the Content-Type
in the content
object.
Add the responses
object to the get
operation of the /posts
path by copying the highlighted lines:
paths:
"/posts":
get:
tags: ["posts"]
summary: Returns all posts.
<$>responses:<$>
<$>"200":<$> # 200 Status Code
<$>description:<$> All went well
<$>content:<$>
<$>application/json:<$> # Reponse is returned in JSON
<$>schema:<$>
Be sure to enclose 200
in quotes to make it a string and not a number.
Here, you’re defining a response that gets returned with the 200
status code, and has a Content-Type
header of application/json
. In this schema
object, you need to pass a reference to the post
schema you just created. That can be done with $ref
.
Aside from schema
, the application/json
object can also contain any examples
you wish to give.
For now, add a reference to the post schema in schema
.
$ref: "#/components/schemas/post"
#
refers to the root of the document. Since the post
schema is located in components
/schemas
/post
, that’s how you should write it. And since #
is a reserved symbol in YAML, you need to enclose the ref in quotes.
Your Insomnia Design tab should look similar to this:
You can see that insomnia has rendered a preview of your document. The /posts
route has been grouped into a posts
section, because of the tag, and the correct response is also showing, as you defined in the schema. The same content-type you defined and the same schema you defined are previewed on the right.
You can try changing something, like the tag
or the responses
of the path, and see it update in real time. Be sure to change it back after you’re done.
Note: Press the Try It Out button in the Path operation in the preview, and then the Execute button to call the JSONPlaceholder API and receive a response.
With the GET
route documented, it’s time to document the POST /posts
route. This will be quite similar to the previous operation, but this time, it will be a POST
request, hence the object’s key is post
(highlighted below). Add the following lines to your YAML file:
paths:
"/posts":
# ...
<$>post<$>:
tags: ["posts"]
summary: Create a new post
responses:
"200":
description: A post was created
content:
application/json:
schema:
$ref: "#/components/schemas/post"
You’ve just defined another operation. This time, it is a POST request, with the same tag
, so it gets grouped along with the GET request you defined earlier. The response also has the same schema since that is what will be returned by JSONPlaceHolder.
There’s still one thing missing: the post
method also accepts a request body. It hasn’t been documented yet, so add the requestBody
object to the post
operation. The request body is similar to the response
object. There’s a description
and content
field, which are the same as the response
object, and there’s also a required
field, which is a boolean. This field governs whether a body is required for this request or not. In this case, it is, so add the requestBody
object to your operation.
paths:
"/posts":
# ...
<$>post<$>:
tags: ["posts"]
summary: Create a new post
<$>requestBody:<$>
<$>content:<$>
<$>application/json:<$>
<$>schema:<$>
<$>$ref: "#/components/schemas/post"<$>
<$>required: true<$>
responses:
"200":
description: A post was created
content:
application/json:
schema:
$ref: "#/components/schemas/post"
At this point, your paths
object should look like this:
paths:
"/posts":
get:
tags: ["posts"]
summary: Returns all posts
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
post:
tags: ["posts"]
summary: Create a new post
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/post"
required: true
responses:
"200":
description: A post was created
content:
application/json:
schema:
$ref: "#/components/schemas/post"
In this section, you documented the GET
and POST
operations available in the /posts
route. Next, you’ll document the /posts/:id
route, which is used to read, modify, or delete a single post.
/posts/:id
RouteNext, you’ll document the /posts/:id
route. This route has three operations: GET
, PUT
, and DELETE
. They get a single post, update a post, and delete a post. :id
is a dynamic parameter that can be a number (for example: /posts/1
, /posts/2
, etc.). In OpenAPI, this is denoted as {id}
, as shown in the following example:
paths:
"/posts":
# ...
"/posts/{id}":
# TODO
The in
property defines where the parameter will be placed. This can be in the query
string, in the cookie
, in the header
, or as a part of the path
itself. The description
is a description of the parameter. The required
field is a boolean that indicates if the parameter is required. In the case of path
parameters, required
has to be true
, since the parameter is a part of the path itself.
Path parameters are special, so they’re defined in the path using braces ({}
). The name of the parameter is enclosed in the braces and must match the name in the name
field. First, you need to define id
as a parameter object in the parameters
array. Here’s how you’ll do it:
paths:
"/posts/{id}":
parameters:
- name: id # Must be same as the value in the {}.
in: path
description: ID of the post
# Since this is in the path, the parameter HAS to be required
required: true
# Defining the type of the parameter
schema:
# In this case, it is just a string
type: string
Be sure to include the hyphen (-
), otherwise the parameters
array would become an object
The last thing to document are the operations and their responses and request bodies, if they have any. This is similar to what you did in the previous section.
First, add the GET /posts/:id
operation, which gets a single post.
get:
tags: ["post"]
summary: Get a single post
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
Notice that this time, there is a 404
response. This is because the GET request can return a 404 error if the post is not found. The properties: {}
in the above code is how you’d define an empty object in YAML.
Next, add the PUT /posts/:id
operation, which updates a post. This method combines the GET and POST methods above, since it has both a requestBody
and a 404
response.
put:
tags: ["post"]
summary: Update a post
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/post"
required: true
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
JSONPlaceholder doesn’t really validate the data you send it, so there is no 400
or 422
response, but if the API you’re documenting does something like that (which it should), be sure to document those responses as well. To avoid repeating yourself, you can create response
components
, as you did in the previous section.
And finally, add the DELETE /posts/:id
operation, which deletes a post. This is the same as the GET method, since it returns a 404
, but this time, the operation is delete
.
delete:
tags: ["post"]
summary: Delete a post
responses:
"200":
description: All went well
content:
application/json:
schema:
type: object
properties: {}
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
Note that the DELETE
method only returns an empty object ({}
), even on a 200
response.
And with that, you’ve successfully documented the /posts
route of JSONPlaceholder. Here’s the full YAML document.
openapi: 3.0.3
info:
title: JSONPlaceholder
description: Free fake API for testing and prototyping.
version: 0.1.0
externalDocs:
description: "JSONPlaceholder's guide"
url: https://jsonplaceholder.typicode.com/guide
servers:
- url: https://jsonplaceholder.typicode.com
description: JSONPlaceholder
paths:
"/posts":
get:
tags: ["posts"]
summary: Returns all posts
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
post:
tags: ["posts"]
summary: Create a new post
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/post"
required: true
responses:
"200":
description: A post was created
content:
application/json:
schema:
$ref: "#/components/schemas/post"
"/posts/{id}":
parameters:
- name: id # Must be same as the value in the {}.
# Location of the parameter.
# Can be `path`, `cookie`, `query` or `header`
in: path
description: ID of the post
# Since this is in the path, the parameter HAS to be required
required: true
# Defining the type of the parameter
schema:
# In this case, it is just a string
type: string
get:
tags: ["post"]
summary: Get a single post
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
# But this time, you can also get a 404 response,
# which is an empty JSON object.
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
put:
tags: ["post"]
summary: Update a post
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/post"
required: true
responses:
"200":
description: All went well
content:
application/json:
schema:
$ref: "#/components/schemas/post"
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
delete:
tags: ["post"]
summary: Delete a post
responses:
"200":
description: All went well
content:
application/json:
schema:
type: object
properties: {}
# But this time, you can also get a 404 response,
# which is an empty JSON object.
"404":
description: Post not found
content:
application/json:
schema:
type: object
properties: {}
components:
schemas:
post:
type: object
properties:
id:
type: number
description: ID of the post
title:
type: string
description: Title of the post
body:
type: string
description: Body of the post
userId:
type: number
description: ID of the user who created the post
In the above document, you’ve documented all /posts
routes provided by JSONPlaceholder, and you’ve also covered all HTTP methods that are supported. you’ve also learned about parameters, request bodies, and different responses.
With the OpenAPI document complete, the next step is to make it available to users.
While Insomnia does have a nice-looking Preview pane, you can’t expect all of your users to have Insomnia installed, so you’ll use Redoc to display the OpenAPI YAML file in a nice readable way.
To build Redoc, you need to have NodeJS installed. (Please note that you don’t need to know any NodeJS or JavaScript to build Redoc.)
Create a new folder anywhere on your computer. You’ll be building Redoc in this folder and deploy it to GitHub.
First, you’ll need to save your current OpenAPI document to this folder. Create a new file called openapi.yaml
in the current folder and copy-paste the contents in Insomnia’s Design tab to this file. Redoc can now use this file to generate your API documentation
Next, open a terminal in that folder and run the below command to build Redoc.
- npx redoc-cli --output index.html bundle openapi.yaml
npx
is the NPM (Node Package Manager)'s CLI tool to fetch a CLI-installable package and run it. This allows you to run redoc-cli
without actually installing it to your global $PATH
. Instead, it will be available via npx
. Be sure to type y
if asked to install redoc-cli
or not. Next, you’re telling Redoc to bundle
the openapi.yaml
file you just created into a zero-dependency HTML file, which in this case, will be index.html
, since you passed the --output
flag.
This should create a new file called index.html
in that directory. This file is the documentation, powered by Redoc. Open the file in your browser and inspect it to make sure that every route you’ve defined is covered.
Now that you have your documentation site generated, it’s time to make it available to others using GitHub Pages.
Now that you have a documentation website, you can use GitHub Pages to deploy it for the world to see. As part of the prerequisites, you created a new GitHub Repository. Copy the clone URL shown in Quick Setup. You’ll use the git command to push your openapi.yaml
and index.html
files to GitHub. Be sure to run the below commands in the folder that contains these two files.
First, you’ll initialize the git repository and commit all of your files:
- git init
- git add .
- git commit -m "First commit" # Feel free to change this message
Next, you’ll deploy your changes to GitHub. First, you need to tell git
that your GitHub repository should be the remote repository. The remote repository is usually stored under the name origin
.
- git remote add origin YOUR_GITHUB_REPO_URL
And finally, push your changes to GitHub with this command:
- git push origin main
Note: Git has changed the name of the default branch from master
to main
, so main
is used in the command above. Feel free to replace main
with master
if you like.
Refresh GitHub, and you should see your two files there.
Now you need to enable GitHub Pages. Go to your repository’s settings and click the Pages button on the Sidebar. Change the source branch to your default branch (usually main
or master
) and the folder to / (root)
and click Save.
Finally, visit https://your_username.github.io/your_repo_name
, you should see the Redoc page you’ve just created. You can view my version published to GitHub Pages here.
With that, your API is now available for anyone to see with the URL above.
In this tutorial, you documented the /posts
route of the JSONPlaceholder API. You documented path parameters as well as request bodies and possible responses. You have also learned to reduce boilerplate using reusable components. Feel free to check out the source code and the live version.
As a next step, try to document the other routes that JSONPlaceholder offers (e.g., /users
), or try this out with your own API. You can go forward from here by using tools like Docasaurus to add documentation that explains and guides the user. Remember to keep your API Spec DRY and easy to read so you can make changes to it in the future. Insomnia also has other features, like the ability to test and debug your API, so be sure to check those out by reading the documentation.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.