Vaadin Fusion Quick Start Tutorial

This tutorial teaches you the core concepts of Vaadin Fusion while you build a full-stack application for managing to-do items. After completing the tutorial, you are ready to start experimenting on your own or continue to more advanced tutorials.

Required Tools

You need the following tools and libraries to complete the tutorial:

Creating a New Vaadin Fusion Project

Download a starter project from start.vaadin.com.

The pre-configured starter project includes:

  • An empty Todo view

  • H2 database and Spring Data JPA dependencies

Importing a Vaadin Fusion Project

Unzip the downloaded file and open the project in your IDE. The instructions in this tutorial assume you use VS Code.

Open the project by either:

  • Navigating to the project folder and running code . (note the period).

  • Choosing File > Open…​ in VS Code and selecting the project folder.

Installing the following plugins in VS Code is recommended for an optimal development experience:

VS Code should automatically suggest these for you when you open the project.

Project Structure and Architecture

Vaadin Fusion projects are based on Spring Boot and use Maven for project management.

Fusion project structure

The two important folders to know are:

frontend

This is where your views and frontend code live.

src

This is where your Java backend code lives.

The key files in a Vaadin Fusion application are:

pom.xml

The project configuration file which defines dependencies.

frontend/index.html

The bootstrap page (you usually do not need to change this).

frontend/index.ts

Defines routing.

src/main/java/com/example/application/Application.java

Runs the Spring Boot application.

Defining the Data Model and Service Layer

Begin by setting up the data model and services for accessing the database.

You can do it in two steps:

  1. Define an entity.

  2. Create a repository for accessing the database.

This tutorial shows how to use an in-memory H2 database and JPA for persistence. The starter you download already includes the needed dependencies in pom.xml.

Define a JPA entity class for the data model, by creating a new Todo.java file in src/main/java/com/example/application/data/entity with the following content:

package com.example.application.data.entity;

import javax.persistence.Entity;
import javax.validation.constraints.NotBlank;

import com.example.application.data.AbstractEntity;

@Entity 1
public class Todo extends AbstractEntity { 2

 private boolean done = false;
 @NotBlank 3
 private String task;

 public Todo() {
 }

 public Todo(String task) {
   this.task = task;
 }

 public boolean isDone() {
   return done;
 }

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

 public String getTask() {
   return task;
 }

 public void setTask(String task) {
   this.task = task;
 }
}
  1. Turn the class into a JPA entity with an @Entity annotation.

  2. Extend from AbstractEntity to get id generation, equals and hashCode.

  3. Add a @NotBlank Java bean validation annotation to enforce validity both in the view and on the server.

Next, create a repository for accessing the database. You only need to provide an interface with type information, Spring Data takes care of the implementation.

Create a new file, TodoRepository.java, in src/main/java/com/example/application/data/service with the following contents:

package com.example.application.data.service;

import com.example.application.data.entity.Todo;

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

public interface TodoRepository extends JpaRepository<Todo, Integer> {

}

You now have all the necessary backend code in place to start building a UI.

Run the project from the command line with the following command:

mvn

The first time you run mvn, it may take up to a few minutes as it downloads all dependencies and builds a frontend bundle. On the following builds mvn does not download dependencies and builds are much faster.

When the build has finished, you should see the application running on http://localhost:8080.

Create a Typed Server Endpoint

One of the key features of Vaadin Fusion is type-safe server access through endpoints. When you define an @Endpoint, Vaadin creates the needed REST-like endpoints, secures them, and generates TypeScript interfaces for all the used data types and public methods. Having full-stack type safety helps you stay productive through autocomplete and helps guard against accidental UI breakage when the data model changes on the server.

Create a new TodoEndpoint.java file in src/main/java/com/example/application/data/endpoint:

package com.example.application.data.endpoint;

import java.util.List;

import com.example.application.data.entity.Todo;
import com.example.application.data.service.TodoRepository;
import com.vaadin.fusion.Endpoint;
import com.vaadin.flow.server.auth.AnonymousAllowed;

@Endpoint 1
@AnonymousAllowed 2
public class TodoEndpoint {
 private TodoRepository repository;

 public TodoEndpoint(TodoRepository repository) { 3
   this.repository = repository;
 }

