Adding an Entity to the Backend

In this step you will add an entity for a todo item to your backend. This will enable you to store todo items in the database and also provide the frontend with a REST API to access and modify todo items. With FeGen you will also be able to generate client code for your frontend to easily use that REST API.

Creating the entity class

Create a java class next to the BackendApplication, so in the backend/src/main/java/com/example/backend directory and name it TodoItem. Add the @Entity annotation to it to tell Spring and FeGen that instances of this class can be saved to the database. Create a public field named id of type long within the class and annotate it with @Id and @GeneratedValue. This ID will be used by FeGen to refer to specific instances of the TodoItem when changing or deleting them and it will be automatically assigned to an entity when it is created. Also add a String field named text to the class that will contain the description of the todo item and a boolean field done. Since we are using Java for this guide, you also have to add the default getter and setter methods for all fields

Your class should now look like this:

package com.example.backend;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class TodoItem {

    @Id
    @GeneratedValue
    public long id;

    public String text;

    public boolean done;

    public long getId() {
      return id;
    }

    public void setId(long id) {
      this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }
}

Nullability

If you execute ./gradlew fegenWeb now, you will get the following error message and code generation will fail:

[FeGen EntityMgr] Field "text" in entity "com.example.backend.TodoItem" is implicitly nullable.
[FeGen EntityMgr]     Please add a @Nullable annotation if this is intentional
[FeGen EntityMgr]     or add a @NotNull annotation to forbid null values
[FeGen EntityMgr]     Set implicitNullable to WARN to continue the build despite missing @Nullable annotations

This is due to the different handling of nullability in Spring and Java versus the target languages of FeGen (Kotlin and Typescript).

In Java, null is a valid value for every non-primitive type. There are no compile time checks whether a value may be null, so you may e.g. just access fields of a variable and if that variable actually contains null at runtime, this would just cause a NullPointerException.

On the other hand, in Kotlin and Typescript nullability is a property of the type that is in both cases declared with a ?. An object's field may either be nullable, in which case the compiler forces you to handle the case that it contains null, or it may not be nullable, in which case it may never be set it to null.

Since the code generated by FeGen will be in Typescript, FeGen needs to know whether the fields of your entity may be null or not. If they are not annotated, Spring will automatically assume that they are nullable. FeGen could just go with that and give the fields the nullable type in the generated code, but since Typescript will by default enforce handling the null case, it is easier to work with a not nullable typed field if you know that its content may never be null.

In our case, text is the essential field that an item on a todo list is composed of, so annotate it with @Column(nullable = false).

Of course, FeGen also accepts @Column(nullable = true), since you just have to explicitly state that you want your field to be nullable. Instead of @Column, you may also use @NotNull or @Nullable in case you are using Spring validation anyway. You do not need to specify nullability for primitive values, as they cannot be null even in Java (this is the case for the done field). By default, the build process will stop if you have not declared nullability for an entity's field. Although not recommended, you can configure FeGen to continue the build or even not show a warning at all. Refer to the Configuration Options page to see how this can be done.

Base Projections

If you execute ./gradlew fegenWeb now, you will still see a warning by FeGen:

[FeGen ProjectionMgr] The following entities do not have a base projection:
[FeGen ProjectionMgr] TodoItem

Base projections are needed for technical reasons. They are interfaces that should be nested into the entity class that they belong to. In our case, it would look like this:

@Entity
public class TodoItem {
    // ...
    
    @Projection(name = "baseProjection", types = {TodoItem.class})
    interface BaseProjection {
        long getId();
        String getText();
        boolean isDone();
    }
}

They need to meet the following criteria to be accepted by FeGen:

  • Have a @Projection annotation with "baseProjection" as name and the corresponding entity class as only item in types
  • Have a method defined for each property of the corresponding entity whose type is not an entity or projection
    • The return type of the method must match the type of the corresponding property
    • The name of the method must be the name of the getter of the property, even if no getter exists on the entity

This guide does not handle using other entities or projections as entities' fields as mentioned in the second bullet point. To learn about it, go to the Relationships page of the reference documentation

Your complete entity should look like this now and FeGen should not complain about it anymore:

package com.example.backend;

import org.springframework.data.rest.core.config.Projection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class TodoItem {

    @Id
    @GeneratedValue
    public long id;

    @Column(nullable = false)
    public String text;

    public boolean done;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    @Projection(name = "baseProjection", types = {TodoItem.class})
    interface BaseProjection {
        long getId();
        String getText();
        boolean isDone();
    }
}

Repository

Although FeGen is happy with your entity, it still won't generate any code to access it. That is because you have not yet defined a repository for it, so Spring will not offer you any way to interact with your entity from your frontend. To change that, create an interface in the same directory as your TodoItem entity, name it TodoItemRepository and have it extend JpaRepository<TodoItem, Long>:

package com.example.backend;

import org.springframework.data.jpa.repository.JpaRepository;

public interface TodoItemRepository extends JpaRepository<TodoItem, Long> {}

Now FeGen will output the Typescript code necessary to access your TodoItem entity.

CORS

In this guide, your backend will run on port 8080, since this is spring's default port, and your frontend will use port 5000 which is the default port of the Rollup development server. Without any further configuration, your browser will prevent your frontend from accessing the backend's API, because it will enforce the rules for Cross Origin Resource Sharing. You will have to configure your backend to explicitly allow access from your frontend. Create a class named Config, place it in the same directory as the other classes you created and enter the following content:

package com.example.backend;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class Config {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                configureCors(registry);
            }
        };
    }

    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer() {
        return new RepositoryRestConfigurer() {
            @Override
            public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
                configureCors(cors);
            }
        };
    }

    private void configureCors(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:5000")
                .allowedMethods("GET", "POST", "PUT", "DELETE");
    }
}

The two methods returning Configurers will ensure that each response to a request from your frontend will include an HTTP header that declares that this access is allowed. The second method is important for us as it includes that header when calling repository endpoints to access entities. The first method is not strictly necessary right now, but will add the header if you create and use custom endpoints.

This is all you have to do for the backend. Go to the next page to learn how to use the code generated by FeGen in the frontend to finish your web app.