API First approach with Swagger

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 many different technologies.

API First: Swagger Top-Down approach

There are a lot of good API design tools. Here are some useful comparisons: 12. 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?

screen-shot-2016-11-13-at-12-50-26

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:

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", "ivan.vaskevych@gmail.com"))
                .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, 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.

Comments

Drop your comment bellow in a twitter reply:

Show Comments

Get the latest posts delivered right to your inbox.