 public List<Todo> findAll() {
   return repository.findAll();
 }

 public Todo save(Todo todo) {
   return repository.save(todo);
 }
}
  1. Annotating a class with @Endpoint exposes it as a service for client-side views. All public methods of an endpoint are callable from TypeScript.

  2. By default, endpoint access requires an authenticated user. @AnonymousAllowed enables access for anyone. See Configuring Security for more information on endpoint security.

  3. Use Spring to automatically inject the TodoRepository dependency for database access.

Save the file and ensure the change is loaded. You should see log output from the reload (that ends with a Frontend compiled successfully message) in the console.

If you did not have the server running, or if something failed, (re)start the server with the mvn command.

An Introduction to Building Reactive UIs

Building reactive views and components may take some getting used to, if your background is in Vaadin Flow, jQuery, or another imperative model. Reactive views have fewer moving parts and because of this they are easier to understand and debug. In reactive UI programming, your UI is a function of the component’s state. Whenever the state changes, the UI is re-rendered.

In imperative UI programming, there are two states: the data model and the UI. It is your responsibility as a developer to keep them in sync. In reactive programming, there is only one state: the data. The UI always reflects that state.

public class ImperativeView extends Div {
 H1 header = new H1();
 ListItem email = new ListItem();
 ListItem phone = new ListItem();

 public ImperativeView() {
   add(header, new UnorderedList(email, phone));
 }

 // Update UI when contact changes
 public void setContact(Contact contact) {
   header.setText(String.format("Details for %s %s",
     contact.getFirstName(),
     contact.getLastName()));
   email.setText("Email: " + contact.getEmail());
   phone.setText("Phone: " + contact.getPhone());
 }
}

When building a UI imperatively, you need to keep references to elements so you can update their values when the underlying state (the Contact) changes.

export class ReactiveView extends View {
 @property({ type: Object })
 contact: Contact = {...};

 // Render is called automatically when contact changes
 render() {
   return html`
     <h1>Details for ${this.contact.firstName} ${this.contact.lastName}</h1>
     <ul>
       <li>Email: ${this.contact.email}</li>
       <li>Phone: ${this.contact.phone}</li>
     </ul>
   `;
 }
}

When building a UI reactively, you define a template using values from the state (Contact). Whenever the model changes, the template is automatically updated.

Tip
Avoid element references in reactive views

As a rule of thumb, you should not use document.querySelector or LitElement @query to get element reference in order to set values or properties imperatively. There is almost always a way to achieve the same with binding values through the template.

Lit Basics

Vaadin Fusion uses the Lit library for client-side views. It is a lightweight and highly performant library for building reactive components with declarative templates.

You can learn the basics of Lit in the Lit basics article.

Building the Todo View Component

Now that you know the basics of reactive UI programming and Lit, you are ready for the final part of the tutorial: creating a view for adding and viewing todo items.

Open frontend/views/todo/todo-view.ts and replace its contents with the following:

import {
  1
  css,
  html,
  LitElement
} from 'lit';

import { customElement, state } from 'lit/decorators.js';

import '@vaadin/vaadin-text-field';
import '@vaadin/vaadin-button';
import '@vaadin/vaadin-checkbox';
import { Binder, field } from '@vaadin/form';
import * as TodoEndpoint from 'Frontend/generated/TodoEndpoint';
import Todo from 'Frontend/generated/com/example/application/data/entity/Todo';
import TodoModel from 'Frontend/generated/com/example/application/data/entity/TodoModel';

@customElement('todo-view') 2
export class TodoView extends LitElement { 3
}
  1. Import the UI components, helpers, and generated TypeScript models required for building the view.

  2. Register the new component with the browser. This makes it available as <todo-view>. The routing in index.ts is already set up to show it when you navigate to the application.

  3. Define the component class that extends from LitElement.

Defining the View State

Inside the TodoView class, define the view state as follows:

 @state()
 private todos: Todo[] = []; 1
 private binder = new Binder(this, TodoModel); 2
  1. The list of Todo items is private and decorated with @state() so Lit observes it for changes.

  2. A Vaadin Binder is used to handle the form state for creating new Todos. TodoModel is automatically generated by Vaadin. It describes the data types and validations that Binder needs. Read more about forms in Creating Client-Side Forms.

