Step by step OpenAPI Swagger setup

We’ll be using the following setups for this walkthrough

  • Springboot
  • Kotlin
  • Maven
  • OpenApiTool
  • Swagger-Codegen
  • Java 8
  • Intellij

Project setup

Let’s setup the Springboot code base first at https://start.spring.io/

Download the generated project and load it into Intellij

Write your own YAML Contract

You can use SwaggerHub https://swagger.io/tools/swaggerhub/ to write the contract. The online editor provides intellisense and error checking. You can also see the contract UI real time as you construct the contract. For our demo we’ll write up a basic contract.

I’ll use the below yaml for this demo.

openapi: 3.0.0
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/Z9527/ZDaySample/1.0.0
info:
description: This is a simple API
version: "1.0.0"
title: Simple Inventory API
contact:
email: you@your-company.com
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: admins
description: Secured Admin-only calls
- name: developers
description: Operations available to regular developers
paths:
/inventory:
get:
tags:
- developers
summary: searches inventory
operationId: searchInventory
description: |
By passing in the appropriate options, you can search for
available inventory in the system
parameters:
- in: query
name: searchString
description: pass an optional search string
required: false
schema:
type: string
- in: query
name: skip
description: number of records to skip for pagination
schema:
type: integer
format: int32
minimum: 0
- in: query
name: limit
description: maximum number of records to return
schema:
type: integer
format: int32
minimum: 0
maximum: 50
responses:
'200':
description: search results matching criteria
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/InventoryItem'
'400':
description: bad input parameter
post:
tags:
- admins
summary: adds an inventory item
operationId: addInventory
description: Adds an item to the system
responses:
'201':
description: item created
'400':
description: 'invalid input, object invalid'
'409':
description: an existing item already exists
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InventoryItem'
description: Inventory item to add
components:
schemas:
InventoryItem:
type: object
required:
- id
- name
- manufacturer
- releaseDate
properties:
id:
type: string
format: uuid
example: d290f1ee-6c54-4b01-90e6-d701748f0851
name:
type: string
example: Widget Adapter
releaseDate:
type: string
format: date-time
example: '2016-08-29T09:12:33.001Z'
manufacturer:
$ref: '#/components/schemas/Manufacturer'
Manufacturer:
required:
- name
properties:
name:
type: string
example: ACME Corporation
homePage:
type: string
format: url
example: 'https://www.acme-corp.com'
phone:
type: string
example: 408-867-5309
type: object

Copy the above yaml and save it as sampleApi.yaml and place it in the below dir within your project:

/src/main/resources/spec/sampleApi.yaml

Generate Code Stub for API Producer

Add the below maven dependencies

<!-- https://mvnrepository.com/artifact/io.swagger/swagger-annotations -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openapitools/jackson-databind-nullable -->
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.1</version>
</dependency>

Add the below plugin

<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.2.3</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/src/main/resources/spec/sampleApi.yaml
</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>com.theo.openApiDemo.controller</apiPackage>
<modelPackage>com.theo.openApiDemo.model</modelPackage>
<supportingFilesToGenerate>
ApiUtil.java
</supportingFilesToGenerate>
<configOptions>
<delegatePattern>true</delegatePattern>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>

Make sure that the inputSpec path is where you’re putting your yaml contract.

Run the below command

./mvnw clean compile

When the project is compiled, you should be able to see the generated code stubs just like below

Let’s take a look at the code stubs generated.

If we look into the InventoryApi and InventoryApiController classes, we see that the code-gen helped to generate your typical boiler plate code for a controller. The actual operation for the Api is in the InventoryApiDelegate. Currently in the InventoryApiDelegate interface it’s throwing a 501 NOT IMPLEMENTED as a default implmentation.

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-11-05T11:17:19.315+08:00[Asia/Hong_Kong]")

public interface InventoryApiDelegate {

default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}

default ResponseEntity<Void> addInventory(InventoryItem inventoryItem) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

}

default ResponseEntity<List<InventoryItem>> searchInventory(String searchString,
Integer skip,
Integer limit) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"releaseDate\" : \"2016-08-29T09:12:33.001Z\", \"name\" : \"Widget Adapter\", \"id\" : \"d290f1ee-6c54-4b01-90e6-d701748f0851\", \"manufacturer\" : { \"phone\" : \"408-867-5309\", \"name\" : \"ACME Corporation\", \"homePage\" : \"https://www.acme-corp.com\" } }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

}

}

