Headless API Using Rest Builder


### A Step-by-Step Technical Guide


## 1. Overview

Liferay 7.4 ships with a **Headless REST framework** built on JAX-RS, OSGi,
and the **Vulcan** module (Liferay's OpenAPI-to-JAX-RS code generator). It powers
all of Liferay's out-of-the-box headless APIs (Headless Delivery, Headless
Commerce, Headless Admin User, etc.) and can be extended to expose **your own
custom entities** (built with Service Builder or Liferay Objects) as REST endpoints.

There are two implementation paths in 7.4:

| Path | When to use |
|---|---|
| **REST Builder** (`rest-builder` Gradle module) | You have a Service Builder
entity and want a fully generated, OpenAPI-documented REST API with minimal
boilerplate. |
| **Manual JAX-RS Component** (`@Component` + `Application`) | You need a
lightweight endpoint, a proxy/aggregator API, or full control over the
request/response cycle. |

This guide covers both, plus OAuth2 setup and testing.

---

## 2. Prerequisites

- Liferay DXP/Portal 7.4 (GA or fix pack), running locally or on a server
- Liferay Workspace (Gradle-based)
- Blade CLI installed and pointed at your workspace
- Java 11 (Liferay 7.4 baseline)
- An existing Service Builder module (e.g. `blog` or a custom `bookmanager-service`)
if you're exposing a custom entity
- Postman or `curl` for testing
- Basic familiarity with OpenAPI 3.0 syntax

---

## 3. Step 1 — Scaffold the REST Builder Module

From your Liferay Workspace root:

```bash
blade create -t rest-builder -p com.acme.bookmanager -v 7.4 bookmanager-rest
```

This generates two Gradle modules:

- `bookmanager-rest-api` — generated interfaces, DTOs
(from `rest-config.yaml` + `openapi.yaml`)
- `bookmanager-rest-impl` — where you write your actual resource implementation
logic

bookmanager-rest
│
├── bookmanager-rest-api       → What the API provides
├── bookmanager-rest-client    → How Java code calls the API
├── bookmanager-rest-impl      → How the API actually works
└── bookmanager-rest-test      → Tests that the API works

Add the module to `settings.gradle` if it isn't picked up automatically.

---

## 4. Step 2 — Define `rest-config.yaml`

Located in `bookmanager-rest-impl/rest-config.yaml`. This tells the REST Builder
generator which OpenAPI file to use and how to map versions to packages.

```yaml
openapi: openapi.yaml

apiPackage: com.acme.bookmanager.rest
apiDir: rest-impl
apiVersion: v1.0

application:
  baseURI: "/o/bookmanager"
  className: BookManagerApplication
  configurationClassName: BookManagerApplicationConfiguration
  configurationPid:
com.acme.bookmanager.rest.internal.configuration.BookManagerApplicationConfiguration
```
---Or
#apiDir: "../bookmanager-rest-api/src/main/java"
#apiPackagePath: "com.acme.bookmanager"
#application:
#    baseURI: "/bookmanager-rest"
#    className: "BookmanagerRestApplication"
#    name: "BookmanagerRest"
#author: "Admin"
#clientDir: "../bookmanager-rest-client/src/main/java"
#testDir: "../bookmanager-rest-test/src/testIntegration/java"
apiDir: "../bookmanager-rest-api/src/main/java"
apiPackagePath: "com.acme.bookmanager"
application:
    baseURI: "/bookmanager-rest"
    className: "BookmanagerRestApplication"
    name: "BookmanagerRest"
author: "Admin"
clientDir: "../bookmanager-rest-client/src/main/java"
testDir: "../bookmanager-rest-test/src/testIntegration/java"
#generateBatch: false
#generateGraphQL: false

## 5. Step 3 — Author the OpenAPI Contract (`openapi.yaml`)

This is the core artifact — the entire generated interface layer is derived from it.

