Appearance
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, runnable 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
- running the resulting Spring Boot application
For general background on the Spring Boot facility, see:
spring-boot-facility.mdFacilities and platformsModel a REST API step by step
1. Prerequisites
You should have:
- Java 17 (or later, depending on your JoinedWorkz / Spring Boot setup)
- Maven 3.8+
- A recent JoinedWorkz release with:
- Base, Java and SpringBoot platforms/facilities
- the
SpringBootApi.cmnmodel withcreateEntity,readEntity, …
- A working JoinedWorkz generator setup (Maven plugin)
- Optional but recommended: JoinedWorkz Studio for editing
.cmnmodels
Example project:
- Repository:
https://gitlab.com/joinedworkz/joinedworkz-examples/-/tree/main - Module:
example-spring-boot
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 https://gitlab.com/joinedworkz/joinedworkz-examples.git
cd joinedworkz-examples/example-spring-boot
# run model generation + Java build
mvn clean install
After the build you should see:
- generated model artefacts (OpenAPI, diagrams, …)
- generated Java sources (entities, DTOs, controllers, data access services, mappers)
- a runnable Spring Boot application (JAR)
3. Model the domain: Customer entity
In the example the domain model lives in a non-api layer (for example core). A simplified version of the customer entity could look like this:
core package org.joinedworkz.examples.customer
import org.joinedworkz.facilities.profiles.springboot
platform SpringBoot
type<entity> Customer {
id**: Id
firstName*: Name
lastName*: Name
email: String(255)
}
Key points:
type<entity> Customerdefines a complex type with the stereotypeentity.id**marks the primary key (stereotypekey).- Other fields (
firstName,lastName,email) are the mutable properties of the entity. - The layer (
core) indicates that this type is part of the internal domain model.
Depending on the facilities you include, the Java / persistence generators will create:
- a JPA entity class (e.g.
Customer) - a
CustomerDataAccessService(or similar) 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:
api package org.joinedworkz.examples.customer.api
import org.joinedworkz.examples.customer
import org.joinedworkz.facilities.profiles.springboot
import org.joinedworkz.facilities.springboot.api
platform SpringBoot
Here:
apiis the layer for external API types and resources.org.joinedworkz.examples.customerbrings theCustomerentity into scope.org.joinedworkz.facilities.springboot.apiprovides 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:
resource /customers as Customer[] by id {
queryEntities()
createEntity()
readEntity()
updateEntity()
deleteEntity()
}
abstract resource /address as Address { }
Explanation:
resource /customers as Customer[] by id/customersis a collection resourceCustomer[]indicates that the default representation is a list ofCustomerby iddefines the identifier for individual items in the collection
- The methods:
queryEntities()→ lists customers with paging/sorting/filteringcreateEntity()→ creates a new customerreadEntity()→ reads one customer by idupdateEntity()→ updates an existing customerdeleteEntity()→ 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 (excerpt from SpringBootApi.cmn):
package org.joinedworkz.facilities.springboot.api
/* additional CRUD resource method types for Spring-Boot entities */
methodtype createEntity POST
consumes='*'
produces='*'
success=201
handler='${entity}DataAccessService.create'
methodtype readEntity GET
instance=true
produces='*'
success=200
operationId='getById'
operationName='get${produces}ById'
handler='${entity}DataAccessService.read'
methodtype updateEntity PUT
instance=true
consumes='*'
produces='*'
success=200
handler='${entity}DataAccessService.update'
operationName='update${entity}'
methodtype queryEntities GET
produces='*[]'
success=200
pagination=true
sort=true
filter=true
handler='${entity}DataAccessService.search'
operationId='query${produces}Entities'
operationName='query${produces[]}'
methodtype deleteEntity DELETE
instance=true
success=204
handler='${entity}DataAccessService.delete'
Important conventions:
${entity}is substituted with the backing entity type (Customer).- The
handlerproperty points to${entity}DataAccessServicemethods:create,read,update,search,delete. pagination,sort,filterflags drive how query parameters are interpreted and how the generated API behaves.
6. Generated Spring Boot artefacts
From the domain and API models the SpringBoot facility generates several artefacts. The exact package names may vary, but you typically see:
6.1 Controller
A Spring MVC controller for /customers, e.g.:
CustomerV1Controller(or similar)
Responsibilities:
- 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 the appropriate handler or
DataAccessServicemethods
6.2 Data access service
A generated service class to encapsulate persistence logic, e.g.:
CustomerDataAccessService
Methods (aligned with handler settings in SpringBootApi.cmn):
create(Customer entity)read(Id id)update(Customer entity)search(...)(with paging / sorting / filters)delete(Id id)
Internally this class typically uses:
- a Spring Data JPA repository
- the JPA entity generated from the
Customermodel
6.3 Mapper interface
If API DTOs differ from the domain entity, the generator creates mappers, for example:
CustomerMapper
Responsibilities:
- map between entity (
Customer) and DTO(s) (for exampleCustomerView) - handle list mappings for query results
In the simple example, the API uses the entity type directly as default representation. In more advanced setups you can define dedicated DTO types in the api layer and use the same pattern with mappers.
7. Run the Spring Boot application
After generation and Maven build you can run the example application:
cd joinedworkz-examples/example-spring-boot
mvn spring-boot:run
By default the application will start on port 8080 (unless configured otherwise). You can then call the generated endpoints, for example:
# list customers (empty at first)
curl http://localhost:8080/customers
# create a customer
curl -X POST http://localhost:8080/customers \
-H "Content-Type: application/json" \
-d '{
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.org"
}'
# read a customer by id (replace {id} with the returned id)
curl http://localhost:8080/customers/{id}
# update a customer
curl -X PUT http://localhost:8080/customers/{id} \
-H "Content-Type: application/json" \
-d '{
"firstName": "Ada",
"lastName": "Byron",
"email": "ada.byron@example.org"
}'
# delete a customer
curl -X DELETE http://localhost:8080/customers/{id}
The exact URLs and payloads depend on the generated controller and Jackson / Spring Boot configuration, but the pattern matches the model: collection resource /customers plus CRUD over /customers/{id}.
8. Adapting the pattern to your own project
To use the same approach in your own application:
Add facilities
Add the Spring Boot facility as a Maven dependency (transitively bringing in Base and Java).Model your entities
In a non-apilayer (e.g.core), define your entities withtype<entity> ...and key fields (**).Model your API layer
- Use an
apilayer for external DTOs and resources. - Import:
- your domain package(s)
org.joinedworkz.facilities.profiles.springbootorg.joinedworkz.facilities.springboot.api
- Define resources that use:
createEntity,readEntity,updateEntity,queryEntities,deleteEntity.
- Use an
Configure outlets (optional but recommended)
Usejoinedworkz.propertiesto direct generated Java sources and OpenAPI files to the right Maven modules, for example:outlet.generatedJavaSource.core.directory=../domain/src/generated/java outlet.generatedJavaSource.api.directory=../webapp/src/generated/java outlet.generatedOpenApi.api.directory=../webapp/src/generated/resources/openapiGenerate and implement
- Run the JoinedWorkz Maven plugin (
cmn-maven-plugin:generate). - Inspect the generated controllers, data access services and mappers.
- If handler interfaces / implementations are generated, implement the handler classes once and keep them under version control.
- Run the JoinedWorkz Maven plugin (
Iterate from the model
As requirements change:- update the CMN models
- regenerate
- re-run tests and the application
Your manual code lives in handler implementations, domain services and other non-generated classes; generators keep the REST + persistence plumbing aligned with your models.
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
- expose a fully working CRUD REST API
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
