Skip to content

Model a REST API step by step

This guide walks you through modelling a small REST API with JoinedWorkz – from domain model to resources, component and application – and generating OpenAPI (and optionally Spring Boot artefacts).

We’ll use a simple Customer domain as running example and the SpringBoot platform so that the same model can drive both OpenAPI and Spring Web controllers.

For background reading see:

The canonical Customer source for release 1.3.80 is the example-spring-boot module on release/1.3.80. Its generation, main-source compilation and packaging were verified at commit df7cabf7f21b with Java 21.0.8 and Maven 3.9.16 on 2026-07-26. The generated integration test was not registered, compiled or run; runtime CRUD smoke testing is a separate pending gate.


1. Prerequisites

Before you start, make sure you have:

  • Java 21 installed
  • Maven 3.9 or newer (mvn -v should work)
  • JoinedWorkz Studio installed (optional but recommended)
  • A project that has:
    • the JoinedWorkz Maven plugin configured
    • a dependency to the SpringBoot facility

A minimal POM setup looks like this (simplified):

xml
<properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <joinedworkz.version>1.3.80</joinedworkz.version>
</properties>

<build>
    <resources>
        <resource>
            <directory>model</directory>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>src/generated/resources</directory>
        </resource>
    </resources>

    <plugins>
        <!-- JoinedWorkz generator -->
        <plugin>
            <groupId>org.joinedworkz.cmn</groupId>
            <artifactId>cmn-maven-plugin</artifactId>
            <version>${joinedworkz.version}</version>
            <executions>
                <execution>
                    <?m2e ignore?><!-- ignore this execution in Eclipse -->
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <!-- register generated Java sources (if you generate Java) -->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>add-generated-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${basedir}/src/generated/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<dependencies>
    <!-- Spring Boot platform (includes Java and Base via transitive dependencies) -->
    <dependency>
        <groupId>org.joinedworkz.facilities</groupId>
        <artifactId>spring-boot</artifactId>
        <version>${joinedworkz.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

This excerpt shows the JoinedWorkz and generated-source setup. Use the canonical module's complete POM when compiling the release example, including its Spring Boot, persistence and mapper dependencies and application shell.


2. Create the domain model

Create a model file, for example:

  • model/customer.cmn

Unlike Java, CMN files do not need to live in a directory structure that matches the package name. You are free to organise the model/ folder by feature (e.g. common, core, api, …).

Add a header with layer, package, imports and platform:

cmn
core package org.joinedworkz.examples.customer

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

platform SpringBoot
  • core – optional layer; in release 1.3.80, tag-aware Java generators can use it to select a source-tag-specific outlet directory.
  • package – namespace of your model.
  • import – Base types from the Base facility plus the SpringBoot profile.
  • platform SpringBoot – tells JoinedWorkz to interpret the model with the SpringBoot platform (which builds on Java and Base).

Note: Base types and the Base profile have global default imports. The explicit imports used by the canonical source keep the model's dependencies visible.

2.1 Enum for setup type

cmn
enum SetupType {

    NONE value="none"
    NP   value="NP"
    JP   value="JP"
}

Each enum literal gets a value property that can be used for external codes (e.g. in JSON or databases).

2.2 Address type

cmn
type Address {
    street*: String(200)
    zipCode: String(10)
    city*:   String(100)
    country: String(2)
}
  • * marks mandatory fields.
  • String(200) is a shorthand for maxLength=200 (see simple types).

2.3 Customer entity

cmn
type<entity> Customer {

    id**:       Id
    firstName*: Name
    lastName*:  Name
    email:      String(255)
    setupType:  SetupType

    mainAddress: Address
}
  • <entity> – stereotypes Customer as an entity.
  • id** – marks the field as the entity/resource key.
  • firstName*, lastName* – mandatory fields.
  • Address is embedded as a containment (default : relationship).

At this point you have a valid domain model that can already be used for diagram generation by the Base platform (via SpringBoot).


3. Model the REST API in a separate file

The release example keeps the API in a separate model file. The layer (core, api, …) expresses architectural intent and can route generated Java classes through a matching source-tag mapping. Other outlets require generator-specific verification; the Base OpenAPI generators in release 1.3.80 use only their global outlet directory. For example:

  • model/api.cmn

Header:

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
  • api – layer for API-related models.
  • import org.joinedworkz.examples.customer – brings the domain types (Customer, Address, SetupType, …) into scope.
  • org.joinedworkz.facilities.springboot.api provides the entity CRUD method types used below.
  • The SpringBoot platform is again selected so that OpenAPI and Spring artefacts are generated for this file.

3.1 Collection resource /customers

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

What this says:

  • /customers is a collection resource of Customer (Customer[]).
  • Items are identified by the id field of Customer.
  • We support:
    • queryEntities() for a paginated, sortable and filterable query
    • createEntity(), readEntity() and updateEntity() for entity CRUD
    • deleteEntity() for deletion

These method types come from the imported SpringBoot API model. They define the HTTP verbs, representations, success codes, CRUD semantics and generated CustomerDataAccessService handler targets used by the release example.

Pagination, sorting and filtering metadata are supplied by queryEntities(). They do not need to be repeated as explicit resource method parameters.

3.2 Reusable address resource

The canonical API model also defines an abstract resource for an address:

cmn
abstract resource /address as Address { }

An abstract resource is reusable modeling input and is not emitted as a standalone endpoint. The release example deliberately does not attach or provide /address; doing so would be a separate extension with its own verification.


4. Attach the API to a component and application

To tie the API to an implementation context, you model a component and an application in a third file, for example:

  • model/customer-backend.cmn

Header:

cmn
package org.joinedworkz.examples.backend

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

platform SpringBoot

The canonical component model is untagged. The key point is that the platform is again SpringBoot – this is what enables generation of Spring controllers and related artefacts. If you selected only the Base platform here, you would get OpenAPI and diagrams but no Java controllers.

4.1 Component: CustomerBackend

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

    provide /customers
        subPackage='customers.v1'
        controller="CustomerV1Controller" {
        // optional pseudo-code can go here
    }
}
  • component – defines a technical building block.
  • provide /customers – this component implements the /customers resource from the API model.
  • basePackage + subPackage – used for generated Java package names.
  • controller – logical controller name, used as tag/class name depending on the platform.

