Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
61 lines
3.1 KiB
Markdown
61 lines
3.1 KiB
Markdown
# thymeleaf-htmx
|
|
|
|
Companion project for **Spring Boot 4 + Thymeleaf + htmx: Server-Side CRUD UI in 2026** on **[ankurm.com](https://ankurm.com)**.
|
|
|
|
A contacts list with search, pagination, create-with-validation, inline edit and delete. All HTML is rendered by Thymeleaf; htmx only decides *which part* of a page to replace. About 25 lines of JavaScript-free attributes, no JS framework, no build step.
|
|
|
|
Every console block in the article is quoted from `output/`. The MockMvc transcripts are written by the tests themselves (they assert on the same data), and `output/11-browser-check.txt` comes from headless Chromium driving the packaged jar.
|
|
|
|
## Versions
|
|
|
|
| | |
|
|
|---|---|
|
|
| Spring Boot | 4.1.2 (latest GA on Maven Central; 4.2.0-RC1 exists but is not GA) |
|
|
| Thymeleaf | 3.1.5.RELEASE (managed by Boot) |
|
|
| htmx | 2.0.11 as the WebJar `org.webjars.npm:htmx.org` (htmx 4.0.0 exists on npm `next`; see the article) |
|
|
| JDK | 25 |
|
|
| Test tooling | `spring-boot-starter-webmvc-test`, `spring-boot-starter-thymeleaf-test`; Playwright 1.64.1 for the browser check (optional) |
|
|
|
|
## Run
|
|
|
|
```bash
|
|
export JAVA_HOME=/path/to/jdk-25
|
|
mvn spring-boot:run # http://localhost:8080/contacts
|
|
mvn test # 10 MockMvc tests, rewrite output/01..09
|
|
./scripts/run-all.sh # tests + live curl + browser check, regenerates all of output/
|
|
```
|
|
|
|
The browser check needs Node 20+ and a one-time `npx playwright install chromium`.
|
|
|
|
## Where things are
|
|
|
|
| Concern | File |
|
|
|---|---|
|
|
| Layout fragment (`layout(title, content)`), htmx config meta tag, WebJar script tag | `templates/layout/base.html` |
|
|
| List page + the `table` fragment htmx receives | `templates/contacts/list.html` |
|
|
| Row, edit-row (also the error state) | `templates/contacts/fragments.html` |
|
|
| Create form (full page and `form` fragment) | `templates/contacts/new.html` |
|
|
| Fragment vs full page decision (`HX-Request`, `HX-History-Restore-Request`) | `Htmx.java`, `ContactController.java` |
|
|
| `Vary` header | `VaryHtmxFilter.java` |
|
|
| Contract tests | `ContactUiTest.java`, `Transcript.java` |
|
|
| Real-browser behaviour checks | `browser-check/check.mjs` |
|
|
|
|
## Output
|
|
|
|
| File | Shows |
|
|
|---|---|
|
|
| `output/00-mvn-test.txt` | surefire summary |
|
|
| `output/01-full-page-vs-fragment.txt` | same URL, with and without `HX-Request` |
|
|
| `output/02-history-restore-gets-full-page.txt` | the history-miss case |
|
|
| `output/03-htmx-webjar.txt` | version-less WebJar path resolved to the pinned version |
|
|
| `output/04-pagination-and-search.txt` | last page, page past the end, search |
|
|
| `output/05-create-validation-errors.txt` | 422 with errors, fragment and full page |
|
|
| `output/06-create-success.txt` | `HX-Redirect` vs 302 |
|
|
| `output/07-inline-edit.txt` | edit row, failed save, successful save |
|
|
| `output/08-delete.txt` | 200 + `HX-Trigger`, 404 |
|
|
| `output/09-delete-last-page-clamps.txt` | pager after deleting the last page's rows |
|
|
| `output/10-live-curl.txt` | real Tomcat answering with curl |
|
|
| `output/11-browser-check.txt` | Chromium: swaps, history, 204 and 422 traps |
|
|
|
|
Not covered: Spring Security / CSRF (no Security dependency in this module), file uploads, and htmx 4.
|