As the producer of this API all you need to do is just to inherit this interface and implement the methods as you see in the example below.

@Service
class InventoryService: InventoryApiDelegate {
override fun searchInventory(searchString: String?, skip: Int?, limit: Int?): ResponseEntity<MutableList<InventoryItem>> {
val result = mutableListOf(
InventoryItem().apply {
id = UUID.randomUUID()
name = "chocolate"
releaseDate = OffsetDateTime.now()
manufacturer = Manufacturer().apply {
name = "HKMC"
homePage = "google.com"
phone = "12345678"
}
}
)

return ResponseEntity.ok(result)
}
}

Now if you have the above service implementation setup, we’ll try calling the API to see what we will get.

Let’s call the one we implemented — 200

curl — location — request GET ‘http://localhost:8080/v1/inventory?searchString=&skip=&limit=‘

Let’s call the one we didn’t implement — 501

curl — location — request POST ‘http://localhost:8080/v1/inventory'

Now your APIs are ready to roll.

Generate Code Stub API Consumer

Let’s write a contract for a service that already exists for the consumer portion.

openapi: 3.0.0
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/Z9527/ConsumerSample/1.0.0
info:
description: This is a sample consumer API
version: "1.0.0"
title: Simple meme API
contact:
email: you@your-company.com
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: memes
description: Get Memes
paths:
/meme:
get:
tags:
- memes
summary: get memes
operationId: getAnotherMeme
description: Call API to get another meme
parameters:
- in: query
name: meme
description: what kind of meme
required: true
schema:
type: string
- in: query
name: top
description: top text for the meme
required: true
schema:
type: string
- in: query
name: bottom
description: bottom text for the meem
required: true
schema:
type: string
responses:
'200':
description: search results matching criteria
content:
image/jpeg:
schema:
type: string
format: binary
'400':
description: bad input parameter

Add the below maven dependencies

<!-- https://mvnrepository.com/artifact/io.swagger/swagger-codegen -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen</artifactId>
<version>3.0.0-rc1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.swagger/swagger-codegen-generators -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-generators</artifactId>
<version>1.0.0-rc1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.threeten/threetenbp -->
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threetenbp</artifactId>
<version>1.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp/okhttp -->
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp/logging-interceptor -->
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>logging-interceptor</artifactId>
<version>2.7.5</version>
</dependency>

Add the below plugin

<plugin>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.0-rc1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/spec/sampleApi.yaml</inputSpec>
<language>java</language>
<configOptions>
<sourceFolder>src/gen/java/main</sourceFolder>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>

Make sure that the inputSpec path is where you’re putting your yaml contract

Run the below command

./mvnw clean compile

When the project is compiled, you should be able to see the generated code stubs just like below

In order to use this API you’ll need to add a config before you can autowire the api class

@Configuration
class ExampleConfig {
@Bean
fun apiClient(): ApiClient {
return ApiClient().setBasePath("http://apimeme.com")
}

@Bean
fun memesApi(): MemesApi {
return MemesApi(apiClient())
}
}

We’ll create a simple controller to invoke the api class

@RestController
class SampleController {
@Autowired
private lateinit var memesApi: MemesApi

@GetMapping(value = ["/test"], produces = arrayOf(MediaType.IMAGE_JPEG_VALUE))
fun test(): ResponseEntity<ByteArray> {
var result = memesApi.getAnotherMeme(
"Confused-Gandalf",
"This is the way to life",
"This is the way to glory"
)

return ResponseEntity.ok(result)
}
}

Now we’ll try to call the test API from postman to see the result

And this is how you can call an API through the contract generated code stubs.

Can I customize the generated code stubs?

Yes! Whether you’re using the openapitool or swagger code gen or any other open api code stub generation dependency you should be able to customize the code that are generated. Some like to generate all the annotations for the controller and override the code in the controller instead and others like to completely ignore the controller layer and work in the service layer.

Refer to the below documentations when customizing:

Github Repo

Producer code: master branch

Consumer code: swagger-code-gen-version branch

Just another developer who's into lazy tools that can make my life easier, and hopefully yours too.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store