Skip to content

Commit b4faffd

Browse files
authored
Add pagination and tighten API boundaries
* Add pagination and tighten API boundaries * Clarify logging terminology --------- Co-authored-by: DataTideHH <219566149+DataTideHH@users.noreply.github.com>
1 parent bc60c4e commit b4faffd

9 files changed

Lines changed: 236 additions & 71 deletions

File tree

README.md

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ It demonstrates:
2121

2222
- REST endpoints for a small process-related resource
2323
- layered backend structure with controller, service and repository
24-
- request validation
25-
- status-based filtering
24+
- request validation and matching database constraints
25+
- status-based filtering and pagination
2626
- explicit HTTP success and error behavior
2727
- basic persistence with Spring Data JPA
28+
- restrained parameterized logging for write operations
2829
- an H2 in-memory database for local development and tests
2930
- automated API integration tests with MockMvc
3031
- a reproducible Maven Wrapper workflow
@@ -52,10 +53,12 @@ It complements my main Data/BI portfolio projects around SQL, Python, Power BI,
5253
| Language | Java 21 | Main implementation language |
5354
| Framework | Spring Boot 4.1 | REST API application framework |
5455
| API layer | Spring Web MVC | HTTP endpoints and JSON responses |
56+
| Pagination | Spring Data `Pageable` and `PagedModel` | Bounded list responses with stable page metadata |
5557
| Persistence | Spring Data JPA | Repository abstraction and entity persistence |
5658
| Database | H2 | In-memory local development and test database |
5759
| Validation | Jakarta Validation | Validation for incoming request data |
5860
| Error format | Spring `ProblemDetail` | Consistent `application/problem+json` responses |
61+
| Logging | SLF4J | Structured create, update and delete messages |
5962
| Tests | JUnit 5, Spring Boot Test, MockMvc | API integration and persistence verification |
6063
| Build tool | Maven Wrapper | Reproducible builds on Windows, macOS and Linux |
6164
| CI | GitHub Actions | Automated Java 21 Maven verification |
@@ -86,13 +89,21 @@ The API uses request and response records instead of exposing the JPA entity dir
8689

8790
| Method | Endpoint | Success | Purpose |
8891
|---|---|---:|---|
89-
| `GET` | `/api/process-checks` | `200 OK` | Return all process-check records |
90-
| `GET` | `/api/process-checks?status=OK` | `200 OK` | Filter records by `OK`, `WARNING` or `CRITICAL` |
92+
| `GET` | `/api/process-checks?page=0&size=20` | `200 OK` | Return one page of process-check records |
93+
| `GET` | `/api/process-checks?status=OK&page=0&size=20` | `200 OK` | Filter and page records by status |
9194
| `GET` | `/api/process-checks/{id}` | `200 OK` | Return one process-check record by ID |
9295
| `POST` | `/api/process-checks` | `201 Created` | Create a record and return its URI in `Location` |
9396
| `PUT` | `/api/process-checks/{id}` | `200 OK` | Replace the editable values of an existing record |
9497
| `DELETE` | `/api/process-checks/{id}` | `204 No Content` | Delete an existing record |
9598

99+
List endpoints accept the standard Spring Data parameters:
100+
101+
- `page`: zero-based page number, default `0`
102+
- `size`: requested page size, default `20`, capped at `100`
103+
- `sort`: field and direction, for example `sort=processName,asc`
104+
105+
The default list order is `lastCheckedAt,desc`.
106+
96107
Typical client errors:
97108

98109
| Situation | Result |
@@ -103,27 +114,39 @@ Typical client errors:
103114

104115
---
105116

106-
## Example Process-Check Record
117+
## Example Paged Response
107118

108119
```json
109120
{
110-
"id": 1,
111-
"processName": "Daily sales import",
112-
"owner": "Data Operations",
113-
"status": "OK",
114-
"lastCheckedAt": "2026-07-10T00:25:00",
115-
"slaMinutes": 60
121+
"content": [
122+
{
123+
"id": 1,
124+
"processName": "Daily sales import",
125+
"owner": "Data Operations",
126+
"status": "OK",
127+
"lastCheckedAt": "2026-07-10T00:25:00",
128+
"slaMinutes": 60
129+
}
130+
],
131+
"page": {
132+
"size": 20,
133+
"totalElements": 1,
134+
"totalPages": 1,
135+
"number": 0
136+
}
116137
}
117138
```
118139

