Skip to article frontmatterSkip to article content

Generated Project Developer Documentation

In this documentation we present the structure of a web application generated with Mike, using a CRM project as the example.

1.Preparing the project

This subsection lists the files required to prepare the CRM project.

mike.yaml
mikeVersion: "1.0.0"
mikeDir: "mike"
sourceRootDirName: "src/main/java"
resourceRootDirName: "src/main/resources"
previewDirName: "mike-preview"
apiKey: "YOUR-API-KEY"
hostUrl: "https://app.codemike.dev/gen"
requestTimeout: 120
techStack: "SpringVaadin"
undoStackSize: 5

The mike.yaml file stores key–value pairs. The mikeVersion entry holds the version number (1.0.0) whose conventions the generated project files follow. mikeDir specifies that the configuration (config.yaml) and descriptor (main.mike) files will be placed under the mike directory mentioned above. sourceRootDirName tells the generator where the Java sources live within the project. Its value is src/main/java, i.e., it follows the conventions used by Java projects built with Maven. resourceRootDirName sets the location of the resources directory in the generated project. This is where resource descriptor files go—e.g., the email templates (MailTemplates) used for password-reset emails, and the locale files used to translate the UI. previewDirName names a directory for previewing newly generated code so you can verify the result before overwriting existing files. apiKey is the subscriber key proving your entitlement to use Mike services. In the sample it is set to YOUR-API-KEY; replace it with your personal API key from the Dashboard. hostUrl is the URL of the Mike code generator. The service at this URL produces source code and communicates with the CLI. If the generator server is temporarily overloaded and code generation is slow—causing the client to time out—raise the requestTimeout above its default of 120 seconds. techStack selects one of the currently supported technology stacks; here it is SpringVaadin, meaning the project is built on the Java Spring framework and Vaadin UI technologies. (Vaadin Flow lets you render the UI server-side almost entirely in Java, so from a developer’s perspective the backend and frontend are not sharply separated. The other option is SpringRest: Spring on the backend with a REST API that any frontend—e.g., React—can consume.) Finally, undoStackSize defines how many Mike-generated changes can be undone (5).

The mike directory (more generally, the directory named by mike.yaml’s mikeDir) contains two files: config.yaml for configuration settings, and main.mike for descriptors.

mike/config.yaml
# Configurations of your project
# Main descriptor
mainMikeFile: main.mike
java:
  package:
    # Base package of the project
    base: hu.rts.mike.example.dev.crm
projectName: "CRM-dev"
languages:
  - "hu-HU"
  - "en-US"

In config.yaml, mainMikeFile is set to main.mike, pointing to the sibling file that defines the data structures and the UI for the generator. java / package / base specifies the root Java package from which all subpackages branch. It is currently hu.rts.mike.example.dev.crm, but you can set your own following the well-known Java package naming conventions. The projectName key sets the project’s name (“CRM-dev”) and is written into the Maven descriptor pom.xml under project / name. Under languages you can list the human languages you want translations for. Language tags follow RFC 5646, ISO-639-1, and ISO 3166-1: two lowercase letters for the language, followed by a hyphen and a two-letter (Alpha-2) country code (default: en-US). When the project is created, strings from the descriptors and certain defaults are copied into the locale files; you can translate these later. security can be NONE (no authentication/authorization), SESSION (Spring session-based), or JWT (JSON Web Tokens, default). theme chooses the generated web app’s color theme: LUMO (default, Vaadin’s base), MIKE, MYSTIC_SANDS, or CUSTOM. If you use CUSTOM, the customTheme key takes effect, letting you tune site colors. Vaadin applies these colors via Vaadin CSS variables for various controls (we list relevant CSS variables in parentheses). Under customTheme / font, primary (--lumo-primary-text-color) and base (--lumo-body-text-color) control text colors. Under customTheme / scheme, base (--lumo-base-color), primary (--lumo-primary-color), and shade (--lumo-shade) control background colors.