Defining CSS

 static styles = css`
   :host {
     display: block;
     padding: var(--lumo-space-m) var(--lumo-space-l); 1
   }
 `;
  1. The padding property is defined using the spacing properties to be consistent with the rest of the app.

Defining the HTML Template

Start by defining a render() method that returns a html template literal.

render() {
 return html`

 `;
}

Add the following code within the html template:

return html`
   <div class="form">
    <vaadin-text-field
      ...="${field(this.binder.model.task)}"
    ></vaadin-text-field> 1
    <vaadin-button
      theme="primary"
      @click=${this.createTodo 2}
      ?disabled=${this.binder.invalid 3}
      >Add</vaadin-button>
   </div>
`;
  1. The text field component is bound to the task property of a Todo using …​="${field(this.binder.model.task)}". The …​="${}" syntax is a spread operator, applying several properties at once. You can read more about forms in Creating Client-Side Forms.

  2. The click event of the Add button is bound to the createTodo() method.

  3. The button is disabled if the form is invalid.

Right underneath the previous <div>, add the following code:

return html`
   <div class="todos">
    ${this.todos.map((todo) => html` 1
        <div class="todo">
          <vaadin-checkbox
            ?checked=${todo.done 2}
            @checked-changed=${(e: CustomEvent) => 3
              this.updateTodoState(todo, e.detail.value)}
          ></vaadin-checkbox>
          <span>${todo.task}</span>
        </div>
      `)}
   </div>
`;
  1. The existing todo items are shown by mapping the todos array to Lit templates. The template for a single Todo contains a checkbox and the task text.

  2. Bind the checked boolean attribute to the done property on the todo.

  3. Call the updateTodoState() method, with the todo and the new value whenever the checked value changes.

Updating the View State and Calling the Backend

Below the render() method in the TodoView class, add a connectedCallback() lifecycle callback to initialize the view when it is attached to the DOM.

async connectedCallback() { 1
 super.connectedCallback(); 2
 this.todos = await TodoEndpoint.findAll(); 3
}
  1. Use an async function to make it easier to handle asynchronous code.

  2. Remember to call the superclass method.

  3. The getTodos() method is automatically generated by Vaadin based on the method in TodosEndpoint.java. The method was imported in the head of the file. The await keyword waits for the server response without blocking the UI.

Below the connectedCallback(), add another method to handle the creation of new `Todo`s.

async createTodo() {
 const createdTodo = await this.binder.submitTo(TodoEndpoint.save); 1
 if (createdTodo) { 2
   this.todos = [...this.todos, createdTodo];
   this.binder.clear();
 }
}
  1. Use binder to submit the form to TodoEndpoint. The binder validates the input before posting it and the server re-validates it.

  2. If the Todo was saved successfully, update the todos array and clear the form.

Finally, add a method for updating the todo state right below createTodo():

updateTodoState(todo: Todo, done: boolean) {
 const updatedTodo = { ...todo, done }; 1
 this.todos = this.todos.map((t) => (t.id === todo.id ? updatedTodo : t)); 2
 TodoEndpoint.save(updatedTodo); 3
}
  1. Create a new Todo with the updated done state.

  2. Update the local todos array with the new state. The map operator creates a new array where the changed todo is swapped out.

  3. Save the updated todo to the server.

Complete View Code

The completed view code is as follows:

import {
  css,
  html,
  LitElement
} from 'lit';

import { customElement, state } from 'lit/decorators.js';

import '@vaadin/vaadin-text-field';
import '@vaadin/vaadin-button';
import '@vaadin/vaadin-checkbox';
import { Binder, field } from '@vaadin/form';
import * as TodoEndpoint from 'Frontend/generated/TodoEndpoint';
import Todo from 'Frontend/generated/com/example/application/data/entity/Todo';
import TodoModel from 'Frontend/generated/com/example/application/data/entity/TodoModel';

@customElement('todo-view')
export class TodoView extends LitElement {
 @state()
 private todos: Todo[] = [];
 private binder = new Binder(this, TodoModel);

 static styles = css`
   :host {
     display: block;
     padding: var(--lumo-space-m) var(--lumo-space-l);
   }
 `;

