Johannes Hoppe

Generating Angular API clients with Swagger

12.04.2018

In this article, we will take a look at swagger codegen. It will save you a ton of work and pain by generating HTTP services automatically from your swagger API description.


Swagger is the world’s largest framework of tools for the OpenAPI Specification (OAS). If you haven't added Swagger to your backend until now, you should do it now! It's the de-facto standard for re-usable and maintainable APIs. The toolset greatly eases the pain of documenting and interacting with APIs. It’s literally a Swiss army knife for all things APIs.

But in this article we are not going to talk about your backend. Let's assume your API is specified with Swagger and that we can focus on your Angular frontend. I'm pretty sure nobody wants to write boring plumping code by hand and manually sync changes between backend and frontend over and over again. So, how can we use the API documentation to generate code automatically?

Hello swagger-codegen

The official tool for code-generation is the Swagger Code Generator. It supports a various range of target languages. The list of supported languages and frameworks is growing constantly: all available languages
We are interested in the typescript-angular code generator, of course.

Don't be afraid!
Yes, the tool is written in Java.
But our final Angular code will not include any piece of Java at all. I promise you!

First of all, you need the compiled generator: swagger-codegen-cli.jar.
You can download the latest version from the following location: swagger-codegen-cli-2.3.1.jar
At the time of writing, v2.3.1 was stable.
If you need a snapshot of the development version, then take a look at: 2.4.0-SNAPSHOT
Right now you want to grab the snapshots. I have prepared PR that adds AOT-compatibility (via ng-packagr) to the Angular-Templates. This is huge! 😄

General usage

The idea is the following:
The code generator inspects the OpenAPI specification and writes a perfect API client for you.
That's it! No more work by a human.
In this article we will use the following API:

Please feel free to explore it via Swagger UI.

Screenshot

Swagger codegen has a plenty of arguments to adjust. The minimal command line arguments are:

java -jar swagger-codegen-cli.jar generate \
   -i https://api.angular.schule/swagger.json \
   -l typescript-angular \
   -o /var/tmp/angular_api_client

(Note: Windows users will have to write this without the backslashes and in one long line.)

  • -i or --input-spec defines the location of the input swagger spec, as URL or file (required)
  • -l or --lang defines the client language to generate (required)
  • -o or --output defines the output directory, where the generated files should be written to (current dir by default)
  • -c or --config defines the path to an additional JSON configuration file. Supported options can be different for each language. We will look at this argument in the paragraph.

Please type java -jar swagger-codegen-cli.jar help generate for a full explanation.

Generating code for angular

We should explore the configuration options for the angular-typescript codegen.
These options are specific to the generator.

java -jar swagger-codegen-cli.jar config-help -l typescript-angular

You will have to adjust the following options:

  • npmName: The name under which you want to publish generated npm package.
    Hint: You have to define a name here, or some files related to a proper npm package will be skipped and the generated README.md won't make that much sense! This is by design, see also #6369.
  • npmVersion: The version of the generated npm package. (default 1.0.0)
  • npmRepository: Use this property to set a URL to your private npm repository in the package.json. I really recommend setting the option, if you want to prevent accidental publishing to npmjs.com. (see publishConfig)
  • snapshot: When setting this to true the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm. This is very handy if you want to have unique package names to publish.
  • ngVersion: The version of angular that will be required by the generated package.json. It's a good idea to align this version with the angular version of your main app. The default is 4.3. A version smaller than 4.3 yields to the generation of the obsolete HttpService (German blogpost).

This is a complete example for our demo api:

java -jar swagger-codegen-cli.jar generate \
   -i https://api.angular.schule/swagger.json \
   -l typescript-angular \
   -o /var/tmp/angular_api_client \
   --additional-properties npmName=@angular-schule/book-monkey-api,snapshot=true,ngVersion=5.0.0

I wonder why the command line argument was called additional-properties! There must have been historical reasons... 😄 As already pointed out, you can also define the additional properties (=== options) via a config file. This cleans up the command a bit:

{
  "npmName": "@angular-schule/book-monkey-api",
  "npmVersion": "0.0.1",
  "snapshot": true,
  "ngVersion": "5.0.0"
}
java -jar swagger-codegen-cli.jar generate \
   -i https://api.angular.schule/swagger.json \
   -l typescript-angular \
   -o /var/tmp/angular_api_client \
   -c options.json