mike/main.mike
data {
    entity Partner @display("{0}", name) {
        id: Int @primary
        name: String!
        taxNumber: String
        zipCode: String  # String so it can start with 0
        city: String
        streetName: String
        streetKind: String
        streetNumber: Int
        invoicingEmail: String
        createdAt: DateTime @value(CREATED_AT)

        contacts: [Contact] @mappedBy(partner)
        companySizeData: [CompanySize] @mappedBy(partner)
        leads: [Lead] @mappedBy(partner)
    }

    entity Contact {
        id: Int @primary
        name: String!
        email: String
        phone: String
        position: String
        modifiedAt: DateTime @value(MODIFIED_AT)
        inactive: DateTime @value(DELETED_AT)

        partner: Partner!
    }

    entity CompanySize {
        id: Int @primary
        year: Int!
        revenue: Int!
        profit: Int!
        headcount: Int!

        partner: Partner!
    }

    entity Lead
        @display("{0} |Salesperson: {1} - Customer: {2} |{3}%", createdAt, vendor.name, partner.name, chanceOfSuccess)
    {
        id: Int @primary
        soreSpot: String!
        createdAt: Date @value(CREATED_AT)
        daysFromCreatedAt: Int @value(COMPUTED)

        chanceOfSuccess: Int @value(COMPUTED)
        estimatedIncome: Int @value(COMPUTED)
        expectedValue: Int @value(COMPUTED)

        vendor: Vendor!
        partner: Partner!
        discounts: [Discount] @unidirectional
        marketingChannel: MarketingChannel!
        events: [Event] @mappedBy(lead)
        products: [Product]
    }

    entity Product @display("{0}", name) {
        id: Int @primary
        name: String!
        regularity: Regularity!
        addQuantityAtBilling: Bool
        price: Int
        tax: Int
        currency: MediumOfExchange!
        note: String

        leads: [Lead] @mappedBy(products)
    }

    enum Regularity: String {
        OCCASIONALLY @storedAs("Occasional")
        MONTHLY @storedAs("Monthly")
        QUARTER @storedAs("Quarterly")
        YEARLY @storedAs("Annual")
    }

    enum MediumOfExchange: String {
        HUF @storedAs("Ft")
        EUR @storedAs("€")
        USD @storedAs("$")
    }

    entity Discount @unique(name, discountType, value) @display("{0} - {1}", name, discountType) {
        id: Int @primary
        name: String!
        discountType: DiscountType!
        value: Int!
    }

    enum DiscountType: String {
        AMOUNT @storedAs("Lump sum")
        PERCENTAGE @storedAs("Percentage")
    }

    entity Event {
        id: Int @primary
        eventDetails: String! @default("Lead recorded")
        nextAppointment: Date!
        chanceOfSuccess: Int!

        communicationChannel: CommunicationChannel! @unidirectional
        lead: Lead
    }

    entity CommunicationChannel @display("{0}", name) {
        id: Int @primary
        name: String!
    }

    entity MarketingChannel @display("{0}", name) {
        id: Int @primary
        name: String!

        leads: [Lead] @mappedBy(marketingChannel)
        budgets: [Budget] @mappedBy(marketingChannel)
    }

    entity Vendor @display("{0}", name) {
        id: Int @primary
        name: String!

        leads: [Lead] @mappedBy(vendor)
    }

    entity Budget @unique(year, month, marketingChannel) {
        id: Int @primary
        year: BudgetYear!
        month: BudgetMonth!
        marketingChannel: MarketingChannel!

        marketingBudgetPlan: Int
        numberOfLeadsPlan: Int
        numberOfSuccessfulLeadsPlan: Int
        revenuePlan: Int

        marketingBudgetActual: Int
        numberOfLeadsActual: Int @value(COMPUTED)
        numberOfSuccessfulLeadsActual: Int @value(COMPUTED)
        revenueActual: Int @value(COMPUTED)
    }

    enum BudgetYear: String {
        YEAR_2023 @storedAs("2023")
        YEAR_2024 @storedAs("2024")
    }

    enum BudgetMonth: String {
        JANUARY @storedAs("January")
        FEBRUARY @storedAs("February")
        MARCH @storedAs("March")
        APRIL @storedAs("April")
        MAY @storedAs("May")
        JUNE @storedAs("June")
        JULY @storedAs("July")
        AUGUST @storedAs("August")
        SEPTEMBER @storedAs("September")
        OCTOBER @storedAs("October")
        NOVEMBER @storedAs("November")
        DECEMBER @storedAs("December")
    }
}

