Spring REST TypeScript Generator

I have been a backend developer for a few months without writing a single line of code on the client-side, but with the dawn of single-page applications, my cooperation with web developers got closer. I started to observe their work with interest. There are so many exciting possibilities on the frontend side. It made me consider to build up my technology stack and become a full stack developer.

I noticed that web developers create TypeScript models and services that reflect our backend model and REST services. It was a tedious job. What is more, after they finally mirrored what we had on the backend, it wasn't the end. Because we always have to keep in mind one thing that is very common in software development... CHANGE. Due to some new business requirements, backend services were modified. The change forced frontend developers to reanalyze backend services and refactor frontend applications so that they would match the server-side.

After some time, I have started my adventure with web development and came up with the idea that all this code could be generated based on type information in Java. I began to look for a TypeScript generator that would be able to create models and services in TypeScript base upon REST interfaces. As my research revealed, there were some libraries providing such functionality already, but none of them covered all our needs such as:

  • Support for JavaBean convention.
  • Support for FasterXML/Jackson annotation.
  • Support for Spring framework — the generation of TypeScript services that can call REST APIs developed in Spring.
  • Generated services aligned with Angular and ReactJS specific requirements (Observable or Promises API).
You may also like: TypeScript Practical Introduction, Part 1.

As a result, we decided to launch a small off-hours project that would provide the above functionality. It works, and we came up with the solution ready to use. It was tested and used in most of our commercial projects where web applications were based on Angular or React.

As our experienced showed, our library brought great benefits. The average generated code was on a level of 20% of web application codebase, but in terms of saved work on changes and tests, it was invaluable. Having such promising results, our company decided to make the project open source. If your development setup uses the Spring framework on the backend and Angular or React on the frontend side, you can experience the same benefits that we did. In this short article, I would like to introduce how you can REST with our spring-rest-2-ts TypeScript generator.

Examples

To get an idea of what spring-rest2ts generator can do, let's create a simple model and REST controller in Java, and we will show what will be generated in TypeScript

public class BaseDTO {
    private int id;
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private Date updateTimeStamp;
 }
public class OrderDTO extends BaseDTO {
    private double price;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    private LocalDateTime orderTimestamp;
}


Spring REST controller:

@Controller
@RequestMapping("api/order")
public class OrderCtrl {
    @PostMapping(consumes = {"application/json"}, produces = {"application/json"})
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public OrderDTO createOrder(@RequestBody OrderDTO entity) {
        return entity;
    }
    @RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseBody
    public OrderDTO getOrder(@PathVariable int id) {
        return new OrderDTO();
    }
}


In TypeScript, for DTO classes, we get two interfaces with mapped inheritance, where each field is mapped to their respective TypeScript types:

export interface Base {
    id: number;
    updateTimeStamp: number;
}
export interface Order extends Base {
    price: number;
    /**
     *    pattern : dd-MM-yyyy hh:mm:ss
     */
    orderTimestamp: string;
}


As we see, if a field has Jackson annotation, it is taken into account. If not, its transformation is based on Java types to TypeScript mapping. There is support for Type names mapping. In Java, we see that there is OrderDTO by providing proper name mapper which cuts off postfix DTO and we get type Order

Observable Based Service

The mapping of model classes is quite easy to understand. What's more interesting is the mapping of Spring REST controllers for which, in TypeScript, there is a generated implementation to call endpoints. Such an approach hides under method names, paths, and parameters, so the code will be resistant to changes on the backend. What is more important is that we transform return types to selected web frameworks; for Angular 2+ there is a generated valid Angular service ready to use for injection:

@Injectable()
export class OrderService {
    httpService: HttpClient;
    public constructor(httpService: HttpClient) {
        this.httpService = httpService;
    }
    public createOrder(entity: Order): Observable<Order> {
        let headers = new HttpHeaders().set('Content-type', 'application/json');
        return this.httpService.post<Order>('api/order', entity, {headers});
    }
    public getOrder(id: number): Observable<Order> {
        return this.httpService.get<Order>('api/order/' + id + '');
    }
}


OrderService is generated for OrderCtrl. Here, the type name was also transformed by the type name mapper. If the REST API is not available on the same host as the web application, there is a possibility to configure baseURL, which could be a path prefix o entire host reference

Promise Based Service

For web frameworks that are using the Promise API generator, proper configuration is also able to generate a service class:

export class OrderService {
    baseURL: URL;
    public constructor(baseURL: URL = new URL(window.document.URL)) {
        this.baseURL = baseURL;
    }
    public createOrder(entity: Order): Promise<Order> {
        const url = new URL('/api/order', this.baseURL);
        return fetch(url.toString(), {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(entity)
        }).then(res => res.json());
    }
    public getOrder(id: number): Promise<Order> {
        const url = new URL('/api/order/' + id + '', this.baseURL);
        return fetch(url.toString(), {method: 'GET'}).then(res => res.json());
    }
}


Configuration

Due to the greater flexibility, TypeScript generator is configured by code; no configuration files are needed. This gives a possibility to easily extend generator in places where it is needed. Here is the simplest generator configurator:

    Rest2tsGenerator tsGenerator = new Rest2tsGenerator();
    // Java Classes filtering
    tsGenerator.setModelClassesCondition(new ExtendsJavaTypeFilter(BaseDTO.class));
    tsGenerator.setRestClassesCondition(new ExtendsJavaTypeFilter(BaseCtrl.class));
    // Java model classes converter setup
    JacksonObjectMapper jacksonObjectMapper = new JacksonObjectMapper();
    jacksonObjectMapper.setFieldsVisibility(JsonAutoDetect.Visibility.ANY);
    modelClassesConverter = new ModelClassesToTsInterfacesConverter(jacksonObjectMapper);
    modelClassesConverter.setClassNameMapper(new SubstringClassNameMapper("DTO", ""));
    tsGenerator.setModelClassesConverter(modelClassesConverter);
    // Spring REST controllers converter
    restClassesConverter = new SpringRestToTsConverter(new Angular4ImplementationGenerator());
    restClassesConverter.setClassNameMapper(new SubstringClassNameMapper("Ctrl", "Service"));
    tsGenerator.setRestClassesConverter(restClassesConverter);
    // set of java root packages for class scanning
    javaPackageSet = Collections.singleton("com.blueveery.springrest2ts.examples");
    tsGenerator.generate(javaPackageSet, Paths.get("../target/ts-code"));


Comments

Popular posts from this blog

SSO — WSO2 API Manager and Keycloak Identity Manager

Single Value Decomposition in Data Science

Video Analysis: Creating Highlights Using Python