Spring Boot and Ionic application development with OpenAPI

Published: September 21, 2017  •  Updated: February 17, 2018  •  spring, java, javascript, ionic3

When you are writing REST services you maybe already heard of the OpenAPI (former Swagger) specification. OpenAPI is a specification for describing REST services. It's a text file in the JSON or Yaml format and tools can read it to generate code, documentation and test cases.

The Swagger specification was renamed to OpenAPI in January 2016. The name renaming did not change the specification, it made the difference between the tools (Swagger) and the specification (OpenAPI) more distinct.

You can incorporate OpenAPI in different ways into your application development workflow. You could first write the specification, generate code and implement the services. Or you could first develop the services and use a tool to generate the OpenAPI specification file.

In this blog post we create a simple todo application with a Spring Boot server that implements the REST service and an Ionic application that consumes the service. We start with the REST service implementation and let a generator create the OpenAPI specification. Then we feed this specification into a code generator that produces TypeScript/Angular code. And finally we use the generated code in the Ionic application to consume the REST service.


Developing the REST services

I usually generate my Spring Boot applications with the https://start.spring.io/ webseite. For this demo project we only need Web as dependency. When everything is set up we create a POJO that represents a todo record.

public class Todo {
  private String id;
  private String title;
  private String description;
  // get and set methods

src/main/java/ch/rasc/swagger/todo/Todo.java

To keep things simple we store everything in memory. A simple database service stores the todo records in a Map.

@Service
public class TodoDb {
  private final Map<String, Todo> todos = new HashMap<>();

  public void save(Todo todo) {
    this.todos.put(todo.getId(), todo);
  }

  public void delete(String id) {
    this.todos.remove(id);
  }

  public Collection<Todo> list() {
    return this.todos.values();
  }
}

src/main/java/ch/rasc/swagger/todo/TodoDb.java

Then we create the Controller that implements the REST services. We create three services save, delete and list, that are mapped to the URLs /todo/save, /todo/delete/{id} and /todo/list.

Because save and delete do not return anything we set the HTTP response code to 204 (No Content). The generated TypeScript code in the next section depends on this behavior. It throws an error when the server sends an empty response with the code 200.

@RestController
@RequestMapping("/todo")
@CrossOrigin
public class TodoService {
  private final TodoDb todoDb;

  public TodoService(TodoDb todoDb) {
    this.todoDb = todoDb;
  }

  @PostMapping("/save")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void save(@RequestBody Todo todo) {
    this.todoDb.save(todo);
  }

  @PostMapping("/delete/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void delete(@PathVariable("id") String id) {
    this.todoDb.delete(id);
  }

  @GetMapping("/list")
  public List<Todo> list() {
    return new ArrayList<>(this.todoDb.list());
  }
}

src/main/java/ch/rasc/swagger/todo/TodoService.java

This concludes our server side implementation. You can start the application in your IDE or from the command line with mvn spring-boot:run To test the application in this state we have to manually generate and send the requests. You can use one of the many REST clients or use curl in the shell.

curl -i -X POST -H "Content-Type:application/json" http://localhost:8080/todo/save -d "{\"id\":\"1\",\"title\":\"Shopping\",\"description\":\"Buy milk\"}"

curl http://localhost:8080/todo/list

curl -i -X POST http://localhost:8080/todo/delete/1

Generating the OpenAPI specification

Our services are working and we want to create an OpenAPI specification file for these three services. For this purpose we add a new library to our project.

  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
  </dependency>

pom.xml

Springfox is a Swagger/OpenAPI implementation for Spring.

To enable Springfox we need to add the @EnableSwagger2 annotation to one of the @Configuration classes.

@SpringBootApplication
@EnableSwagger2
public class Application {

src/main/java/ch/rasc/swagger/Application.java

This is sufficient to generate the OpenAPI configuration file. Start the Spring Boot application, open the browser with the url http://localhost:8080/v2/api-docs and you should see the OpenAPI specification in JSON format. To convert this into a more human readable form you can go to the Online Swagger Editor and paste the JSON into the editor.

By default, Springfox adds all the http services it finds to the specification file. By default, Spring Boot installs an error handler mapped to the URL /error. That's the reason you see the /error endpoint in the specification. If you want to limit the services exposed in the specification you need an additional configuration.

Stop the server and add the following bean definition to a configuration class.

