TypeScript Endpoints Generator
The TypeScript generator produces TypeScript files based on the information from an OpenApi document which is generated from Java files in src/main/java
folder by default.
Note | Vaadin uses OpenAPI Specification as a middle layer between Java endpoints and TypeScript endpoint clients. The current implementation is based on OpenAPI specification 3.0. For details, please refer to the appendix at the end of this page. |
Examples
A simple generated TypeScript files will look like the following snippet:
/**
* User endpoints.
*
* This module has been generated from UserEndpoints.java
* @module UserEndpoints
*/
import client from './connect-client.default';
/**
* Check if a user is admin or not.
*
* @param id User id to be checked
* Return Return true if the given user is an admin, otherwise false.
*/
export async function isAdmin(
id?: number
) {
return await client.call('UserEndpoints', 'isAdmin', {id});
}
The import client from './connect-client.default'
is a static part of any generated file.
connect-client.default.ts
is another generated file which includes default configurations for the ConnectClient
and exports its instance as client
.
Each method in the generated modules is corresponding to a Java method in @Endpoint
annotated classes.
For example, the following Java code is corresponding to the generated UserEndpoints.ts:
/**
* User endpoints.
*/
@Endpoint
public class UserEndpoints {
/**
* Check if a user is admin or not.
*
* @param id
* User id to be checked
* @return Return true if the given user is an admin, otherwise false.
*/
public boolean isAdmin(long id) {
return id == 0;
}
}
Note | For more information about type mapping between Java and TypeScript, please refer to type conversion page. |
Type Nullability
Types are either nullable (optional) or non-nullable (required). By default, types are mapped and generated using the Java rules:
Any primitive type, such as
int
, is non-nullable.Any reference type, such as
String
orInteger
, is nullable.A collection accepts
null
unless the collection item type is primitive.A map accepts
null
unless the collection item type is primitive.
Any of mentioned nullable types can be made non-nullable with a @Nonnull
annotation applied.
You may use any annotation that has a nonnull
(case-insensitive) name, for example:
javax.annotation.Nonnull
edu.umd.cs.findbugs.annotations.NonNull
lombok.NonNull
android.support.annotation.NonNull
org.eclipse.jdt.annotation.NonNull
any other annotation (including custom) that has the
nonnull
(case-insensitive) name.
Endpoint Functions
For the endpoint function, nullable elements are:
Function parameter type. Arguments cannot be omitted even when the parameter types are nullable. To receive a
null
parameter value in Java, send anundefined
argument in the endpoint function call.Function return type.
@Endpoint
class PersonEndpoint {
// Person must have at least the first and the last name
public void setFullName(@Nonnull String firstName, @Nonnull String lastName, String middleName) {
// omitted code
}
// Full name must exist
@Nonnull
public String getFullName() {
// omitted code
}
// Person can have no connections with other people. But if they have,
// the connection cannot be null.
public Map<String, @Nonnull String> getConnections() {
// omitted code
}
}
export async function setName(
firstName: string,
lastName: string,
middleName: string | undefined
) {
return client.call('PersonEndpoint', 'setFullName', {firstName, lastName, middleName});
}
export async function getFullName(): Promise<string> {
return client.call('PersonEndpoint', 'getFullName');
}
export async function getConnections(): Promise<Record<string, string> | undefined> {
return client.call('PersonEndpoint', 'getConnections');
}
Data Class Properties
Properties of the data classes are nullable. Unlike the function parameters, all nullable properties can be omitted.
public class MyBean {
private long id;
@Nonnull
private String value;
private String description;
private Map<String, String> map;
@Nonnull
private List<String> list;
}
export default interface MyBean {
id: number;
value: string;
description?: string;
map?: Record<string, string | undefined>;
list: Array<string | undefined>;
}
Collection Item Types
The collection item type is nullable.
public class MyBean {
private List<String> list;
private List<@Nonnull String> nonNullableList;
private Map<String, String> map;
private Map<String, @Nonnull String> nonNullableMap;
}
export default interface MyBean {
list?: Array<string | undefined>;
nonNullableList?: Array<string>;
map?: Record<string, string | undefined>;
nonNullableMap?: Record<string, string>;
}
Enum
The Java enum
type is mapped to an enum
TypeScript type.
It is an object type, so you can work with it as you work with regular TypeScript objects.
public enum Enumeration {
FIRST,
SECOND,
}
export enum Enumeration {
FIRST = "FIRST",
SECOND = "SECOND"
}
Note | Complex Java enums mapping The |
public enum Enumeration {
FIRST("ONE"),
SECOND("TWO");
private String value;
public Enumeration(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
export enum Enumeration {
FIRST = "FIRST",
SECOND = "SECOND"
}
Appendix: How The Generator Generate TypeScript From OpenAPI Specification
Modules
The generator collects all the tags
field of all operations in the OpenAPI document.
Each tag generates a corresponding TypeScript file.
The tag name is used for TypeScript module name as well as the file name.
TsDoc of the class is fetched from description
field of the tag object which has the same name as the class.
Methods
Each exported method in a module is corresponding to a POST operation of a path item in paths object.
Note | Currently, the generator only supports |
The path must start with /
as described in Patterned Fields.
It is parsed as /<endpoint name>/<method name>
which are used as parameters to call to Java endpoints in the backend.
Method name from the path is also reused as the method name in the generated TypeScript file.
Method’s Parameters
Parameters of the method are taken from the application/json
content of request body object.
To get the result as [UserEndpoint.ts], the request body content should be:
{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "User id to be checked"
}
}
}
}
}
}
Type and description of each property are used for TsDoc that describes the parameter in more details.
Note | All the other content types of request body object are not ignored by the Vaadin Generator.
It means that without the |
Method’s Return Type
Return type and its description are taken from the 200
response object.
As same as request body object, the generator is only interested at application/json
content type.
The schema type indicates the return type and the description describes the result.
Here is an example of a responses objects:
{
"200": {
"description": "Return true if the given user is an admin, otherwise false.",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
}
Note | At this point, the generator only takes the advantage of |
Method’s TsDoc
The TsDoc of the generated method is stored as description
value of the POST
operation in path item.
A valid POST
operation combined with [request-body] and [response-object] would look like:
{
"tags": ["UserEndpoint"], 1
"description": "Check if a user is admin or not.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "User id to be checked"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Return true if the given user is an admin, otherwise false.",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
}
}
As mentioned in operation object specification, in Vaadin Generator,
tags
are used to classify operations into TypeScript files. It means each tag will have a corresponding generated TypeScript file. The operations, which contain more than one tag, will appear in all generated files. Empty tags operations will be placed inDefault.ts
file.
Note | Although multiple tags do not break the generator, it might be confusing in the development time when there are two exact same methods in different TypeScript files. It is recommended to have only one tag per operation. |
Here is an example OpenAPI document which could generate the above [UserEndpoint.ts].
{
"openapi" : "3.0.1",
"info" : {
"title" : "My example application",
"version" : "1.0.0"
},
"servers" : [ {
"url" : "https://myhost.com/myendpoint",
"description" : "Vaadin backend server"
} ],
"tags" : [ {
"name" : "UserEndpoint",
"description" : "User endpoint class."
} ],
"paths" : {
"/UserEndpoint/isAdmin" : {
"post": {
"tags": ["UserEndpoint"],
"description": "Check if a user is admin or not.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [ "id" ],
"properties": {
"id": {
"type": "number",
"description": "User id to be checked"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Return true if the given user is an admin, otherwise false.",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
}
}
}
}
}