Vaadin Fusion Quick Start

Vaadin Fusion enables you to integrate a Spring Boot Java backend with a reactive TypeScript frontend. It includes all the UI components and tools you need so you can focus on building your app instead of configuring stuff.

In this guide, you learn how to build a small but fully functional shopping-list application using Vaadin Fusion.

What You Need

Step 1: Download a Vaadin Fusion Project

Download

Unpack the downloaded zip into a folder on your computer, and import the project in the IDE of your choice.

The pre-configured starter project includes an empty Grocery view written in Typescript that you will modify as described further below.

Step 2: Define the Data Model

For the data model, define a plain old Java object (POJO) by creating a new GroceryItem.java file in src/main/java/com/example/application/ with the following content:

package com.example.application;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

public class GroceryItem {

    @NotBlank 1
    private String name;

    @NotNull
    @Min(value = 1) 2
    private Integer quantity;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}
  1. Add a @NotBlank annotation to ensure that the item’s name is non-null and that it contains at least one non-whitespace character.

  2. Add @NotNull and @Min annotations to ensure that the item’s quantity is a non-null integer that is greater than 0.

Step 3: Create a Typed Server Endpoint

Vaadin Fusion enables your frontend to have secure, type-safe access to the server through REST-like endpoints.

Create a new GroceryEndpoint.java file in src/main/java/com/example/application/ with the following content:

package com.example.application;

import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.fusion.Endpoint;
import com.vaadin.fusion.Nonnull;

import java.util.ArrayList;
import java.util.List;

@Endpoint 1
@AnonymousAllowed 2
public class GroceryEndpoint {

    static final List<GroceryItem> groceryList = new ArrayList<>();

    public @Nonnull List<@Nonnull GroceryItem> getGroceries() { 3
        return groceryList;
    }

    public GroceryItem save(GroceryItem item) {
        groceryList.add(item);
        return item;
    }
}
  1. Annotating the 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. Using the @Nonnull annotation ensures that the TypeScript Generator does not interpret these values as possibly undefined.

Step 4: Create a Reactive UI in TypeScript

Vaadin Fusion uses the Lit library to create client-side views. Lit is a lightweight and highly performant library for building reactive UI.

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

import '@vaadin/vaadin-button';
import '@vaadin/vaadin-text-field';
import '@vaadin/vaadin-text-field/vaadin-number-field';
import '@vaadin/vaadin-grid/vaadin-grid';
import { html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/vaadin-lumo-styles/sizing';
import '@vaadin/vaadin-lumo-styles/spacing';
import { View } from '../../views/view';
import { Binder, field } from '@vaadin/form';
import { getGroceries, save } from '../../generated/GroceryEndpoint';
import GroceryItem from 'Frontend/generated/com/example/application/GroceryItem';
import GroceryItemModel from 'Frontend/generated/com/example/application/GroceryItemModel';

@customElement('grocery-view') 1
export class GroceryView extends View { 2
  @state()
  private groceries: GroceryItem[] = []; 3
  private binder = new Binder(this, GroceryItemModel); 4

  render() {
    return html`
      <div style="padding: 25px">
        <div>
          <vaadin-text-field ${field(this.binder.model.name)} label="Item">
          </vaadin-text-field> 5
          <vaadin-number-field
            ${field(this.binder.model.quantity)}
            has-controls
            label="Quantity"
          ></vaadin-number-field> 6
          <vaadin-button theme="primary" @click=${this.addItem} ?disabled=${this.binder.invalid}>
          Add</vaadin-button> 7
        </div>

        <h3>Grocery List</h3>
        <vaadin-grid .items="${this.groceries}" theme="row-stripes" style="max-width: 400px"> 8
          <vaadin-grid-column path="name"></vaadin-grid-column>
          <vaadin-grid-column path="quantity"></vaadin-grid-column>
        </vaadin-grid>
      </div>
    `;
  }

  async addItem() {
    const groceryItem = await this.binder.submitTo(save); 9
    if (groceryItem) { 10
      this.groceries = [...this.groceries, groceryItem];
      this.binder.clear();
    }
  }

  async firstUpdated() { 11
    const groceries = await getGroceries();
    this.groceries = groceries;
  }
}
  1. Register the new component with the browser. This makes it available as <grocery-view>. The routing in index.ts is already set up to show it when you navigate to the application.

  2. Define the component class that extends from Vaadin View class, which itself extends from LitElement.

  3. The list of groceries is private and decorated with @state() so Lit observes it for changes.

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

  5. The Text Field component is bound to the name property of a GroceryItem using element expression: ${field(this.binder.model.name)}.

  6. Analogous to the Text Field, the Number Field is bound to the quantity property of a GroceryItem using ${field(this.binder.model.quantity)}.

  7. The click event of the Add button is bound to the addItem() method. The button is disabled if the form is invalid.

  8. Use Vaadin Grid to display the current content of the grocery list.

  9. Use binder to submit the form to GroceryEndpoint. The binder validates the input before posting it and the server re-validates it.

  10. If the GroceryItem was saved successfully, update the groceries array and clear the form.

  11. Retrieve the list of groceries from the server upon the view’s first rendering.

Step 5: Run the Application

To run the project in your IDE, launch Application.java, which is located under src/main/java/com/example/application/.

Alternatively, you can run the project from the command line by typing mvnw (on Windows), or ./mvnw (on macOS or Linux).

Then, in your browser, open localhost:8080/grocery.

Go further

Congratulations on finishing the tutorial! Now you have a taste of how Vaadin Fusion empowers you to quickly build web apps that integrate a Java backend with a reactive TypeScript frontend.

Continue exploring Vaadin Fusion in the following resources:

If you get stuck or need help, please reach out to the Vaadin Community in Discord.

The full source code of this project is available on GitHub.