Don't accidentally publish to npmjs.com! 🚨

There is a minimal danger that you accidentally published your top-secret API client to the public npmjs.com repository! Please choose between one of the two options to prevent this:

  1. Set the npmRepository option. This will define a publishConfig.registry entry in the package.json.
  2. Use a npmName with a scope (e.g. @angular-schule/book-monkey-api) By default, scoped packages are private on npmjs.com. You have to explicelty make them public with --access=public. A scope can be easily redirected to a private registry, too. See this document for more information.

What's inside the box?

We should take a look at the generated files:

Screenshot

You will see that this is a complete angular project with all required config files and typescript files to create an angular package. It's a crazy world and unlike every other angular package we have to compile this again.

To install the required dependencies and to build the typescript sources run:

npm install
npm run build

Now everything is prepared to finally publish the package to NPM or a private repository.

npm publish dist --access=public

Consuming the API with @angular/cli

now navigate to the folder of your consuming Angular project and run

npm install @angular-schule/book-monkey-api --save

It's generally a good practice to extend the src/environments/*.ts files by adding a corresponding base path:

// src/environments/environments.ts
export const environment = {
  production: false,
  API_BASE_PATH: 'https://api.angular.schule'
};

The shortest possible setup looks like this:

// src/app/app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { ApiModule, BASE_PATH } from '@angular-schule/book-monkey-api';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [
    HttpClientModule,
    ApiModule
  ],
  providers: [{ provide: BASE_PATH, useValue: environment.API_BASE_PATH }],
  bootstrap: [AppComponent]
})
export class AppModule { }

You have to import both: the HttpClientModule from Angular as well as our generated ApiModule. You might wonder why HttpClientModule is included here. One could think that ApiModule should do that import for us, but this can lead to a strange but intended behaviour of Angular (see issue #20575).

The usage of the generated API is straightforward. Every REST operation has its own method. For example, if we want to get a list of all books, then we can simply import the BookService and call the corresponding method.

// src/app/app.component.ts
import { BookService } from '@angular-schule/book-monkey-api';

@Component({
  // ...
})
export class AppComponent {
  constructor(bookService: BookService) {

    bookService
      .booksGet()
      .subscribe(console.log);
  }
}

Conclusion

🎉 Congratulations! We have mastered a journey for automatically generated api code. Your project will benefit from less errors and more harmony between team members, who can concentrate on real solutions instead of boring boilerplate code.

Now it should be your task to automate the code generation on you CI system. On every (relevant) change of the backend you should also generate a new client. To upgrade to the latest version in your consuming Angular project, you just need to call npm install PACKAGE_NAME --save again.

You can find the full setup of the swagger codegen here:

The following Angular demo app is using the package @angular-schule/book-monkey-api:

Have fun doing awesome Angular stuff! 😄


Extra: Using own templates

Sooner or later everybody wants to customise some aspects of the generated code. You can change most parts by modifying the mustache-templates.

  • -t or --template-dir defines a folder containing own template files. If specified, those templates files will be used instead of the inbuilt versions. You can start by modifying the original ones from Github.

Extra: Building the codegen from the sources

You might want to use the very latest version directly from Github. Or you might want to contribute to the codegen -- that would be a great idea! All in all, a first start isn't that complicated, since everything is nicely prepared with Maven.

git clone https://github.com/swagger-api/swagger-codegen.git
cd swagger-codegen
mvn clean install

We are using the master branch, some unit test might be broken. Or you just want to save some time... Anyway, mvn clean package -Dmaven.test.skip will skip the tests. ;-)

Maven will create the necessary Java archive at the location modules/swagger-codegen-cli/target/swagger-codegen-cli.jar Now you should have created a snapshot version:

java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar version
> 2.4.0-SNAPSHOT

It's important to know that you have to use Java 7 or 8. It won't compile with Java 9.

Right now it's fine to install Java 8 as instructed in the README of swagger-codegen. This should be fixed some day! 😉 Just for the records, on my Mac I executed the following workaround:

brew cask install caskroom/versions/java8

export JAVA_HOME=`/usr/libexec/java_home -v 1.8`
export PATH=${JAVA_HOME}/bin:$PATH

java -version

Zurück | Back
Suggestions? Feedback? Bugs? Please fork/edit this page on Github.