ui {
    navigation CrmNav {
        item MarketingItem @title("Marketing") @icon("fas:chart-column") {
            item CommunicationChannelNavItem: CommunicationChannels @title("Communication channels")
            item MarketingChannelNavItem: MarketingChannels @title("Marketing channels")
        }

        item SalesItem @title("Sales") @icon("fas:dollar") {
            item ProductsNavItem: Products @title("Products")
            item DiscountsNavItem: Discounts @title("Discounts")
            item VendorsNavItem: Vendors @title("Sales people")
            item PartnersNavItem: Partners @title("Partners")
            item ContactsNavItem: Contacts @title("Contacts")
            item LeadsNavItem: Leads @title("Leads")
        }
    }

    page Partners @title("Partners") @home {
        table PartnersList: Partner
            @hiddenFilters
            @searchBar(name, city, streetName, taxNumber)
            @viewPage(ViewPartner)
            @createPage(AddNewPartner)
            @editPage(EditPartner)
        {
            column: name @title("Name")
            column: zipCode @title("Zip code")
            column: city @title("City")
            column: streetName @title("Street")
            column: streetKind @title("Public area type")
            column: streetNumber @title("House number")
            column: taxNumber @title("Tax number")
        }
    }

    page AddNewPartner @title("New partner") {
        form AddNewPartnerForm: Partner {
            input: name @title("Name")
            input: taxNumber @title("Tax number")
            input: zipCode @title("Zip code")
            input: city @title("City")
            input: streetName @title("Street")
            input: streetKind @title("Public area type")
            input: streetNumber @title("House number")
            input: invoicingEmail @title("Billing e-mail")
        }
    }

    page EditPartner requires Partner as partner @title("Edit partner") {
        form EditPartnerForm: Partner using partner {
            input: name @title("Name")
            input: taxNumber @title("Tax number")
            input: zipCode @title("Zip code")
            input: city @title("City")
            input: streetName @title("Street")
            input: streetKind @title("Public area type")
            input: streetNumber @title("House number")
            input: invoicingEmail @title("Billing e-mail")
        }
    }

    page ViewPartner requires Partner as partner @title("Partner profile") @layout(HORIZONTAL) {
        details ViewPartnerHeaderDetails: Partner using partner @editPage(EditPartner) {
            field: name @title("Name")
            field: taxNumber @title("Tax number")
            field: createdAt @title("Created at")
            field: zipCode @title("Zip code")
            field: city @title("City")
            field: streetName @title("Street")
            field: streetKind @title("Public area type")
            field: streetNumber @title("House number")
        }

        container ViewPartnerBodyContainer @layout(TABBED) {
            table ViewPartnerBodyContactsTable: Contact using partner.contacts
                @omitted(VIEW)
                @editPage(ContactEdit)
                @createPage(ContactAddNew)
                @hiddenFilters
                @heading("Contacts")
            {
                column: name @title("Name")
                column: position @title("Position")
                column: phone @title("Phone")
                column: email @title("E-mail")
                column: modifiedAt @title("Last modified")
            }

            table ViewPartnerBodyCompanySizeTable: CompanySize using partner.companySizeData
                @omitted(VIEW)
                @omitted(DELETE)
                @editPage(CompanySizeEdit)
                @createPage(CompanySizeAddNew)
                @hiddenFilters
                @heading("Company size")
            {
                column: year @title("Year")
                column: revenue @title("Revenue")
                column: profit @title("Profit")
                column: headcount @title("Headcount")
            }
        }
    }

    page ContactAddNew @title("New contact") {
        form ContactAddNewForm: Contact {
            input: name @title("Name")
            input: position @title("Position")
            input: phone @title("Phone")
            input: email @title("E-mail")
            input: partner @title("Partner")
        }
    }

    page ContactEdit requires Contact as contact @title("Edit contact") {
        form ContactEditForm: Contact using contact {
            input: name @title("Name")
            input: position @title("Position")
            input: phone @title("Phone")
            input: email @title("E-mail")
            input: partner @title("Partner")
        }
    }

    page CompanySizeAddNew @title("New company size") {
        form CompanySizeAddNewForm: CompanySize {
            input: year @title("Year")
            input: revenue @title("Revenue")
            input: profit @title("Profit")
            input: headcount @title("Headcount")
            input: partner @title("Partner")
        }
    }

    page CompanySizeEdit requires CompanySize as companySize @title("Edit company size") {
        form CompanySizeEditForm: CompanySize using companySize {
            input: year @title("Year")
            input: revenue @title("Revenue")
            input: profit @title("Profit")
            input: headcount @title("Headcount")
            input: partner @title("Partner")
        }
    }

    page Products @title("Products") {
        table ProductsTable: Product @omitted(VIEW) @createPage(CreateProduct) @editPage(EditProduct) {
            column: name @title("Name")
            column: price @title("Price")
            column: currency @title("Currency")
        }
    }

    page CreateProduct {
        form CreateProductForm: Product @heading("New product") {
            input: name @title("Name")
            input: regularity @title("Regularity")
            input: addQuantityAtBilling @title("Value to be entered at billing")
            input: price @title("Price")
            input: tax @title("Tax")
            input: currency @title("Currency")
            input: note @title("Note")
        }
    }

    page EditProduct requires Product as product {
        form EditProductForm: Product using product @heading("Edit product") {
            input: name @title("Name")
            input: regularity @title("Regularity")
            input: addQuantityAtBilling @title("Value to be entered at billing")
            input: price @title("Price")
            input: tax @title("Tax")
            input: currency @title("Currency")
            input: note @title("Note")
        }
    }

    page Discounts @title("Discounts") {
        table DiscountsTable: Discount
            @hiddenFilters
            @omitted(VIEW)
            @createPage(CreateDiscount)
            @editPage(EditDiscount)
        {
            column: name @title("Discount name")
            column: discountType @title("Type")
            column: value @title("Value")
        }
    }

    page CreateDiscount @title("New discount") {
        form CreateDiscountForm: Discount {
            input: name @title("Name")
            input: discountType @title("Type")
            input: value @title("Value")
        }
    }

    page EditDiscount requires Discount as discount @title("Edit discount") {
        form EditDiscountForm: Discount using discount {
            input: name @title("Name")
            input: discountType @title("Type")
            input: value @title("Value")
        }
    }

    page Leads @title("Leads") {
        table LeadsTable: Lead
            @omitted(DELETE)
            @omitted(EDIT)
            @createPage(AddNewLead)
            @viewPage(ViewLead)
        {
            column: id @title("#")
            column: chanceOfSuccess @title("Chance")
            column: estimatedIncome @title("Estimated revenue") @hidden
            column: expectedValue @title("Expected value") @hidden
            column: partner.name @title("Partner")
            column: vendor.name @title("Salesperson")
            column: createdAt @title("Created")
            column: daysFromCreatedAt @title("Elapsed") @hidden
            column: marketingChannel.name @title("Marketing channel")
        }
    }

    page AddNewLead @title("Add new lead") {
        form AddNewLeadForm: Lead {
            input: vendor @title("Salesperson")
            input: partner @title("Partner")
            input: discounts @title("Discounts")
            input: marketingChannel @title("Marketing channel")
            input: products @title("Products")
            input: soreSpot @title("Sore spot")
        }
    }

    page ViewLead requires Lead as lead @title("Lead profile") {
        container ViewLeadLeft @layout(HORIZONTAL) {
            details ViewLeadDetailsHeader: Lead using lead @heading("Lead") @omitted(EDIT) {
                field: partner.name @title("Partner")
                field: partner.taxNumber @title("Tax number")
                field: createdAt @title("Created at")
                field: soreSpot @title("Sore spot")
                field: vendor @title("Salesperson")
            }

            container ViewLeadTabbedContainer @layout(TABBED) {
                table ViewLeadBodyProductsTable: Product using lead.products
                    @omitted(VIEW)
                    @createPage(CreateProduct)
                    @editPage(EditProduct)
                    @hiddenFilters
                    @heading("Products")
                {
                    column: name @title("Name")
                    column: price @title("Price")
                    column: regularity @title("Regularity")
                    column: tax @title("Tax")
                    column: currency @title("Currency")
                }

                table ViewLeadBodyContactsTableRight: Contact using lead.partner.contacts
                    @omitted(VIEW)
                    @editPage(ContactEdit)
                    @createPage(ContactAddNew)
                    @hiddenFilters
                    @heading("Contacts")
                {
                    column: name @title("Name")
                    column: position @title("Position")
                    column: phone @title("Phone")
                    column: email @title("E-mail")
                    column: modifiedAt @title("Last modified") @hidden
                }

                table ViewLeadBodyCompanySizeTableRight: CompanySize using lead.partner.companySizeData
                    @omitted(VIEW)
                    @omitted(DELETE)
                    @editPage(CompanySizeEdit)
                    @createPage(CompanySizeAddNew)
                    @hiddenFilters
                    @heading("Company size")
                {
                    column: year @title("Year")
                    column: revenue @title("Revenue")
                    column: profit @title("Profit")
                    column: headcount @title("Headcount")
                }
            }
        }

        table ViewLeadEventsTable: Event using lead.events
            @omitted(EDIT)
            @omitted(DELETE)
            @omitted(VIEW)
            @heading("Events")
            @createPage(AddNewEventPage)
        {
            column: communicationChannel.name @title("Communication channel")
            column: lead.partner.name @title("Partner")
            column: chanceOfSuccess @title("Chance")
            column: nextAppointment @title("Appointment")
            column: eventDetails @title("Details")
        }
    }

    page AddNewEventPage @title("Add new event") {
        form AddNewEventForm: Event {
            input: communicationChannel @title("Communication channel")
            input: lead @title("Lead")
            input: chanceOfSuccess @title("Chance")
            input: nextAppointment @title("Appointment")
            input: eventDetails @title("Details")
        }
    }

    page CommunicationChannels @title("Communication channels") {
        table CommunicationChannelsTable: CommunicationChannel
            @heading("Communication channels")
            @omitted(VIEW)
            @editPage(EditCommunicationChannel)
            @createPage(AddNewCommunicationChannel)
        {
            column: name @title("Channel name")
        }
    }

    page EditCommunicationChannel requires CommunicationChannel as channel @title("Modify communication channel") {
        form MarketingChannelEditForm: CommunicationChannel using channel @heading("Modify communication channel") {
            input: name @title("Channel name")
        }
    }

    page AddNewCommunicationChannel @title("New communication channel") {
        form MarketingChannelEditForm: CommunicationChannel @heading("New communication channel") {
            input: name @title("Channel name")
        }
    }

    page MarketingChannels @title("Marketing channels") {
        table MarketingChannelsTable: MarketingChannel
            @heading("Marketing channels")
            @viewPage(ViewMarketingChannelBudget)
            @editPage(EditMarketingChannel)
            @createPage(AddNewMarketingChannel)
        {
            column: name @title("Channel name")
        }

    }

    page ViewMarketingChannelBudget requires MarketingChannel as channel @title("Marketing channel budget") {
        container MChannelBudgetContainer @layout(TABBED) {
            table MarketingChannelBudget: Budget using channel.budgets
                @omitted(VIEW)
                @createPage(AddNewMChannelBudget)
                @editPage(EditMChannelBudget)
                @title("Marketing channel budget")
            {
                column: year @title("Year")
                column: month @title("Month")
                column: marketingBudgetPlan @title("Planned marketing costs")
                column: numberOfLeadsPlan @title("No. of planned leads")
                column: numberOfSuccessfulLeadsPlan @title("No. of planned successful leads")
                column: revenuePlan @title("Planned revenue")
                column: marketingBudgetActual @title("Actual marketing costs")
                column: numberOfLeadsActual @title("No. of actual leads")
                column: numberOfSuccessfulLeadsActual @title("No. of actual successful leads")
                column: revenueActual @title("Actual revenue")
            }
        }
    }

    page AddNewMChannelBudget @title("Add new marketing channel budget") {
        form AddNewMChannelBudgetForm: Budget {
            input: year @title("Year")
            input: month @title("Month")
            input: marketingChannel @title("Marketing channel")
            input: marketingBudgetPlan @title("Planned marketing costs")
            input: numberOfLeadsPlan @title("No. of planned leads")
            input: numberOfSuccessfulLeadsPlan @title("No. of planned successful leads")
            input: revenuePlan @title("Planned revenue")
            input: marketingBudgetActual @title("Actual marketing costs")
        }
    }

    page EditMChannelBudget requires Budget as budget @title("Edit marketing channel budget") {
        form EditMChannelBudgetForm: Budget using budget {
            input: year @title("Year")
            input: month @title("Month")
            input: marketingChannel @title("Marketing channel")
            input: marketingBudgetPlan @title("Planned marketing costs")
            input: numberOfLeadsPlan @title("No. of planned leads")
            input: numberOfSuccessfulLeadsPlan @title("No. of planned successful leads")
            input: revenuePlan @title("Planned revenue")
            input: marketingBudgetActual @title("Actual marketing costs")
        }
    }

    page EditMarketingChannel requires MarketingChannel as channel @title("Modify marketing channel") {
        form MarketingChannelEditForm: MarketingChannel using channel @heading("Modify marketing channel") {
            input: name @title("Channel name")
        }
    }

    page AddNewMarketingChannel @title("New marketing channel") {
        form MarketingChannelEditForm: MarketingChannel @heading("New marketing channel") {
            input: name @title("Channel name")
        }
    }

    page Contacts @title("Contacts") {
        table ContactsTable: Contact @createPage(AddNewContact) @editPage(EditContact) @viewPage(ViewContact) {
            column: name @title("Name")
            column: email @title("E-mail")
            column: phone @title("Phone")
            column: position @title("Position")
            column: modifiedAt @title("Modified at") @hidden
            column: partner.name @title("Partner")
            column: inactive @title("Inactivated") @hidden
        }
    }

    page AddNewContact @title("Add new contact") {
        form AddNewContactForm: Contact {
            input: name @title("Name")
            input: email @title("E-mail")
            input: phone @title("Phone")
            input: position @title("Position")
            input: partner @title("Partner")

        }
    }

    page EditContact requires Contact as contact @title("Edit contact") {
        form EditContactForm: Contact using contact {
            input: name @title("Name")
            input: email @title("E-mail")
            input: phone @title("Phone")
            input: position @title("Position")
            input: partner @title("Partner")
        }
    }

    page ViewContact requires Contact as contact @title("Contact profile") {
        details ContactDetails: Contact using contact @editPage(EditContact) {
            field: name @title("Name")
            field: email @title("E-mail")
            field: phone @title("Phone")
            field: position @title("Position")
            field: partner.name @title("Partner")
        }
    }

    page Vendors @title("Sales people") {
        table VendorsTable: Vendor @viewPage(ViewVendor) @createPage(AddNewVendor) @editPage(EditVendor) {
            column: name @title("Name")
        }
    }

    page AddNewVendor @title("Add salesperson") {
        form AddNewVendorForm: Vendor {
            input: name @title("Name")
        }
    }

    page EditVendor requires Vendor as vendor @title("Edit salesperson") {
        form EditVendorForm: Vendor using vendor {
            input: name @title("Name")
        }
    }

    page ViewVendor requires Vendor as vendor @title("Salesperson profile") {
        details VendorDetails: Vendor using vendor @editPage(EditVendor) {
            field: name @title("Name")
        }

        container ViewVendorBody @layout(TABBED) {
            table VendorLeadsTable: Lead using vendor.leads
                @readonly
                @viewPage(ViewLead)
                @title("Salesperson's leads")
            {
                column: id @title("#")
                column: chanceOfSuccess @title("Chance")
                column: estimatedIncome @title("Estimated revenue")
                column: expectedValue @title("Expected value")
                column: partner.name @title("Partner")
                column: createdAt @title("Created")
                column: daysFromCreatedAt @title("Elapsed")
                column: marketingChannel.name @title("Marketing channel")
            }
        }
    }
}

