-
Notifications
You must be signed in to change notification settings - Fork 97
Add Grails 8 HTTP client guide (v8 prose, snippets, guides.yml) #520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jamesfredley
merged 3 commits into
apache:master
from
sanjana2505006:grails-http-client-v8-guide
Jul 21, 2026
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
guides/grails-http-client/v8/guide/domainAndControllers.adoc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| Local record label data stays in GORM while album search results come from the external API. | ||
|
|
||
| [[recordLabel]] | ||
| == RecordLabel domain | ||
|
|
||
| [source,groovy] | ||
| .grails-app/domain/example/RecordLabel.groovy | ||
| ---- | ||
| include::../snippets/grails-app/domain/example/RecordLabel.groovy[] | ||
| ---- | ||
|
|
||
| Seed development data in `BootStrap`: | ||
|
|
||
| [source,groovy] | ||
| .grails-app/init/example/BootStrap.groovy | ||
| ---- | ||
| include::../snippets/grails-app/init/example/BootStrap.groovy[] | ||
| ---- | ||
|
|
||
| [[urlMappings]] | ||
| == URL mappings | ||
|
|
||
| [source,groovy] | ||
| .grails-app/controllers/example/UrlMappings.groovy | ||
| ---- | ||
| include::../snippets/grails-app/controllers/example/UrlMappings.groovy[] | ||
| ---- | ||
|
|
||
| [[recordLabelController]] | ||
| == RecordLabelController | ||
|
|
||
| The REST controller delegates persistence to GORM and returns JSON views: | ||
|
|
||
| [source,groovy] | ||
| .grails-app/controllers/example/RecordLabelController.groovy | ||
| ---- | ||
| include::../snippets/grails-app/controllers/example/RecordLabelController.groovy[] | ||
| ---- | ||
|
|
||
| Call `validate()` after binding request JSON and before `save`. That returns structured `422` responses for constraint violations. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| Clone the repository and run the starting application: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| git clone -b grails8 https://github.com/grails-guides/grails-http-client.git | ||
| cd grails-http-client/initial | ||
| ./gradlew bootRun | ||
| ---- | ||
|
|
||
| Open http://localhost:8080/[http://localhost:8080/] for the welcome JSON payload. The `initial/` project is a vanilla Grails 8 REST API starter with no HTTP client yet. | ||
|
|
||
| To skip ahead, `cd ../complete` and run the same commands - that tree contains the finished `@HttpExchange` client, services, and tests. | ||
|
|
||
| [[cloneAndRun]] | ||
| == Verify tests | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| ./gradlew test | ||
| ---- | ||
|
|
||
| Both `initial` and `complete` must pass in CI (see `.github/workflows/grails8.yml`). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| include::{commondir}/common-helpWithGrails.adoc[] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| Register HTTP service interfaces with `@ImportHttpServices` and explicitly import the client configuration: | ||
|
|
||
| [source,groovy] | ||
| .grails-app/init/example/Application.groovy | ||
| ---- | ||
| include::../snippets/grails-app/init/example/Application.groovy[] | ||
| ---- | ||
|
|
||
| `@ImportHttpServices` scans `example` for `@HttpExchange` interfaces and registers a `RestClient`-backed proxy bean for each one. `@Import(ItunesClientConfiguration)` ensures Grails loads the `RestClient` group configurer. This requires the Grails 8 Spring 7 / Boot 4 baseline. | ||
|
|
||
| [[itunesClient]] | ||
| == ItunesClient | ||
|
|
||
| Declare the iTunes Search API client as a Spring HTTP service interface: | ||
|
|
||
| [source,groovy] | ||
| .src/main/groovy/example/ItunesClient.groovy | ||
| ---- | ||
| include::../snippets/src/main/groovy/example/ItunesClient.groovy[] | ||
| ---- | ||
|
|
||
| Configure the `RestClient` base URL from `itunes.base-url` (defaults to the public iTunes API; integration tests override it with MockWebServer). The iTunes API returns JSON with a `text/javascript` content type, so add that media type to Spring's Jackson converter: | ||
|
|
||
| [source,groovy] | ||
| .src/main/groovy/example/ItunesClientConfiguration.groovy | ||
| ---- | ||
| include::../snippets/src/main/groovy/example/ItunesClientConfiguration.groovy[] | ||
| ---- | ||
|
|
||
| [[dtos]] | ||
| == Response DTOs | ||
|
|
||
| Map the JSON response with simple POGOs: | ||
|
|
||
| [source,groovy] | ||
| .src/main/groovy/example/Album.groovy | ||
| ---- | ||
| include::../snippets/src/main/groovy/example/Album.groovy[] | ||
| ---- | ||
|
|
||
| [source,groovy] | ||
| .src/main/groovy/example/SearchResult.groovy | ||
| ---- | ||
| include::../snippets/src/main/groovy/example/SearchResult.groovy[] | ||
| ---- | ||
|
|
||
| With `spring-boot-starter-json` on the classpath and `text/javascript` enabled above, Spring deserializes the iTunes JSON into these types automatically. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Learn how to call external REST APIs from a Grails 8 application using Spring Framework HTTP Services: define a `@HttpExchange` interface, register it with `@ImportHttpServices`, inject the generated client into a Grails service, and expose results through JSON views. Local `RecordLabel` data stays in GORM/PostgreSQL; album metadata comes from the iTunes Search API. | ||
|
|
||
| No Micronaut plugin is required - this uses the same Spring stack Grails 8 already runs on. Add `spring-boot-starter-json` so the HTTP client can deserialize JSON responses. | ||
|
|
||
| This guide follows the standard Grails guides layout: work in the `initial/` project and compare your progress with `complete/`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| * Approximately 45 minutes | ||
| * JDK 21 (Apache Grails 8 requires Java 21) | ||
| * A https://www.grails.org/[Grails] installation or the bundled Gradle wrapper in `initial/` and `complete/` | ||
| * **Docker** - required for integration tests (Testcontainers PostgreSQL) | ||
| * **PostgreSQL** on `localhost:5432` - required for `./gradlew bootRun` in both `initial/` and `complete/` (default database `devDb`; see `grails-app/conf/application.yml`) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| From the repository root: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| cd complete | ||
| ./gradlew bootRun | ||
| ---- | ||
|
|
||
| Example endpoints: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| curl -s http://localhost:8080/api/recordLabels | ||
| curl -s 'http://localhost:8080/api/search?q=U2' | ||
| ---- | ||
|
|
||
| The search endpoint returns album metadata from the iTunes Search API. Record labels are served from your local PostgreSQL database. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| Inject the `@HttpExchange` client into a Grails service: | ||
|
|
||
| [source,groovy] | ||
| .grails-app/services/example/ItunesSearchService.groovy | ||
| ---- | ||
| include::../snippets/grails-app/services/example/ItunesSearchService.groovy[] | ||
| ---- | ||
|
|
||
| Expose search results through a thin controller: | ||
|
|
||
| [source,groovy] | ||
| .grails-app/controllers/example/SearchController.groovy | ||
| ---- | ||
| include::../snippets/grails-app/controllers/example/SearchController.groovy[] | ||
| ---- | ||
|
|
||
| The service trims blank search terms and returns an empty list rather than calling the remote API with invalid input. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| You added a declarative HTTP client to a Grails 8 REST API: | ||
|
|
||
| * `@ImportHttpServices` and a `@HttpExchange` interface for the iTunes Search API | ||
| * A Grails service that injects the generated client | ||
| * Local GORM data alongside external API results | ||
| * Spock unit and integration tests | ||
|
|
||
| Next steps: add error handling for remote API failures, cache search results, or secure outbound calls with API keys. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| [[unitTests]] | ||
| == Unit tests | ||
|
|
||
| Domain constraints: | ||
|
|
||
| [source,groovy] | ||
| .src/test/groovy/example/RecordLabelSpec.groovy | ||
| ---- | ||
| include::../snippets/src/test/groovy/example/RecordLabelSpec.groovy[] | ||
| ---- | ||
|
|
||
| Service delegation to the HTTP client with a mock: | ||
|
|
||
| [source,groovy] | ||
| .src/test/groovy/example/ItunesSearchServiceSpec.groovy | ||
| ---- | ||
| include::../snippets/src/test/groovy/example/ItunesSearchServiceSpec.groovy[] | ||
| ---- | ||
|
|
||
| [[integrationTests]] | ||
| == Integration tests | ||
|
|
||
| Verify the HTTP client is registered as a Spring bean, encodes query values, accepts the iTunes API's `text/javascript` response, and deserializes albums: | ||
|
|
||
| [source,groovy] | ||
| .src/integration-test/groovy/example/ItunesClientIntegrationSpec.groovy | ||
| ---- | ||
| include::../snippets/src/integration-test/groovy/example/ItunesClientIntegrationSpec.groovy[] | ||
| ---- | ||
|
|
||
| Assert GORM persistence against real PostgreSQL (Testcontainers): | ||
|
|
||
| [source,groovy] | ||
| .src/integration-test/groovy/example/RecordLabelIntegrationSpec.groovy | ||
| ---- | ||
| include::../snippets/src/integration-test/groovy/example/RecordLabelIntegrationSpec.groovy[] | ||
| ---- | ||
|
|
||
| Run unit tests (from the repository root): | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| cd complete | ||
|
sanjana2505006 marked this conversation as resolved.
|
||
| ./gradlew test | ||
| ---- | ||
|
|
||
| Run integration tests (requires Docker): | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| ./gradlew integrationTest | ||
| ---- | ||
56 changes: 56 additions & 0 deletions
56
...rails-http-client/v8/snippets/grails-app/controllers/example/RecordLabelController.groovy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package example | ||
|
|
||
| import grails.gorm.transactions.Transactional | ||
|
|
||
| class RecordLabelController { | ||
|
|
||
| static responseFormats = ['json'] | ||
| static allowedMethods = [index: 'GET', show: 'GET', save: 'POST', update: 'PUT', delete: 'DELETE'] | ||
|
|
||
| def index(Integer max) { | ||
| params.max = Math.min(max ?: 10, 100) | ||
| respond RecordLabel.list(params), model: [recordLabelCount: RecordLabel.count()] | ||
| } | ||
|
|
||
| def show(Long id) { | ||
| respond RecordLabel.get(id) | ||
| } | ||
|
|
||
| @Transactional | ||
| def save() { | ||
| def recordLabel = new RecordLabel(request.JSON as Map) | ||
| if (!recordLabel.validate()) { | ||
| respond recordLabel.errors, status: 422 | ||
| return | ||
| } | ||
| recordLabel.save(failOnError: true, flush: true) | ||
| respond recordLabel, status: 201 | ||
| } | ||
|
|
||
| @Transactional | ||
| def update(Long id) { | ||
| def recordLabel = RecordLabel.get(id) | ||
| if (!recordLabel) { | ||
| render status: 404 | ||
| return | ||
| } | ||
| recordLabel.properties = request.JSON | ||
| if (!recordLabel.validate()) { | ||
| respond recordLabel.errors, status: 422 | ||
| return | ||
| } | ||
| recordLabel.save(failOnError: true, flush: true) | ||
| respond recordLabel | ||
| } | ||
|
|
||
| @Transactional | ||
| def delete(Long id) { | ||
| def recordLabel = RecordLabel.get(id) | ||
| if (!recordLabel) { | ||
| render status: 404 | ||
| return | ||
| } | ||
| recordLabel.delete(flush: true) | ||
| render status: 204 | ||
| } | ||
| } |
21 changes: 21 additions & 0 deletions
21
guides/grails-http-client/v8/snippets/grails-app/controllers/example/SearchController.groovy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package example | ||
|
|
||
| import groovy.transform.CompileStatic | ||
|
|
||
| @CompileStatic | ||
| class SearchController { | ||
|
|
||
| static responseFormats = ['json'] | ||
| static allowedMethods = [index: 'GET'] | ||
|
|
||
| ItunesSearchService itunesSearchService | ||
|
|
||
| def index(String q) { | ||
| if (!q?.trim()) { | ||
| respond([searchTerm: q, albums: []]) | ||
| return | ||
| } | ||
| List<Album> albums = itunesSearchService.searchAlbums(q) | ||
| respond([searchTerm: q.trim(), albums: albums]) | ||
| } | ||
| } |
19 changes: 19 additions & 0 deletions
19
guides/grails-http-client/v8/snippets/grails-app/controllers/example/UrlMappings.groovy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package example | ||
|
|
||
| class UrlMappings { | ||
|
|
||
| static mappings = { | ||
| "/$controller/$action?/$id?(.$format)?"{ | ||
| constraints { | ||
| // apply constraints here | ||
| } | ||
| } | ||
|
|
||
| "/api/search"(controller: 'search', action: 'index') | ||
| "/api/recordLabels"(resources: 'recordLabel') | ||
|
|
||
| "/"(controller: 'application', action: 'index') | ||
| "500"(view: '/error') | ||
| "404"(view: '/notFound') | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.