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, 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.md
  • Facilities and platforms
  • Model 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.cmn model with createEntity, readEntity, …
  • A working JoinedWorkz generator setup (Maven plugin)
  • Optional but recommended: JoinedWorkz Studio for editing .cmn models

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> Customer defines a complex type with the stereotype entity.
  • id** marks the primary key (stereotype key).
  • 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:

  • 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:

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 (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 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.

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 DataAccessService methods

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 Customer model

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 example CustomerView)
  • 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:

  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 (optional but recommended)
    Use joinedworkz.properties to 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/openapi
    
  5. Generate 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.
  6. 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