### 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
Post a Comment