When the SpringBoot platform is active, it can generate controller classes in org.joinedworkz.examples.customer.webapp.customers.v1 based on this information.

4.2 Application: CustomerApp

cmn
application CustomerApp {

    consists of {
        CustomerBackend
    }

    use {
        // external components can be listed here later
    }
}

The application:

  • gives you a high-level view of the components that belong together
  • allows the Base platform to generate application/component diagrams.

The Base OpenAPI cartridge uses the component, not the application, as the aggregation boundary. In release 1.3.80 it generates:

  • a model-scoped document containing the non-abstract resources declared in the API model;
  • an additional component-scoped document containing every endpoint in CustomerBackend's provide declarations.

This component document can combine resources imported from several CMN models. Controller names can be used as operation tags. The application does not produce a third, application-wide OpenAPI document.


5. Generate the artefacts

You can now generate OpenAPI (and optionally Java / Spring Boot artefacts) either via Maven or via JoinedWorkz Studio.

5.1 Using Maven

From the project root, run:

bash
mvn clean verify

or during development:

bash
mvn generate-sources

For the canonical release module, the verified default outputs include:

  • Model-scoped OpenAPI YAMLsrc/generated/resources/openapi/org.joinedworkz.examples.customer.api.yaml
  • Aggregated component OpenAPI YAMLsrc/generated/resources/openapi/org.joinedworkz.examples.backend_customerbackend.yaml
  • Matching OpenAPI HTML viewersdiagram/api/org.joinedworkz.examples.customer.api.html and diagram/api/org.joinedworkz.examples.backend_customerbackend.html
  • Controller and API interfacesrc/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/controller/CustomerV1Controller.java and src/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/api/CustomerV1Api.java

Make sure the src/generated/resources and src/generated/java directories are registered in your POM as shown in the prerequisites section.

If you route outlets to other modules via joinedworkz.properties, look for the generated artefacts in the corresponding target modules.

5.2 Using JoinedWorkz Studio

If you open the project in JoinedWorkz Studio:

  1. Import the Maven project.
  2. Open the .cmn models in the editor.
  3. Fix any validation errors reported in the Problems view.
  4. Save the files – this can trigger generation.
  5. Alternatively, use the explicit Generate command from the menu or context menu.

Generation progress and any generator messages are shown in a dedicated console. Errors and warnings are linked back to the model elements in the editor.


6. Next steps

From here you can extend the example in several directions:

  • Error modelling
    Define error types (e.g. NotFoundError, ValidationError) and add them as additional responses to method types or resource methods.

  • Versioning
    Introduce versioned subpackages (e.g. customers.v2) and map them to separate controllers in the component model.

  • DTOs and projections
    Use separate DTO types (<projection> or plain complex types) for external representations, and map from entities to DTOs in generators.

  • Integration with existing Spring Boot projects
    Use the generated OpenAPI or controllers as starting point and integrate them into an existing codebase.

For more details on the individual modelling constructs, refer back to:

7. Using other method types (advanced)

This guide uses the SpringBoot entity CRUD method types createEntity(), readEntity(), updateEntity(), queryEntities() and deleteEntity(). They are the canonical choice for the generated DataAccessService pattern shown here.

The Base facility additionally provides platform-neutral opinionated method types such as create(), read(), update(), query() and list(). It also provides raw method types for lower-level HTTP contracts.

If you need very special HTTP contracts or non-standard behaviour, the Base facility also provides raw method types without defaults:

cmn
methodtype get    GET
methodtype post   POST
methodtype put    PUT
methodtype patch  PATCH
methodtype delete DELETE

Release 1.3.80 also contains a second Base declaration named delete. Therefore, use the unambiguous get, post, put, and patch names where appropriate, but define a uniquely named project method type such as removeCustomer for DELETE rather than relying on Base delete() defaults. You can then specify details directly on the resource methods:

  • consumes / produces (including dictionaries)
  • success and error status codes
  • additional responses
  • whether the method operates on the collection or a resource item

This gives you full freedom when you need it, while the opinionated method types remain the primary building blocks for typical REST APIs.