The second file, main.mike, contains the project descriptors and is unique per application. This documentation does not detail its structure; see the Guide and Reference chapters. The first data section defines the data model; the second ui section governs UI construction.

These files and the information within are sufficient to generate source code. The simplest way is to run mike gen (assuming the mike CLI is already installed). This also creates a mike.id file used by the generator and CLI to distinguish builds, and adds several directories to the project.

2.Dependencies of the generated project

To build and run the project you will need:

As an alternative, you can use Docker. The project’s README.md explains this.

3.Structure of the generated project

The project follows the typical layering and layout of Spring Boot + Vaadin applications. All directories mentioned below live under the source tree.

Data is stored in a PostgreSQL database server, though switching to another RDBMS is straightforward by editing pom.xml and application.properties. The generated app contains very little database-vendor-specific code (an exception is ExceptionHandlerConfig.java, which handles PSQLExceptions). The app does not access the database directly; instead, Hibernate provides object-relational mapping (ORM).

Accordingly, entities are created under the entity package, carrying the fields defined in the Mike data descriptors. These are annotated Java classes with private fields, getters/setters, and equals()/hashCode(). Annotations define table names, relationship cardinalities (e.g., one partner has many leads), indexes, and so on.

The data access layer forms the lowest application layer and lives under repository. There you typically find triplets per entity: EntityRepository, EntityRepositoryCustom, and EntityRepositoryImpl interfaces/classes, where Entity is a Hibernate-managed entity. For example, PartnerRepository.java derives from JpaRepository and PartnerRepositoryCustom without adding much code. JpaRepository supplies the common CRUD-like operations; the custom interface adds entity-specific queries to populate grid views (e.g., findAllLeads() returns pages from the filtered result set).

