Home | Send Feedback

Spring Boot and Ionic 4 application development with OpenAPI

Published: September 21, 2017  •  Updated: December 04, 2018  •  spring, java, javascript, ionic4

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 JSON or Yaml format and tools can read it to generate code, create documentation and test cases.

The Swagger specification was renamed to OpenAPI in January 2016. The name change did not alter 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 from the source code.

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

Developing the REST services

I usually generate my Spring Boot applications with the https://start.spring.io/ website. For this demo project we only need the Web dependency.

When everything is set up, we first create a POJO that represents a todo record.

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

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();
  }

}

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 behaviour. 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)
  @ApiOperation("Deletes a todo entry")
  public void delete(@ApiParam(value = "Primary key of the todo entity",
      required = true) @PathVariable("id") String id) {
    this.todoDb.delete(id);
  }

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

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 available REST clients or use curl from 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 OpenAPI specification

Our services are working and we want to create an OpenAPI specification file for these three services.
For this purpose we add the springfox-swagger2 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 an arbitrary @Configuration class.

@SpringBootApplication
@EnableSwagger2
public class Application {

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 see the OpenAPI specification in JSON format.

To convert this into a more readable form you can open the Online Swagger Editor and paste the JSON into the editor.

By default, Springfox adds all the HTTP services it finds inside the Spring application to the specification file. If you want to limit the exposed services 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();
  }

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.


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 of the file 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 documentation page for your REST services. It's not just a static documentation page it even allows you to test the services. For 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}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  @ApiOperation("Deletes a todo entry")
  public void delete(@ApiParam(value = "Primary key of the todo entity",
      required = true) @PathVariable("id") String id) {
    this.todoDb.delete(id);
  }

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;

Todo.java

See the official Springfox documentation for more information.

Generating client code

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

The application for this example is based on the Ionic blank starter template.

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 a 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.4.0</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/app/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:

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/app/swagger directory. The model subdirectory contains a TypeScript class for the todo model and in the api subdirectory you find the service class with methods for each of our services.

Next we add a property baseUrl to the environment objects (environment.ts and environment.prod.ts).

export const environment = {
  production: false,
  basePath: 'http://127.0.0.1:8080'
};

environment.ts

To use the generated service we have to import it into our application. Open src/app/app.module.ts and add ApiModule.forRoot to the imports array. 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: environment.basePath
  };
  return new Configuration(params);
}

@NgModule({
  declarations: [AppComponent, HomePage, EditPage],
  entryComponents: [],
  imports: [BrowserModule,
    CommonModule,
    HttpClientModule,
    ApiModule.forRoot(apiConfigFactory),
    FormsModule,

app.module.ts

Make sure that you import HttpClientModule, the generated code depends on this.


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) {
  }

home.page.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);
  }

home.page.ts

delete

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

home.page.ts

save

  save() {
    this.todoService.saveUsingPOST(this.todo).subscribe(() => this.navCtrl.navigateBack(['home']));
  }

edit.page.ts


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