Skip to content

Build a CRUD API with Spring Boot facility

This guide shows how to build a CRUD REST API with the JoinedWorkz SpringBoot platform using the example-spring-boot project as a concrete generation and compilation example.

The focus is on:

  • modelling an entity and its API
  • using the SpringBoot CRUD method types
  • understanding the generated controllers, data access services and mappers

Release baseline and verification

This page uses example-spring-boot from release/1.3.80, commit df7cabf7f21b. Generation, main-source compilation and packaging were verified with Java 21.0.8 and Maven 3.9.16 on 2026-07-26. The generated CustomersResourceIT is not registered as a Maven test source and was therefore neither compiled nor run. Runtime startup and CRUD requests have not passed the planned smoke-test gate.

For general background on the Spring Boot facility, see:

  • spring-boot-facility.md
  • Facilities and platforms
  • Model a REST API step by step

1. Prerequisites

You should have:

  • Java 21
  • Maven 3.9 or newer
  • JoinedWorkz 1.3.80 with:
    • Base, Java and SpringBoot platforms/facilities
    • the SpringBootApi.cmn model with createEntity, readEntity, …
  • A working JoinedWorkz generator setup (Maven plugin)
  • Optional but recommended: JoinedWorkz Studio for editing .cmn models

Example project:

The guide assumes you use that module as-is, but the pattern is the same for your own projects.


2. Get the example project

Clone the examples repository and build the Spring Boot example:

git clone --branch release/1.3.80 --single-branch \
  https://gitlab.com/joinedworkz/joinedworkz-examples.git
cd joinedworkz-examples/example-spring-boot

# run model generation + Java build
mvn clean verify

After the build you should see:

  • src/generated/resources/openapi/org.joinedworkz.examples.customer.api.yaml;
  • src/generated/resources/openapi/org.joinedworkz.examples.backend_customerbackend.yaml;
  • generated Java sources for the entity, DTOs, controller, API interface, data access service, repository and mapper;
  • compiled main classes and the repackaged Spring Boot JAR.

This command does not currently compile or run the generated integration test.


3. Model the domain: Customer entity

In the example the domain model lives in the core layer. The canonical release model contains:

cmn
core package org.joinedworkz.examples.customer

import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot

enum SetupType {
    NONE value="none"
    NP   value="NP"
    JP   value="JP"
}

type Address {
    street*: String(200)
    zipCode: String(10)
    city*:   String(100)
    country: String(2)
}

type<entity> Customer {
    id**:       Id
    firstName*: Name
    lastName*:  Name
    email:      String(255)
    setupType:  SetupType

    mainAddress: Address
}

Key points:

  • type<entity> Customer defines a complex type with the stereotype entity.
  • id** marks the field as the entity/resource key. An explicit <key> stereotype is separate syntax.
  • Other fields model the customer data, including the embedded Address.
  • The layer (core) indicates that this type is part of the internal domain model.

For the canonical release example, the Java and persistence generators create:

  • the JPA entity Customer;
  • CustomerDataAccessService with CRUD operations.

4. Import Spring Boot API helpers

For REST APIs the example uses:

  • the SpringBoot platform profile
  • the CRUD method types from SpringBootApi.cmn

In the API model file you import both:

cmn
api package org.joinedworkz.examples.customer.api vendorSpecificMimeType=true

import org.joinedworkz.examples.customer

import org.joinedworkz.facilities.profiles.springboot
import org.joinedworkz.facilities.springboot.api

platform SpringBoot

Here:

  • api is the layer for external API types and resources.
  • org.joinedworkz.examples.customer brings the Customer entity into scope.
  • org.joinedworkz.facilities.springboot.api provides the method types: createEntity, readEntity, updateEntity, queryEntities, deleteEntity.

5. Model the CRUD resource with SpringBoot helpers

The main REST API for customers is defined as a resource that uses the CRUD method types:

cmn
resource /customers as Customer[] by id {
    queryEntities()
    createEntity()
    readEntity()
    updateEntity()
    deleteEntity()
}

abstract resource /address as Address { }

Explanation:

  • resource /customers as Customer[] by id
    • /customers is a collection resource
    • Customer[] indicates that the default representation is a list of Customer
    • by id defines the identifier for individual items in the collection
  • The methods:
    • queryEntities() → lists customers with paging/sorting/filtering
    • createEntity() → creates a new customer
    • readEntity() → reads one customer by id
    • updateEntity() → updates an existing customer
    • deleteEntity() → deletes a customer by id
  • abstract resource /address as Address { }
    • defines a reusable sub-resource pattern for customer addresses (used in more advanced scenarios)

Behind the scenes these method types are defined like this (abridged excerpt from release 1.3.80 SpringBootApi.cmn; the referenced semantic constants are defined later in that file):

cmn
package org.joinedworkz.facilities.springboot.api

methodtype createEntity POST
    consumes='*'
    produces='*'
    success=201
    handler='${entity}DataAccessService.create'
    semantic=SEM_CRUD_CREATE

methodtype readEntity GET
    instance=true
    produces='*'
    success=200
    operationId='get${produces}ById'
    operationName='get${produces}ById'
    handler='${entity}DataAccessService.read'
    semantic=SEM_CRUD_READ

methodtype updateEntity PUT
    instance=true
    consumes='*'
    produces='*'
    success=200
    handler='${entity}DataAccessService.update'
    operationName='update${entity}'
    semantic=SEM_CRUD_UPDATE