Building on that layer, the service layer adds application logic: checking privileges (@RolesAllowed), mapping entities to DTOs (e.g., RoleService.findAll()), and enforcing preconditions (e.g., RoleService.delete() refuses to delete a role while any user still holds it).

If the web app exposes a REST interface, rest/controller contains controllers that call service-layer functions when a user hits an endpoint (@RequestMapping) with a given HTTP method (@GetMapping, @DeleteMapping, etc.). This layer also performs authorization checks (@PreAuthorize) and maps between entities and DTOs.

Vaadin UI classes under view implement the user interface. Data is rendered by a grid view (*List, *Table), a form (*Form), or an entity detail view (*Details). In the UI descriptor these correspond to the table, form, and details keywords. They appear on pages represented by page classes (often named after the entity, optionally with a View prefix) and are declared in the UI descriptor via page. A page may contain multiple views arranged per the descriptor (@layout: vertical/horizontal split, split view with a draggable divider, tabs). If that’s not enough, you can put containers (UI descriptor keyword) on a page; functionally they behave like page classes with respect to layout and embedded views. Each container and page has its own package, with its views placed alongside.

4.Directory structure produced by generation

One of the created directories is hidden (.mike); it contains generator runtime data and should not be edited manually.

frontend/generated is created by Vaadin (not by Mike). frontend/themes contains a nested directory named after the project; here it is crm-dev. In it, styles.css adapts the default Vaadin styles for the app. Because we factor CSS rules into thematic files, it imports them—for example branding.css styles the logo in the menu bar, and filter.css affects built-in grid filters. You typically won’t need to edit these.

