API First approach with Swagger
Table of Contents
Modern IT companies embrace an API (Application programming interface) as an important part of their businesses. Follow the trend and change how you perceive APIs. Build an API before anything else, except business plan (hopefully). Acknowledge the fact that your service will be customer-facing, even if for now the only customer is a web front-end your colleague is working on. Customer-facing, that should ring a bell.
Also accept an API as a first-class citizen for your company instead of an afterthought plugin thing. API is a part of company’s strategy.
In this post, we dive into designing and implementing an API using Swagger, Spring Boot. We also consider API cooperation, maintenance and documentation.
Technology stack
I’ll be using Java and Spring Boot for RESTful API implementation examples. However, most of the content of this post is language/framework agnostic, because of the fact that Swagger supports different technologies.
API First: Swagger Top-Down approach
There are a lot of good API design tools. Here are some useful comparisons: 1, 2. My weapon of choice is Swagger, mainly because of prior positive experience and a strong community. After all, who would want to see search results below?
Design API with your clients
Let’s get our hands dirty doing something. Open Swagger Editor and start hacking your API’s specification. To do that some basic knowledge of Swagger Specification (OpenAPI Specification) is useful. If API if fairly simple you should be able to do this with little to no specification archeology. Open any example (File -> Open Example) and write by analogy.
Few days later, after long conversations between your team and API clients you come up with the first API draft:
swagger: '2.0'
info:
version: '0.0.1'
title: Petstore
description: A simple Petstore API
basePath: /v1
schemes:
- http
- https
consumes:
- application/json
produces:
- application/json
paths:
/pets:
get:
description: Returns all pets from the system that the user has access to
operationId: findPets
produces:
- application/json
parameters:
- name: tags
in: query
description: tags to filter by
required: false
type: array
items:
type: string
collectionFormat: csv
- name: limit
in: query
description: maximum number of results to return
required: false
type: integer
format: int32
responses:
'200':
description: pet response
schema:
type: array
items:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
post:
description: Creates a new pet in the store.Duplicates are allowed
operationId: addPet
produces:
- application/json
parameters:
- name: pet
in: body
description: Pet to add to the store
required: true
schema:
$ref: '#/definitions/newPet'
responses:
'200':
description: pet response
schema:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
definitions:
pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
newPet:
type: object
required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
errorModel:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
You feel good. You send API clients (Web, iOS, Android, others) the link with the API doc, which comes with integrated stub:
- Copy the above API definition
- Paste it to http://playground.apistudio.io/
You get first feedbacks and start correcting the API. You end up with API that looks good for everyone:
swagger: '2.0'
info:
version: '0.3.1'
title: Petstore
description: A simple Petstore API
basePath: /v1
schemes:
- http
- https
consumes:
- application/json
produces:
- application/json
paths:
/pets:
get:
description: Returns all pets from the system that the user has access to
operationId: findPets
produces:
- application/json
parameters:
- name: tags
in: query
description: tags to filter by
required: false
type: array
items:
type: string
collectionFormat: csv
- name: limit
in: query
description: maximum number of results to return
required: false
type: integer
format: int32
responses:
'200':
description: pet response
schema:
type: array
items:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
post:
description: Creates a new pet in the store.Duplicates are allowed
operationId: addPet
produces:
- application/json
parameters:
- name: pet
in: body
description: Pet to add to the store
required: true
schema:
$ref: '#/definitions/newPet'
responses:
'200':
description: pet response
schema:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
/pets/{id}:
get:
description: Returns a pet
operationId: findPetById
produces:
- application/json
parameters:
- name: id
in: path
description: ID of pet to fetch
required: true
type: integer
format: int64
responses:
'200':
description: pet response
schema:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
definitions:
pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
newPet:
type: object
required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
errorModel:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
In the example above there’s only one modification from the first version. It’s /pets/{id}
request to get a pet by it’s id.
Mock
The next step is to create a usable mock server for your clients to start integrating with the API. That way you get to work in parallel. You also get early feedback and could make API correction while you’re still not too far down the road implementing it.
The tooling options are wide. You can generate a mock server with swagger-codegen tool. With it you can create stub for a wide range of platforms and languages, from Node.js to Scala NancyFX .NET framework (Server-stub-generator-HOWTO).
wget http://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.2.1/swagger-codegen-cli-2.2.1.jar java -jar swagger-codegen-cli.jar generate -i petstore.yaml -l nodejs-server server/petstore/nodejs
It’s also possible to generate mock server code directly from Swagger Editor (Generate Server menu).
http://playground.apistudio.io/ also creates a mock server, you can choose the request on documentation screen and click Try this operation.
You can also import your API specification (yaml or json file) to swaggerhub.com. Beware, you can only create a Public API using Free tier. Then add ‘API auto mocking’ integration as described in a doc and you have a basic mock server up and running.
Client library
Now, it’s a good practice to create a client library for your API. No problem, build a skeleton based on the API definition you just developed, you can choose between a number of languages:
- Use swagger-codegen-cli
- Or Online Swagger Editor (‘Generate Client’)
It still needs some work to become a smart client, but it’s a good start.
If you need a JavaScript client, consider using swagger-js, dynamic library that doesn’t need any static code generation.
##$ Implement
Now, let’s implement the actual business logic behind our newly born API. Let’s generate a skeleton for Spring Boot. As usual, using Swagger Editor or:
java -jar swagger-codegen-cli.jar generate -i petstore.json -l spring -o server/boot
You can now start implementing logic. Classes named *Controller.java are the entry point of your REST server. Just don’t forget, no logic in Controllers.
Taking documentation seriously
Any API should be well documented. How “well” exactly depends on many things.
The path of least resistance
Now, what you’ve got for free is a good looking documentation for your API. You can use Swagger UI to provide an interactive documentation that would look similar to this demo.
If you’re using Java and Spring Boot as a framework, there’s a great library (springfox) that will take care of dynamic documentation generation, no need to to download, run and configure a separate Swagger UI.
Boot to Fox
Let’s instrument your Spring Boot application with a shiny dynamic API documentation. First, the dependencies.
buildscript {
ext {
springfoxVersion = '2.6.0'
}
repositories {
jcenter()
}
}
dependencies {
compile group: 'io.springfox', name: 'springfox-swagger2', version: springfoxVersion
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: springfoxVersion
compile group: 'joda-time', name: 'joda-time'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda'
testCompile group: 'io.springfox', name: 'springfox-staticdocs', version: springfoxVersion
}
Next, the configuration. In springfox terms, you build the documentation using a Docket.
@Configuration
public class SwaggerDocumentationConfig {
@Bean
public Docket petstoreApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.petstore"))
.build()
.host("localhost:8080")
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Petstore API")
.license("TBD")
.version("0.0.1")
.contact(new Contact("Ivan Vaskevych", "https://www.easyitblog.info/about", "[email protected]"))
.build();
}
}
What happened here? We just created a swagger documentation. Endpoints will be searched under ‘com.petstore’ package and lower. The “Try this operation” button will send requests to “localhost:8080”.
Let’s also configure the redirection to swagger-ui html. Not required, but handy.
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String index() {
return "redirect:swagger-ui.html";
}
}
That’s it. Run the application and go to http://localhost:8080/ (change to whatever port you have).
If you need a API specification in a raw format, visit http://localhost:8080/v2/api-docs to get a JSON. Useful after API modifications. You can use that JSON to create mocks, host API documentation or any other purpose.
Tip: convert JSON specification to Yaml using Swagger Editor (File -> Paste JSON).
You will see your API documentation, generated from your current API (as described @RequestMapping, @ApiOperation and other Spring and Swagger annotations). No generation step needed.
There are much more features that won’t be covered here for brevity’s sake. For example, if you’ll throw in admin API to your app, it’s wise not to allow public access to it’s documentation. You can separate open and admin APIs and use “.groupName” docket method to name them.
Make it pretty
If you would like to customise Swagger UI page (shiny new logo, your site’s colours) you can host your own copy and customise the look of it.
I would presume you have a docker installed on the server that you want to host the documentation. Let’s do it.
docker pull swaggerapi/swagger-ui
docker run -t -p 80:8080 swaggerapi/swagger-ui
docker ps
docker exec -it <CONTAINER ID from the above command> /bin/sh
swagger-ui# cd /usr/share/nginx/html
swagger-ui# vi index.html
# do your customisation here
swagger-ui# exit
docker commit <CONTAINER ID> my-swagger-ui
docker kill <CONTAINER ID>
docker run -t -p 80:8080 my-swagger-ui
Now you should be able to access it via http://<your_host>/
. Sweet!
For the customisations, at the very least you would want to change
window.swaggerUi = new SwaggerUi({
url: url,
to something like
window.swaggerUi = new SwaggerUi({
url: "http://<spring_app_host>/v2/api-docs",
That will make Swagger-UI to load your API documentation immediately.
Tip: Want cool-new-responsive-material design? Take this pretty Swagger-UI fork for a spin. It’s outdated, though.
API Corrections
One thing is certain about your API is that it’ll need to evolve. You could import your API specification to the editor and edit it, then generate scaffolding again and merge to the existing code. I wouldn’t suggest it unless the API rework is really big, and maybe not even then. You better off modifying API in the one place where there is no lie, code.
Add or modify Spring Boot’s @Controller-s and @RequestMapping-s. Swagger gets most of the API info from those standards annotations and request methods signatures. Others, like @ApiOperation and @ApiResponse, will swiftly start making sense.
In the process of modifications, as you grasp swagger’s semantics and annotations, you’ll probably also clean up the generated code. Generated code is a source of some evil.
Security checks, tools
If you’ll check other Swagger Tools the power of using mainstream tooling becomes obvious. Some of the tools are expensive, but some you can use freely for a small projects. For example, you can use Rest Secured to check your API for common security vulnerabilities.
Conclusion
There are a lot of reasons to take API seriously. Whether it’s purpose is internal systems communication or a public-facing interface, it pays off to design an API as any important thing, with a good forethought.
With an API, as with any software solution, maintenance cost is prevailing in overall cost. That’s the main reason to choose tooling carefully, consider and research choices. Better yet, to get hands dirty prototyping.
Swagger ecosystem provides a wide variety of choices and a huge community. I’d definitely recommend to give it try.