  @Bean
  public Docket api() {
  return new Docket(DocumentationType.SWAGGER_2)
      .useDefaultResponseMessages(false)
      .apiInfo(new ApiInfoBuilder().title("Todo API Endpoints").version("1").build())
      .select().apis(RequestHandlerSelectors.basePackage("ch.rasc.swagger.todo"))
      .paths(PathSelectors.any())
      .build();
  }

src/main/java/ch/rasc/swagger/Application.java

The Docket bean allows an application to specify which services should appear in the OpenAPI specification file. There are different ways to filter services. In this example we select all services in the package ch.rasc.swagger.todo. With the Docket you can also configure other parts of Springfox. You find more information in the official Springfox documentation.

Start the server and open the specification page again. The file should longer contain a description for the /error endpoint. Besides the REST services, OpenAPI also describes the objects that are used as return values and parameters of the services. You find them at the end in the definitions section.

Based on this specification it would be nice if we can create a nice looking documentation page directly in our application instead of copying the JSON to the online editor. Fortunately there is an easy way to do that. Add this dependency to the project and restart the Spring Boot application.

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.9.2</version>
</dependency>

pom.xml

Open the browser with the URL: http://localhost:8080/swagger-ui.html and you see a nice looking documentation page for your REST services. It's not just a static documentation page it even allows you to test the services. Beneath each operation you find a "Try it out" button that sends a request to the server. You have to fill in the mandatory parameters, before you can send the request.

By default, the documentation is very generic but you can customize a lot of the information with Springfox annotations. You can describe the service with @ApiOperation and the parameters with @ApiParam.

  @PostMapping("/delete/{id}")
  @ApiOperation("Deletes a todo entry")
  public boolean delete(@ApiParam("Primary key of the todo entity") @PathVariable("id") String id) {
    this.todoDb.delete(id);
    return true;
  }

src/main/java/ch/rasc/swagger/todo/TodoService.java

You can also add annotations to the model classes.

@ApiModel(description="The todo record")
public class Todo {
  @ApiModelProperty(required=true, value="The primary key")
  private String id;

src/main/java/ch/rasc/swagger/todo/Todo.java

See the official Springfox documentation for more information.


Generating client code

Next we want to create an Ionic app that consumes these REST services.

First create an empty ionic app based on the blank template

ionic start todo blank

Our app consists of a list and an edit page. We use the HomePage from the blank template for the list and generate an additional edit page.

ionic g page edit --no-module

In this example we want to assign the primary key (id) on the client side when the user creates a new todo record. For this purpose we install the uuid JavaScript library.

npm install uuid

Next we use a Swagger code generator that produces TypeScript code. In this project we use a Maven plugin to run the code generator.

  <plugin>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>2.3.1</version>
    <executions>
      <execution>
        <id>typescriptgen</id>
        <phase>none</phase>
        <goals>
          <goal>generate</goal>
        </goals>
        <configuration>
          <inputSpec>http://localhost:8080/v2/api-docs</inputSpec>
          <language>typescript-angular</language>
          <output>${basedir}/../client/src/swagger</output>
        </configuration>
      </execution>
    </executions>
  </plugin>

pom.xml

The output path points to the src directory of the Ionic app. I set the phase of the plugin to none ( <phase>none</phase>) to prevent the plugin from running during the normal Maven lifecycle. With this configuration you have run the plugin manually with this command:

mvn io.swagger:swagger-codegen-maven-plugin:generate@typescriptgen

You can remove the none-phase and the plugin will run every time you call the generate-sources lifecycle (mvn generate-sources).

Either way, before you run the plugin you have to start the Spring Boot application because the plugin downloads the OpenAPI file from the provided inputSpec address.

Alternatively you could download the specification file and store it in the project directory and configure a file path instead of an URL.

<inputSpec>src/main/resources/api.json</inputSpec>

You find more information about the plugin on the project page: https://github.com/swagger-api/swagger-codegen


Developing the client

When the generator did his job without throwing an error you find the generated source code in the src\swagger directory. The model subdirectory contains a TypeScript class for the todo model and in the api subdirectory you find the Todoservice class with methods for each of our services.

To use the generated service we have to import it into app.module.ts with ApiModule.forRoot. You also need to insert a configuration factory for the module where you can specify things like base URL, api keys and username/password. For this example we only have to configure the base URL that points to the server.

export function apiConfigFactory (): Configuration {
  const params: ConfigurationParameters = {
    basePath: 'http://localhost:8080'
  }
  return new Configuration(params);
}

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    EditPage
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    ApiModule.forRoot(apiConfigFactory),
    IonicModule.forRoot(MyApp)
  ],

src/app/app.module.ts


Then we can inject the service class into other classes as usual.

export class HomePage {

  todos: Todo[];

  constructor(private readonly navCtrl: NavController,
              private readonly todoService: TodoServiceService) {
  }

src/pages/home/home.ts

After this setup, the application can call one of the provided methods to send a request to the server. All the generated methods return an Observable and we have to subscribe to it, otherwise nothing happens.

list

  ionViewDidEnter() {
    this.todoService.listUsingGET().subscribe(data => this.todos = data);
  }

src/pages/home/home.ts

delete

  deleteTodo(slidingItem: ItemSliding, todo: Todo) {
    slidingItem.close();
    this.todoService.deleteUsingPOST(todo.id).subscribe(() => this.ionViewDidEnter());
  }

src/pages/home/home.ts

save

  save() {
    this.todoservice.saveUsingPOST(this.todo).subscribe(() => this.navCtrl.pop());
  }

src/pages/edit/edit.ts

You find the complete source for the Spring Boot and Ionic application on GitHub: https://github.com/ralscha/blog/tree/master/swagger