Because sourceRootDirName in mike.yaml is src/main/java, sources go there. More precisely, the Java package path set by java / package / base in config.yaml is created under it. In this case: src/main/java/hu/rts/mike/example/dev/crm.

Similarly, the resources directory src/main/resources is created. We detail both trees below.

node_modules is created by Vaadin during development. It is not needed at runtime and, with the right Maven parameters, is removed automatically.

target contains compiled Java bytecode and various runtime resources.

The project root also gains files such as Dockerfile, which describes how to package the app as a Docker image for containerized deployment. Key steps include building a runnable jar with Maven (mvn -B -DskipTests -Pproduction clean package), after which the service listens on port 8888.

With docker-compose.yaml you can start/stop the containers required by the app. One container runs the app (as described by the Dockerfile); the other runs the database. Notable details: the database is PostgreSQL on the default port 5432; a database named crm_dev is created for the app; and you should change the default password (“Password”) before production use.

The README.md gives basic setup (e.g., DB and mail server) and runtime (Docker) instructions. It also lists environment variables you can use to override defaults such as the DB password—recommended for security.

.gitignore helps with Git usage by excluding common IDE artifacts, etc. The generator itself uses Git so that generated sources are versioned alongside handwritten code.

pom.xml is the Maven descriptor for the app. Mike writes the provided project metadata (e.g., project name), declares dependencies and version requirements (e.g., Java 17 for compilation and Vaadin 24.3.9 for generated components).

4.1.Source files

component contains reusable components referenced across generated code. Mike effectively provides a small Vaadin-based UI framework whose building blocks the generator uses. When extending the generated code, prefer these components to reduce redundancy and ensure a consistent UX.

Inside component, customfield contains enhanced Vaadin UI elements—for example LocalizedDatePicker.java, which displays dates per the user’s locale and renders messages in the selected language.