119140
Request validation requires:
120141

121-
- a non-blank `processName`
122-
- a non-blank `owner`
142+
- a non-blank `processName` with at most 120 characters
143+
- a non-blank `owner` with at most 120 characters
123144
- a valid status: `OK`, `WARNING` or `CRITICAL`
124145
- a non-null ISO local date-time value
125146
- `slaMinutes` of at least `1`
126147

148+
The entity mirrors the non-null and maximum-length constraints so the API and database schema enforce the same basic rules.
149+
127150
---
128151

129152
## Error Response Example
@@ -164,10 +187,18 @@ Then open:
164187
http://localhost:8080/api/process-checks
165188
```
166189

167-
At first startup, the API returns an empty JSON array because the H2 database is empty:
190+
At first startup, the H2 database is empty, so the list endpoint returns an empty page:
168191

169192
```json
170-
[]
193+
{
194+
"content": [],
195+
"page": {
196+
"size": 20,
197+
"totalElements": 0,
198+
"totalPages": 0,
199+
"number": 0
200+
}
201+
}
171202
```
172203

173204
---
@@ -189,13 +220,14 @@ At first startup, the API returns an empty JSON array because the H2 database is
189220
The automated suite verifies:
190221

191222
- application context startup
223+
- default and requested pagination
192224
- unfiltered and status-filtered list requests
193225
- empty filter results
194226
- invalid status handling
195227
- lookup by ID
196228
- `404` Problem Detail responses
197229
- successful creation with `201 Created` and `Location`
198-
- request validation failures
230+
- blank, invalid and oversized request values
199231
- update behavior and persisted values
200232
- successful deletion with `204 No Content`
201233
- update and delete behavior for unknown IDs
@@ -223,11 +255,12 @@ The workflow has read-only repository permissions and cancels superseded runs fo
223255
The full CRUD flow can also be exercised manually with curl, an API client or an IDE HTTP client:
224256

225257
```text
226-
POST /api/process-checks create a process-check record
227-
GET /api/process-checks list all process-check records
228-
GET /api/process-checks/1 read one process-check record
229-
PUT /api/process-checks/1 update one process-check record
230-
DELETE /api/process-checks/1 delete one process-check record
258+
POST /api/process-checks
259+
GET /api/process-checks?page=0&size=20
260+
GET /api/process-checks?status=OK&page=0&size=20
261+
GET /api/process-checks/1
262+
PUT /api/process-checks/1
263+
DELETE /api/process-checks/1
231264
```
232265

233266
Example test data:
@@ -242,6 +275,14 @@ slaMinutes: 60
242275

243276
---
244277

278+
## Logging
279+
280+
Create, update and delete operations write one parameterized application log entry containing the record ID and, where useful, its status.
281+
282+
Read requests and complete request bodies are not logged. This keeps the example useful for troubleshooting without producing noisy logs or copying input data unnecessarily.
283+
284+
---
285+
245286
## H2 Database Note
246287

247288
This project uses an **H2 in-memory database** for local development and API testing.
@@ -314,12 +355,14 @@ This repository demonstrates a small but realistic backend foundation:
314355
- Spring Boot application structure
315356
- REST endpoint and HTTP-status design
316357
- JSON request and response handling
317-
- CRUD operations and status filtering
358+
- paginated list queries and status filtering
318359
- layered backend organization
319-
- request validation
360+
- request validation aligned with persistence constraints
320361
- standard Problem Detail error responses
321362
- explicit transaction boundaries
363+
- JPA dirty checking for managed updates
322364
- persistence abstraction with Spring Data JPA
365+
- restrained parameterized logging
323366
- local development and testing with H2
324367
- automated integration testing
325368
- reproducible Maven builds
@@ -332,7 +375,7 @@ This repository demonstrates a small but realistic backend foundation:
332375

333376
This is a learning project.
334377

335-
It does not include production database configuration, Docker deployment, authentication and authorization, a frontend UI, cloud deployment, monitoring infrastructure, pagination or enterprise-scale operational error handling.
378+
It does not include production database configuration, Docker deployment, authentication and authorization, a frontend UI, cloud deployment, metrics, tracing or enterprise-scale operational error handling.
336379

337380
These omissions are intentional. The current scope is limited to a clean, understandable and tested Spring Boot REST API baseline.
338381

docs/index.md

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Small Java 21 / Spring Boot REST API portfolio project
55

66
# Spring Boot Process API Basics
77

8-
**Small Java 21 / Spring Boot REST API project exposing validated process-check data through a layered backend structure, automated tests and H2 persistence.**
8+
**Small Java 21 / Spring Boot REST API project exposing validated and paginated process-check data through a layered backend structure, automated tests and H2 persistence.**
99

1010
[View repository](https://github.com/DataTideHH/spring-boot-process-api-basics) · [Read the full README](https://github.com/DataTideHH/spring-boot-process-api-basics/blob/main/README.md) · [View CI](https://github.com/DataTideHH/spring-boot-process-api-basics/actions/workflows/ci.yml) · [DataTideHH portfolio](https://datatidehh.de/)
1111

@@ -17,7 +17,7 @@ This project is a deliberately compact backend learning project.
1717

1818
It demonstrates how process-related records can be represented, validated, persisted and exposed through a small REST API using Spring Boot.
1919

20-
The goal is not to present a production service or an enterprise backend system. The goal is to document a clean first step from Java basics toward a small layered REST API with explicit HTTP behavior, persistence and automated verification.
20+
The goal is not to present a production service or an enterprise backend system. The goal is to document a clean first step from Java basics toward a small layered REST API with explicit HTTP behavior, bounded list queries, persistence and automated verification.
2121

2222
---
2323

@@ -37,11 +37,14 @@ It follows the [IPv4 Subnet Calculator Multilang](https://datatidehh.github.io/i
3737
- controller, service and repository separation
3838
- Spring Data JPA repository usage
3939
- request and response records
40-
- Jakarta Validation
40+
- Jakarta Validation aligned with entity constraints
4141
- H2 in-memory persistence
4242
- CRUD endpoints for process-check data
43-
- optional status filtering
43+
- status filtering and pagination
44+
- stable page metadata through Spring Data `PagedModel`
4445
- standard `ProblemDetail` error responses
46+
- explicit transaction boundaries and JPA dirty checking
47+
- restrained parameterized logging for write operations
4548
- integration tests with Spring Boot Test and MockMvc
4649
- reproducible Maven Wrapper builds
4750
- GitHub Actions verification on Java 21
@@ -52,30 +55,44 @@ It follows the [IPv4 Subnet Calculator Multilang](https://datatidehh.github.io/i
5255

5356
| Method | Endpoint | Result | Purpose |
5457
|---|---|---:|---|
55-
| `GET` | `/api/process-checks` | `200` | List all records |
56-
| `GET` | `/api/process-checks?status=OK` | `200` | Filter by `OK`, `WARNING` or `CRITICAL` |
58+
| `GET` | `/api/process-checks?page=0&size=20` | `200` | List one page of records |
59+
| `GET` | `/api/process-checks?status=OK&page=0&size=20` | `200` | Filter and page by status |
5760
| `GET` | `/api/process-checks/{id}` | `200` | Read one record |
5861
| `POST` | `/api/process-checks` | `201` | Create a record and return `Location` |
5962
| `PUT` | `/api/process-checks/{id}` | `200` | Update a record |
6063
| `DELETE` | `/api/process-checks/{id}` | `204` | Delete a record |
6164

65+
The list endpoint defaults to 20 records, sorts by `lastCheckedAt` descending and caps requested page sizes at 100.
66+
6267
Invalid input returns `400 Bad Request`. Unknown record IDs return `404 Not Found` as `application/problem+json`.
6368

6469
---
6570

66-
## Example process-check record
71+
## Example paged response
6772

6873
```json
6974
{
70-
"id": 1,
71-
"processName": "Daily sales import",
72-
"owner": "Data Operations",
73-
"status": "OK",
74-
"lastCheckedAt": "2026-07-10T00:25:00",
75-
"slaMinutes": 60
75+
"content": [
76+
{
77+
"id": 1,
78+
"processName": "Daily sales import",
79+
"owner": "Data Operations",
80+
"status": "OK",
81+
"lastCheckedAt": "2026-07-10T00:25:00",
82+
"slaMinutes": 60
83+
}
84+
],
85+
"page": {
86+
"size": 20,
87+
"totalElements": 1,
88+
"totalPages": 1,
89+
"number": 0
90+
}
7691
}
7792
```
7893

94+
`processName` and `owner` are required and limited to 120 characters. The JPA entity mirrors these length and nullability rules.
95+
7996
---
8097

8198
## Local usage
@@ -104,7 +121,7 @@ http://localhost:8080/api/process-checks
104121

105122
## Verification
106123

107-
The integration suite covers list and filter behavior, lookup by ID, creation, validation failures, updates, deletion, persistence effects and `404` Problem Detail responses.
124+
The integration suite covers pagination, sorting, status filtering, lookup by ID, creation, blank and oversized input, updates, deletion, persistence effects and `404` Problem Detail responses.
108125

109126
The GitHub Actions workflow runs `clean verify` with Eclipse Temurin Java 21 for pull requests and pushes to `main`.
110127

@@ -136,4 +153,4 @@ The project uses an H2 in-memory database. Data is reset when the application st
136153

137154
The sample data is synthetic and does not contain personal, customer or production data.
138155

139-
This is a learning project with a deliberately limited scope. It does not claim production deployment, authentication, cloud operation or enterprise-scale infrastructure.
156+
This is a learning project with a deliberately limited scope. It does not claim production deployment, authentication, cloud operation, monitoring infrastructure or enterprise-scale operation.

src/main/java/de/datatidehh/processapi/processcheck/ProcessCheck.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package de.datatidehh.processapi.processcheck;
22

3-
import jakarta.persistence.*;
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.GeneratedValue;
8+
import jakarta.persistence.GenerationType;
9+
import jakarta.persistence.Id;
10+
411
import java.time.LocalDateTime;
512

613
@Entity
@@ -10,13 +17,20 @@ public class ProcessCheck {
1017
@GeneratedValue(strategy = GenerationType.IDENTITY)
1118
private Long id;
1219

20+
@Column(nullable = false, length = 120)
1321
private String processName;
22+
23+
@Column(nullable = false, length = 120)
1424
private String owner;
1525

1626
@Enumerated(EnumType.STRING)
27+
@Column(nullable = false, length = 20)
1728
private ProcessStatus status;
1829

30+
@Column(nullable = false)
1931
private LocalDateTime lastCheckedAt;
32+
33+
@Column(nullable = false)
2034
private Integer slaMinutes;
2135

2236
protected ProcessCheck() {

src/main/java/de/datatidehh/processapi/processcheck/ProcessCheckController.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package de.datatidehh.processapi.processcheck;
22

33
import jakarta.validation.Valid;
4+
import org.springframework.data.domain.Pageable;
5+
import org.springframework.data.domain.Sort;
6+
import org.springframework.data.web.PagedModel;
7+
import org.springframework.data.web.PageableDefault;
48
import org.springframework.http.ResponseEntity;
59
import org.springframework.web.bind.annotation.DeleteMapping;
610
import org.springframework.web.bind.annotation.GetMapping;
@@ -14,7 +18,6 @@
1418
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
1519

1620
import java.net.URI;
17-
import java.util.List;
1821

1922
@RestController
2023
@RequestMapping("/api/process-checks")
@@ -27,10 +30,15 @@ public ProcessCheckController(ProcessCheckService service) {
2730
}
2831

2932
@GetMapping
30-
public List<ProcessCheckResponse> findAll(
31-
@RequestParam(required = false) ProcessStatus status
33+
public PagedModel<ProcessCheckResponse> findAll(
34+
@RequestParam(required = false) ProcessStatus status,
35+
@PageableDefault(
36+
size = 20,
37+
sort = "lastCheckedAt",
38+
direction = Sort.Direction.DESC
39+
) Pageable pageable
3240
) {
33-
return service.findAll(status);
41+
return new PagedModel<>(service.findAll(status, pageable));
3442
}
3543

3644
@GetMapping("/{id}")
@@ -70,4 +78,4 @@ private URI buildLocation(Long id) {
7078
.buildAndExpand(id)
7179
.toUri();
7280
}
73-
}
81+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package de.datatidehh.processapi.processcheck;
22

3+
import org.springframework.data.domain.Page;
4+
import org.springframework.data.domain.Pageable;
35
import org.springframework.data.jpa.repository.JpaRepository;
46

5-
import java.util.List;
6-
77
public interface ProcessCheckRepository extends JpaRepository<ProcessCheck, Long> {
88

9-
List<ProcessCheck> findAllByStatus(ProcessStatus status);
9+
Page<ProcessCheck> findAllByStatus(ProcessStatus status, Pageable pageable);
1010
}

0 commit comments

Comments
 (0)