/apina

Tool for creating client-side TypeScript code from server-side APIs

Primary LanguageKotlinMIT LicenseMIT

Apina

Apina creates client-side TypeScript code for either for Angular or Fetch API based on server-side APIs. Apina reads Spring Web MVC's @RestController annotated classes and their related Jackson classes and creates code for the data model and for executing HTTP-requests.

Read the manual for details.

Quick start using Gradle

Include something like the following in your web application project:

plugins {
    id("fi.evident.apina") version "0.23.1"
}

tasks.apina {
    // Set the name of the created TypeScript file. Default is 'build/apina/apina.ts'.
    target.set(project(":frontend").layout.projectDirectory.file("app/apina-api.ts"))

    // Specify types that should not be generated, but are implemented manually
    // and should be imported to the generated code. Keys are module paths, values
    // are list of types imported from the module.
    imports.set(mapOf(
        "./my-time-module" to listOf("Instant", "LocalDate"),
        "./other-module" to listOf("Foo", "Bar")
    ))

    // How Java enums are translated to TypeScript enums? (Default mode is 'default'.)
    //  - 'default'      => enum MyEnum { FOO = "FOO", BAR = "BAR", BAZ = "BAZ" }
    //  - 'int_enum'     => enum MyEnum { FOO, BAR, BAZ }
    //  - 'string_union' => type MyEnum = "FOO" | "BAR" | "BAZ"
    enumMode.set(EnumMode.DEFAULT)

    // How nullables are translated to TypeScript interfaces? (Default mode is 'NULL'.)
    //  - 'NULL'      => name: Type | null
    //  - 'UNDEFINED' => name?: Type
    optionalTypeMode.set(OptionalTypeMode.NULL)

    // Which controllers to include when generating API? Defaults to everything.
    endpoints.set(listOf("""my\.package\.foo\..+"""))

    // If generated URLs would start with given prefix, removes it. Useful when configuring Apina
    // to work behind reverse proxies. Defaults to empty string (URL is not modified).
    removedUrlPrefix.set("/foo")

    // Code generation target (Default is 'angular')
    // - 'angular' => Generate Angular module that uses Angular's HttpClient
    // - 'es6' => Generate code that uses Fetch API and has no dependencies apart from ES6
    platform.set(Platform.ANGULAR)
}

// Tell the frontend to run apina before setup
// (the 'setup' task will probably be different for you)
tasks.findByPath(":frontend:setup").dependsOn(tasks.apina)

Using generated code

Make your application dependent on Apina and HttpClient:

import { provideHttpClient } from "@angular/common/http";
import { provideApina } from 'apina';

bootstrapApplication(AppComponent, {
    providers: [
        provideHttpClient(),
        provideApina()
    ]
});

Then just inject the generated endpoint and use it:

import { DocumentsEndpoint } from 'apina';

@Injectable({providedIn: 'root'})
class MyService {

    constructor(private readonly documentsEndpoint: DocumentsEndpoint) { }

    load() {
        this.documentsEndpoint.findDocument(42).subscribe(doc =>
            console.log("loaded", doc);
        );
    }
}

Modules

  • apina-core main apina code
  • apina-cli command line interface for running conversion
  • apina-gradle plugin for Gradle