component/factory holds classes implementing the Factory pattern to streamline object creation—for instance, ButtonFactory.java provides ready-made buttons via a single method call (e.g., standardized “Delete” buttons).

component/grid supports grid views with utilities such as ColumnToggleButton.java for showing/hiding columns, and a filter subpackage with filter classes per data type (e.g., NumberFilter.java for numeric values).

component/layout currently contains LazyComponent.java, which defers tab content loading until needed (lazy load).

component/navigation/Branding.java renders the brand logo. The image is at /src/main/resources/META-INF/resources/images/logos/logo.png.

component/Badge.java renders badge-style markers for use in grids and detail pages (not used by the CRM example).

config includes three DTO-related files. In Vaadin-stack projects, DTOs are used only during user authentication; REST stacks never expose Hibernate entities directly to the frontend but convert them to DTOs first. ModelMapperConfig.java configures the ModelMapper library (e.g., time handling). DtoMappingBuilder.java helps build ModelMapper configs, especially when custom mapping rules are needed. DtoConverter.java is an interface ensuring type access during entity–DTO conversion. ExperimentalFeatures.java simplifies checking whether experimental features are enabled in application.properties.

PresentationStringConverter.java centralizes safe (e.g., null-tolerant) and consistent (e.g., locale-aware, fixed decimals) string conversion.

entity is largely project-dependent: for every entity in main.mike, a class is generated. These Hibernate entities map classes to tables via annotations (column names, relationship cardinalities like one-to-many), expose getters/setters, and support object equality. If the app requires authentication/authorization—as the CRM does—additional entities appear here. For example, AuthInfo.java stores key user data (but not all, for security; e.g., passwords, privileges). User.java, in a 1:1 relation with AuthInfo, stores other user info (username, email). (If the descriptor uses @userAccount on an entity, then the generated file name uses that entity’s name instead of User.java, and includes the extra fields defined there.) Role.java supports role-based privilege management. PasswordChange.java maintains temporary secret codes for password resets. HasTranslationName.java helps manage human-readable names for some enums.

error contains error-handling classes. ErrorCode.java defines an enum of backend error codes (e.g., WEAK_NEW_PASSWORD when a new password fails policy, INVALID_FILTER_FORMAT when grid filter parameters are malformed). ErrorCodeException.java is an exception type wrapping those codes. ErrorDto.java packages the error message and an associated unique ID as a DTO. These are used primarily by REST endpoints.

i18n provides TranslationProvider.java for UI localization—serving UI strings in the selected language from prepared resource bundles. For Vaadin to discover it, a servlet with the right parameters is also created: TranslationServlet.java.

model contains DTO classes (or records) mirroring each entity. These are plain Java classes with getters/setters per field. model/converter implements DTOConverter classes specifying special rules for entity↔DTO conversion—for example, mapping AuthInfo to AuthInfoDto without including passwords.

repository contains the interfaces of the data access layer—typically one per entity. Hibernate generates the implementing classes automatically. For example, PartnerRepository for the Partner entity provides basic data operations (lookup by ID, search). Often these are extended—here via *RepositoryCustom.java files—e.g., PartnerRepositoryCustom requires methods for grid assembly (findAllForGrid) and for 1:N related data (all leads of a partner via findAllLeads). Implementations live in *RepositoryImpl.java files. repository/utility contains repository helpers. The Filter record stores a concrete filter (e.g., “field greater than threshold”), FilterOperator enumerates operators (less than, greater than, etc.), and QueryUtils.java translates filter specs into Hibernate-consumable queries (e.g., using a CriteriaBuilder or entity graphs).

rest matters when you use the REST interface. The config subpackage has three files. ExceptionHandlerConfig.java maps exceptions (PSQLException, ErrorCodeException) to DTOs so clients receive error details. JacksonConfig.java configures entity→DTO mapping (null handling, date formats) and JSON serialization (JsonSerializer.java is the third file). The controller subpackage holds controllers (one per entity, plus three for user/role management). It defines which HTTP methods each endpoint supports, required privileges, available parameters (e.g., grid page size), and minimal logic (e.g., error handling). Typically, it delegates to service-layer methods (see service). The security subpackage contains services for authentication/authorization. AuthService.java loads user data by username, issues JWTs, queries privileges, etc. Password-reset support lives in ForgottenPasswordService.java. Token lifecycle and privilege handling live in JwtService.java. JwtTokenFilter.java handles token filtering/validation. RestSecurityConfig.java configures Spring Security (e.g., handlers for unauthorized access, endpoint permissions). utility has TableQueryUtil.java to parse and validate grid sorting/filtering parameters.