```yaml
openapi: 3.0.1

info:
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
  title: "Book Manager REST API"
  version: "v1.0"

paths:

  "/books/{bookId}":

    get:
      operationId: getBook

      parameters:
        - name: bookId
          in: path
          required: true
          schema:
            type: integer
            format: int64

      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Book"
            application/xml:
              schema:
                $ref: "#/components/schemas/Book"

    put:
      operationId: putBook

      parameters:
        - name: bookId
          in: path
          required: true
          schema:
            type: integer
            format: int64

      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Book"
          application/xml:
            schema:
              $ref: "#/components/schemas/Book"

      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Book"
            application/xml:
              schema:
                $ref: "#/components/schemas/Book"

    delete:
      operationId: deleteBook

      parameters:
        - name: bookId
          in: path
          required: true
          schema:
            type: integer
            format: int64

      responses:
        "204":
          description: "No Content"

components:

  schemas:

    Book:
      x-class-name: Book
      properties:
        id:
          type: integer
          format: int64
        title:
          type: string
        author:
          type: string
        isbn:
          type: string

```

Run the generator:

```bash
blade gw :bookmanager-rest:bookmanager-rest-impl:buildREST
blade gw :modules:bookmanager-rest:bookmanager-rest-impl:buildREST --info
```

This produces the DTOs, the JAX-RS `Application` class, and an **abstract base
resource class** (`BaseBookResourceImpl`) with unimplemented
methods for each `operationId`.

---

## 6. Step 4 — Implement the Resource Class

Create `BookResourceImpl.java` extending the generated base class:

```java
package com.acme.bookmanager.internal.resource.v1_0;

import com.acme.bookmanager.resource.v1_0.BookResource;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;

import com.acme.bookmanager.dto.v1_0.Book;
 

/**
 * @author Admin
 */
@Component(
  properties = "OSGI-INF/liferay/rest/v1_0/book.properties",
  scope = ServiceScope.PROTOTYPE, service = BookResource.class
)
public class BookResourceImpl extends BaseBookResourceImpl {
 
    @Override
      public Book getBook(Long bookId) throws Exception {
          Book book = new Book();

          book.setId(bookId);
          book.setTitle("Test Book");
          book.setAuthor("John Doe");
          book.setIsbn("978-1234567890");

          return book;
      }

      @Override
      public Book putBook(Long bookId, Book book) throws Exception {
          book.setId(bookId);
          book.setTitle("Updated Book");
          book.setAuthor("Updated Author");
          book.setIsbn("978-9876543210");

          return book;
      }

    @Override
    public void delete(
        Collection<Book> books, Map<String, Serializable> parameters)
      throws Exception {

      throw new UnsupportedOperationException(
        "This method needs to be implemented");
    }
 
}
```

Key context objects available for free inside a
`BaseXResourceImpl`: `contextUser`, `contextCompany`, `contextHttpServletRequest`,
`contextAcceptLanguage`.

---

## 7. Step 5 — Build & Deploy

```bash
blade gw deploy
```

Or from Liferay Workspace root:

```bash

blade gw :modules:bookmanager-rest:bookmanager-rest-api:clean
blade gw :modules:bookmanager-rest:bookmanager-rest-impl:clean
blade gw :modules:bookmanager-rest:bookmanager-rest-impl:buildREST
blade gw :modules:bookmanager-rest:bookmanager-rest-api:build

blade gw :modules:bookmanager-rest:bookmanager-rest-api:build
blade gw :modules:bookmanager-rest:bookmanager-rest-impl:build

blade gw :modules:bookmanager-rest:bookmanager-rest-api:deploy
./gradlew :modules:bookmanager-rest:bookmanager-rest-impl:deploy
```

Confirm in **Gogo Shell** (`telnet localhost 11311`):

```
lb bookmanager
```

The bundle should show `Active`. The endpoint is now live at:

```
http://localhost:9070/o/bookmanager-rest/v1.0/books/4949
```

Liferay also auto-generates interactive Swagger/OpenAPI docs at:

```
http://localhost:9070/o/api
```

Comments