Using Swagger UI with any codebase

26 Nov 2017, by Pang Yan Han

This post shows you how to use Swagger UI with any codebase. There are no library dependencies once you extract the assets. The only things you need are a web server and a file containing the API documentation in the OpenAPI format.

At the end of the post, you should be able to use your web server to host API documentation that looks something like http://petstore.swagger.io

Like many of my posts, this is technical and there are instructions you have to follow.

Accompanying code

The GitHub repo is at https://github.com/yanhan/swagger-ui-any-codebase

We will be making use of some files there in this demo.

The big picture

We will be making use of the swagger-ui-dist npm package and extracting the assets from there. To do so, we will be using nvm to install npm and use npm to install swagger-ui-dist. Once that is done, we can extract the assets.

With the assets and an API documentation file, we can use a web server to serve the API documentation Swagger UI style.

Create a new directory

Create a new directory somewhere to reduce clutter. We will be doing everything in this new directory.

Extracting static assets for Swagger UI

This section focuses on extracting the static assets required by Swagger UI.

Installing nvm

Follow the instructions at the nvm GitHub README. I have copied and pasted what I used:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash

nvm allows us to install multiple versions of Nodejs without having to override the system’s Nodejs.

Install Nodejs using nvm

I am using Nodejs 8.9.1 in this demo. I believe any 8.x Nodejs should work.

nvm install 8.9.1

Install swagger-ui-dist

Create a package.json file with the following contents:

{
    "version": "0.1.0",
    "dependencies": {
        "swagger-ui-dist": "~3.5.0"
    }
}

Then activate Nodejs 8.9.1 and install the package:

nvm use 8.9.1
npm install

You should see a node_modules directory. We will be extracting the static assets of swagger-ui-dist from it soon.

Extract static assets

Copy the node_modules/swagger-ui-dist directory:

cp -R node_modules/swagger-ui-dist .

In the swagger-ui-dist directory, there is a file named index.html. Use a text editor to open that file and go to line 76. You should see code like this:

  const ui = SwaggerUIBundle({
    url: "http://petstore.swagger.io/v2/swagger.json",

For purposes of the demo below, modify the value of the url key so that it looks like:

  const ui = SwaggerUIBundle({
    url: "/api-docs.yml",

This is telling the code to fire a request to /api-docs.yml to load the API documentation. Feel free to change the name of this endpoint, as long as you configure your web server supports it and return the correct file.

In fact, you can copy / move the entire swagger-ui-dist directory to your web server’s repository and take things from there (while skipping the rest of this article). If you are unsure on how to proceed, carry on reading so you can see a demo of how we incorporate some example API documentation so it is hosted on a Flask server.

Demo using Flask server (using Docker)

This is the easy way.

Simply clone https://github.com/yanhan/swagger-ui-any-codebase and run the following commands to build the Docker image and run it:

./build-docker.sh
./run-docker.sh

Then go to http://127.0.0.1:5000/swagger-ui . Tadah!

Demo using Flask server (not using Docker)

This is the hard way. With the Docker option, I don’t really recommend doing this unless you want to familiarize yourself with the pyenv toolchain.

Install pyenv

Follow the instructions at the pyenv GitHub README to install pyenv. I used the instructions at the pyenv-installer to install pyenv. Specifically, the following command:

curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

Install Python 3.6.3 using pyenv

Now we install Python 3.6.3 using pyenv. Any version of Python 3.x should work fine.

pyenv install 3.6.3

If you encounter any errors installing pyenv, google is your friend.

Create a virtualenv for this demo

We will be creating a virtualenv to install Flask. We will be naming it swagger-demo.

pyenv virtualenv 3.6.3 swagger-demo

Install Flask

Now we will be installing Flask inside the swagger-demo virtualenv:

pyenv activate swagger-demo
pip install -r requirements.txt

Web server code

Get it from https://github.com/yanhan/swagger-ui-any-codebase/blob/master/server.py and save it as server.py.

We will be going through some of the code later.

api-docs.yml file

Get it from https://github.com/yanhan/swagger-ui-any-codebase/blob/master/api-docs.yml and save it as api-docs.yml.

This is our example API documentation saved as a YAML file.

Running the server

python server.py

Then go to http://127.0.0.1:5000/swagger-ui to see the documentation. Tadah!

How everything works

Understand this section and you can adapt the Swagger UI assets to any codebase you are using. You are recommended to open https://github.com/yanhan/swagger-ui-any-codebase/blob/master/server.py and refer to it as we explain the code.

Recall that earlier we extracted the swagger-ui-dist directory from the swagger-ui-dist NPM package. That directory contains all the static assets required. We just need to find a way for the server to deliver the swagger-ui-dist/index.html file on a user request.

Notice that to see the docs, we go to the path /swagger-ui, which serves the swagger-ui-dist/index.html file. Which implies that our web server must have an endpoint for this path taht serves the file. For our demo, it is right here:

@app.route("/swagger-ui/")
def swagger_ui():
    return send_from_directory(SWAGGER_UI_DIST_DIR, "index.html")

This tells Flask to return the index.html page from our swagger-ui-dist directory. Recall that earlier, we modified that file so the SwaggerUIBundle has its url value changed:

  const ui = SwaggerUIBundle({
    url: "/api-docs.yml",

which implies we must have another route to /api-docs.yml that returns the file containing the API documentation. Indeed, it is here:

@app.route("/api-docs.yml")
@nocache
def swagger_api_docs_yml():
    return send_from_directory(".", "api-docs.yml")

The endpoint and the filename do not have to be the same as long as the endpoint you are using returns the correct file; you can use whatever name you like for the endpoint and the filename. We are only using the same name to reduce confusion. I added the nocache decorator so that the docs served by the Flask server will be updated as I save the actual file and refresh my browser. Credits to Aru Sahni’s blog post for this code.

Finally, if we open swagger-ui-dist/index.html, we will see some references to other static assets with the ./ prefix, for instance:

./swagger-ui.css
./favicon-32x32.png
./favicon-16x16.png

These are all available at the swagger-ui-dist directory. Due to the ./ and the fact that we are hosting the API docs at /swagger-ui, these assets will be requested from the web server under /swagger-ui. For instance, /swagger-ui/favicon-32x32.png.

Hence we need an endpoint for serving these assets, right here:

@app.route("/swagger-ui/<asset>")
def swagger_assets(asset):
    return send_from_directory(SWAGGER_UI_DIST_DIR, asset)

If we do not do this step, we will be able to load /swagger-ui but fail to load the other static assets. Open the Network Inspector / similar of your web browser and you will see a number of 404s. In fact, that was how I debugged the problem.

Security implications

The big drawback to this approach is security. Specifically, exposure of internal API docs to the public Internet. Here, I highlight some concerns that I see.

  1. Please disable this endpoint for production. In fact, you should write this code immediately so you don’t forget about it.
  2. If you are writing docs for an internal API, please take care not to expose this documentation to the Internet. Minimally, your web server should not have a public IP address and should be on a private network that is only accessible using a VPN.

Conclusion

And that completes our explanation of the web server code. Hopefully, you will be able to adapt this to any codebase.

comments powered by Disqus