security (outside rest) secures Vaadin-UI projects. AuditingConfig.vm supports @CreatedBy and @LastModifiedBy by locating the user entity for Spring. AuthDataInitializer.java initializes required users/roles at startup, removes privileges for entities deleted from the descriptor, ensures the admin role exists with the full privilege set, and, if no active admin user is present, creates the default admin account. PasswordConfig.java configures BCrypt with strength 11. Privilege.java enumerates all privileges with numeric IDs (the latter used only with the REST interface). PrivilegeValueConverter.java converts between Privilege objects and their database string representation and guards against drift when descriptors change but role privilege lists don’t. SecurityConfig.java sets several security options (public paths, firewall tuning, token expiry, hash algorithm, etc.). TokenCache.java enables blacklisting users—i.e., blocking them immediately by invalidating tokens before expiry. UserBlockedException signals attempts by blocked users.

service stores service-layer classes—mostly per entity. For Partner, PartnerService.java provides findAll* methods to populate grids (partners, contacts, leads) and CRUD operations (create, update, delete, find by ID). Where authentication/authorization is used, more files appear. AuthInfoDetailsService extends Spring’s user management by handling privileges attached to roles. Key capabilities include finding users by name, determining the logged-in user, and checking privileges for actions. AdminRoleException protects the built-in admin role: it is raised when an operation would delete the role, strip privileges from it, or block/demote the last active admin user. LoginService handles login and password-change workflows, e.g., sending an email with a temporary link, checking password strength, and performing the change. RoleExistsException is thrown when attempting to create a duplicate role; RoleInUseException when deleting a role still assigned to any user (this is intentionally manual for safety). RoleService creates, updates (renaming an existing role is not allowed), lists, and deletes roles. AuthInfoService handles similar operations for users, including blocking/unblocking users, and ensures the last admin-privileged user stays active.

session handles user sessions. Note that Vaadin uses sessions even when identification is token-based. LocaleManager manages locale (language/region) settings. It prefers storing and reading settings from a cookie; if absent, it infers a sensible default from the browser’s settings. It lets users change locale and can report the current setting. Similarly, TimeZoneManager.java manages time zone settings in a cookie, falling back to the browser if needed, and provides conversions between local time and Java’s Instant.

view contains UI elements, all Vaadin-based and structured. For example, view/partners includes all classes for partner pages. Partners.java represents the page loaded by the browser, handles routing, checks privileges, and renders the page. Here it shows a single grid view, PartnerList. *List classes are based on Vaadin Grid but add capabilities: show/hide buttons/links depending on privileges, provide headers for filtering and sorting, localize labels, etc. view/viewpartner renders the details of a specific partner. ViewPartner.java is the page that embeds two components side by side: ViewPartnerHeaderDetails.java, which shows identity and core attributes (name, tax number, created date) and provides Edit/Delete/Back actions; and a container whose content lives under view/viewpartnerbodycontainer. The ViewPartnerBodyContainer class is similar to a page in that it can embed multiple views; here it renders a tabbed control. The first tab hosts ViewPartnerBodyContactsTable (the partner’s contacts in a grid). The second tab hosts ViewPartnerBodyCompanySizeTable (company size data). Subpackages prefixed with addnew host pages and forms for creating new entities—e.g., addnewpartner contains AddNewPartner.java (page) and AddNewPartnerForm.java (form). Beyond obvious duties, they validate required fields and handle DB errors. Editing existing data lives under edit subpackages—e.g., editpartner has EditPartner.java and EditPartnerForm.java. This form is pre-populated and the page includes a Delete button. You will also find user-management views. Under user, UserForm.java lets you create and modify users; while form-based, its structure differs from entity-generated forms, and UserList.java is not a byte-for-byte copy of generated list views. Role management lives under role as RoleForm.java and RoleList.java. MainLayout.java supplies the shell for all authenticated views. It wires the drawer navigation, branding header, and the top-right action menu. When security is enabled, the menu displays the signed-in user’s username alongside the user icon, and the submenu offers language switching (if configured), theme toggling, and sign-out. Without enabled security, it still provides the theme toggle and optional language selector.

The login package under view contains login and password-change/forgot-password pages. LoginView.java is based on Vaadin’s login component and implements the standard username/password flow, with a “forgot password” link. Following that link shows ForgottenPasswordView.java, where the user provides a username or email. If found, the system emails a short-lived link. Visiting that link allows the user to set a new password via PasswordChangeView.java and then redirects back to login. All three classes use HeaderLayout, rendering a user icon in the top-right; from there, even without being logged in, users can change language and theme.

The source tree also contains Application.java (the application entry point) and the app’s favicon (the small site icon in the browser’s title bar).

4.2.Resource files

Under resources, MailTemplates contains one html file per supported language (filename suffixes encode language and country). These are the email body templates used for password resets. The text includes placeholders (natural numbers in curly braces) that LoginService.sendMessage() replaces—here with the username, a link to the password-change page, and a signature (the value of key ForgottenPasswordSignature from the locale files).

META-INF/resources/images contains the app’s favicon at icons/favicon.ico and the brand logo at logos/logo.png.

application.properties stores application settings as key–value pairs: token lifetimes (seconds), minimum password length, DB/mail server addresses, etc.

The messages*.properties files contain translations of UI strings per language.