methodtype queryEntities GET
    produces='*[]'
    success=200
    pagination=true
    sort=true
    filter=true
    handler='${entity}DataAccessService.search'
    operationId='query${produces}Entities'
    operationName='query${produces[]}'
    semantic=SEM_CRUD_QUERY

methodtype deleteEntity DELETE
    instance=true
    success=204
    handler='${entity}DataAccessService.delete'
    semantic=SEM_CRUD_DELETE

Important conventions:

  • ${entity} is substituted with the backing entity type (Customer).
  • The handler property points to ${entity}DataAccessService methods: create, read, update, search, delete.
  • pagination, sort, filter flags drive how query parameters are interpreted and how the generated API behaves.

5.1 Bind the resource to a component

The API resource alone does not define a generated controller. The release example contains a third model in which a component provides the resource:

cmn
package org.joinedworkz.examples.backend

import org.joinedworkz.examples.customer.api
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot

component CustomerBackend
    basePackage='org.joinedworkz.examples.customer.webapp' {

    provide /customers
        subPackage='customers.v1'
        controller="CustomerV1Controller" {
    }
}

The provide /customers boundary drives controller/API-interface generation. It also creates an additional component-scoped OpenAPI document that aggregates all endpoints provided by CustomerBackend. This is separate from the model-scoped document generated from the non-abstract resources declared in the API model. An application can compose the component for diagrams, but does not create another OpenAPI aggregate in release 1.3.80.


6. Generated Spring Boot artefacts

From the domain, API and component models the SpringBoot facility generates the following concrete Main sources in the canonical release module:

6.1 Controller

A Spring MVC controller for /customers:

  • src/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/controller/CustomerV1Controller.java
  • src/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/api/CustomerV1Api.java

Responsibilities:

  • define the generated controller contract in CustomerV1Api;
  • expose HTTP endpoints for:
    • GET /customers (queryEntities)
    • POST /customers (createEntity)
    • GET /customers/{id} (readEntity)
    • PUT /customers/{id} (updateEntity)
    • DELETE /customers/{id} (deleteEntity)
  • map HTTP details (path variables, query parameters, request body) to Java types
  • call CustomerDataAccessService directly

6.2 Data access service

A generated service class encapsulates persistence logic:

  • src/generated/java/org/joinedworkz/examples/customer/das/CustomerDataAccessService.java

The generated class provides create, read, update, search and delete operations over the generated DTO and identifier types. Exact Java signatures are generator output and should be read from the generated class for the selected model. In the release 1.3.80 example, the identifier type is UUID and create/update use CustomerDto; it is not a create(Customer entity) API.

Internally this class uses:

  • src/generated/java/org/joinedworkz/examples/customer/repository/CustomerRepository.java;
  • src/generated/java/org/joinedworkz/examples/customer/entity/Customer.java.

6.3 Mapper interface

The generator creates:

  • src/generated/java/org/joinedworkz/examples/customer/mapper/CustomerMapper.java;
  • src/generated/java/org/joinedworkz/examples/customer/dto/CustomerDto.java.

Responsibilities:

  • map between the JPA entity Customer and CustomerDto
  • update an existing entity from CustomerDto while preserving its identifier

MapStruct generates CustomerMapperImpl during compilation. In more advanced setups you can define additional DTO types and use the same mapper pattern.


7. Runtime verification gate

The runtime startup command and concrete CRUD requests will be added after the example has passed a dedicated startup and endpoint smoke test. Until then, use the generated OpenAPI only as generation output, not as evidence that the example's persistence setup and every request flow work end to end.


8. Adapting the pattern to your own project

To use the same approach in your own application:

  1. Add facilities
    Add the Spring Boot facility as a Maven dependency (transitively bringing in Base and Java).

  2. Model your entities
    In a non-api layer (e.g. core), define your entities with type<entity> ... and key fields (**).

  3. Model your API layer

    • Use an api layer for external DTOs and resources.
    • Import:
      • your domain package(s)
      • org.joinedworkz.facilities.profiles.springboot
      • org.joinedworkz.facilities.springboot.api
    • Define resources that use:
      • createEntity, readEntity, updateEntity, queryEntities, deleteEntity.
  4. Configure outlets when adapting to multiple modules

    The canonical release example is a single Maven module and does not use sibling-module outlet overrides. Follow the Multi-module setup with outlet overrides for a separately verified routing example and the release 1.3.80 limitations.

  5. Generate and add manual code

    • Run the JoinedWorkz Maven plugin (cmn-maven-plugin:generate).
    • Inspect the generated controllers, data access services and mappers.
    • Keep application-specific services and other manual extensions outside src/generated/**.
  6. Iterate from the model
    As requirements change:

    • update the CMN models
    • regenerate
    • re-run tests and the application

In the canonical module, src/generated/** is replaceable output. The manual application shell is src/main/java/org/joinedworkz/examples/SpringBootExampleApplication.java; manual configuration is in src/main/resources/application.properties. Your own manual services likewise belong outside the generated tree.


9. Summary

The example-spring-boot project demonstrates how the SpringBoot facility can:

  • read a domain model (Customer)
  • read an API model that uses entity-specific CRUD method types
  • generate:
    • Spring MVC controllers
    • data access services
    • mappers between entities and DTOs
  • generate and compile the intended main-source CRUD API structure

By following the same pattern in your own project you can:

  • model entities and APIs in CMN
  • let JoinedWorkz generate the Spring Boot plumbing
  • focus your manual code on business logic instead of boilerplate

This summary does not imply that the generated integration test or the runtime CRUD flow has been verified.