 render() {
   return html`
     <div class="form">
       <vaadin-text-field
         ...="${field(this.binder.model.task)}"
       ></vaadin-text-field>
       <vaadin-button
         theme="primary"
         @click="${this.createTodo}"
         ?disabled="${this.binder.invalid}"
         >Add</vaadin-button
       >
     </div>
     <div class="todos">
       ${this.todos.map(
         (todo) => html`
           <div class="todo">
             <vaadin-checkbox
               ?checked="${todo.done}"
               @checked-changed="${(
                 e: CustomEvent
               ) => this.updateTodoState(todo, e.detail.value)}"
             ></vaadin-checkbox>
             <span>${todo.task}</span>
           </div>
         `
       )}
     </div>
   `;
 }

 async connectedCallback() {
   //(1)
   super.connectedCallback();
   this.todos = await TodoEndpoint.findAll();
 }

 async createTodo() {
   const createdTodo = await this.binder.submitTo(TodoEndpoint.save);
   if (createdTodo) {
     this.todos = [...this.todos, createdTodo];
     this.binder.clear();
   }
 }

 updateTodoState(todo: Todo, done: boolean) {
   const updatedTodo = { ...todo, done };
   this.todos = this.todos.map((t) => (t.id === todo.id ? updatedTodo : t));
   TodoEndpoint.save(updatedTodo);
 }
}

Run the Completed Application

Start your server with the mvn command if you do not already have it running.

Open http://localhost:8080 in your browser and you should now have a fully functional todo application. Notice that you can refresh the browser and it keeps the same todo items as they are persisted in the database.

Optional: Deploy the Application to Heroku

The following steps are optional. You can follow them to deploy your application to Heroku, a cloud deployment platform that offers a free tier that does not require a credit card.

Preparing the Application for Production

It’s important to build a separate production-optimized version of the application before deploying it. In development mode, Vaadin has a live-reload widget, debug logging, and uses a quick, but unoptimized, frontend build that includes source maps for easy debugging. Unoptimized frontend bundles can contain several megabytes of JavaScript.

The pom.xml build includes a production profile configuration that prepares an optimized build that’s ready for production.

Using a PostgreSQL Database in Production

During development, the application has used an in-memory H2 database. It is convenient and works well for a single user. In production, you want to use something more robust and persistent. Heroku’s free tier supports PostgreSQL, so can configure your application to use that.

First, add the PostgreSQL dependency in the production profile of pom.xml:

<profile>
 <id>production</id>
 <!-- Omitted -->
 <dependencies>
   <dependency>
     <groupId>org.postgresql</groupId>
     <artifactId>postgresql</artifactId>
   </dependency>
 </dependencies>
</profile>

Next, configure how JPA should handle schema generation. Add the following two properties to the end of application.properties.

spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
Warning
Avoid data loss

This setup recreates the database on every deployment. If you are working with real data, you should use ddl-auto=none and instead use a database migration tool like Liquibase or Flyway so you can evolve the database schema without losing data.

Building a Production-Optimized JAR

Build the application with the production profile:

mvn clean package -Pproduction

This builds a production-optimized JAR file in the target folder.

Creating a Heroku Account and Installing Heroku CLI

Complete the following steps to create a Heroku account and install the Heroku CLI.

  1. Go to https://signup.heroku.com/, create a new account, and verify your email.

  2. Go to https://devcenter.heroku.com/articles/heroku-cli and follow the instructions for installing the CLI on your operating system.

Deploying a Vaadin Application to Heroku

Use the Heroku CLI to create and deploy your application.

  1. Log in:

    heroku login
  2. Install the Heroku Java plugin:

    heroku plugins:install java
  3. Create a new app. Replace APPNAME with a name of your choice. APPNAME is part of the URL, like https://APPNAME.herokuapp.com, so choose a name that’s unique and easy to remember.

    heroku create APPNAME
  4. Enable the PostgreSQL plugin for the newly created app:

    heroku addons:create heroku-postgresql -a APPNAME
  5. Deploy the production-optimized JAR file you created in the previous section.

    heroku deploy:jar target/fusioncrmtutorial-1.0-SNAPSHOT.jar -a APPNAME
  6. Open the application in your browser.

    heroku open
  7. View the application logs, if anything goes wrong.

    heroku logs --tail