Complete reference documentation

Closes gh-78
This commit is contained in:
Rossen Stoyanchev
2021-07-05 12:26:54 +01:00
parent 04d6a72eec
commit 553dd3e571

View File

@@ -4,46 +4,74 @@ Brian Clozel; Andreas Marek; Rossen Stoyanchev
:toclevels: 4
:tabsize: 4
:repository: https://github.com/spring-projects/spring-graphql/tree/main
[[web]]
[[overview]]
== Overview
Spring GraphQL provides support for Spring applications built
https://www.graphql-java.com/[GraphQL Java]. It is a joint collaboration between both
teams, and our shared philosophy is to be less opinionated and more focused on
comprehensive and wide-ranging support.
Spring GraphQL is the successor of the
https://github.com/graphql-java/graphql-java-spring[GraphQL Java Spring] project from
the GraphQL Java team. It aims to be the foundation for all Spring, GraphQL applications.
The project is in a milestone phase towards a 1.0 release, currently, and looking for
feedback. Please, use our
https://github.com/spring-projects/spring-graphql/issues[issue tracker] to report an
issue, discuss a design issue, or request a feature.
To get started, please see the <<boot-graphql>> and the <<samples>> sections.
[[web-transports]]
== Web Transports
Spring GraphQL supports GraphQL requests over HTTP and over WebSocket. It comes with a choice
of handlers for Spring MVC and Spring WebFlux applications.
Spring GraphQL supports GraphQL requests over HTTP and over WebSocket.
[[web-http]]
=== HTTP
`GraphQlHttpHandler` classes, in their respective WebMvc and WebFlux sub-packages, provide
handling of GraphQL over HTTP requests and both delegate to a common <<web-interceptor>>
chain for actual handling and query execution.
`GraphQlHttpHandler` handles GraphQL over HTTP requests and delegates to the
<<web-interception>> chain for request execution. There are two variants, one for
Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have
equivalent functionality, but rely on blocking vs non-blocking I/O respectively for
writing the HTTP response.
The HTTP handlers for WebMvc and WebFlux have equivalent functionality. Both perform
asynchronous execution of GraphQL queries, while the WebFlux handler also uses non-blocking
I/O to write to the HTTP response.
Requests should have the HTTP POST method with the query in the request body as defined in the
Requests must use HTTP POST with GraphQL request details included as JSON in the
request body, as defined in the proposed
https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md[GraphQL over HTTP]
spec proposal.
specification. Once the JSON body has been successfully decoded, the HTTP response
status is always 200 (OK), and any errors from GraphQL request execution appear in the
"errors" section of the GraphQL response.
The handlers can be exposed as endpoints by declaring a `RouterFunction` bean and using
the `RouterFunctions`, functional endpoint DSL for WebMvc or WebFlux respectively to
create the mappings. The Boot starter does this by default, see <<boot-graphql-web>> for
details or look in the `GraphQlWebMvcAutoConfiguration` or
`GraphQlWebFluxAutoConfiguration` classes for example config.
`GraphQlHttpHandler` can be exposed as an HTTP endpoint by declaring a `RouterFunction`
bean and using the `RouterFunctions` from Spring MVC or WebFlux to create the route. The
Boot starter does this, see <<boot-graphql-web>> for details or check
`GraphQlWebMvcAutoConfiguration` or `GraphQlWebFluxAutoConfiguration` for example config.
The Spring GraphQL repository contains a Spring MVC
{repository}/samples/webmvc-http[HTTP sample] application.
[[web-websocket]]
=== WebSocket
`GraphQlWebSocketHandler` classes, in their respective WebMvc and WebFlux sub-packages,
support GraphQL over WebSocket requests based on the
`GraphQlWebSocketHandler` handles GraphQL over WebSocket requests based on the
https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md[protocol] defined in the
`graphql-ws` library that also lists a number of
https://github.com/enisdenjo/graphql-ws#recipes[recipes] for use with various clients.
https://github.com/enisdenjo/graphql-ws[graphql-ws] library. The main reason to use
GraphQL over WebSocket is subscriptions which allow sending a stream of GraphQL
responses, but it can also be used for regular queries with a single response.
The handler delegates every request to the <<web-interception>> chain for further
request execution.
[TIP]
.GraphQL Over WebSocket Protocols
@@ -56,30 +84,36 @@ succeeded by the latter. Read this
https://the-guild.dev/blog/graphql-over-websockets[blog post] for the history.
====
The WebSocket handlers for WebMvc and WebFlux have equivalent functionality. Both perform
asynchronous execution of GraphQL queries, but the WebFlux handler also uses non-blocking
I/O and back pressure to stream messages to the WebSocket connection.
There are two variants of `GraphQlWebSocketHandler`, one for Spring MVC and one for
Spring WebFlux. Both handle requests asynchronously and have equivalent functionality.
The WebFlux handler also uses non-blocking I/O and back pressure to stream messages,
which works well since in GraphQL Java a subscription response is a Reactive Streams
`Publisher`.
GraphQL over WebSocket protocol supports the execution both queries and streaming
subscriptions and the <<web-interceptor>> can be used to intercept each query or
subscription request.
The `graphql-ws` project lists a number of
https://github.com/enisdenjo/graphql-ws#recipes[recipes] for client use.
The handlers can be exposed as endpoints by declaring `SimpleUrlHandlerMapping` beans for
WebMvc or WebFlux respectively. The Boot starter provides options to enable and conifgure
all this, see<<boot-graphql-web>> for details or look in the `GraphQlWebMvcAutoConfiguration`
or `GraphQlWebFluxAutoConfiguration` classes for example configuration.
`GraphQlWebSocketHandler` can be exposed as a WebSocket endpoint by declaring a
`SimpleUrlHandlerMapping` bean and using it to map the handler to a URL path. The Boot
starter has options to enable this, see <<boot-graphql-web>> for details or check
`GraphQlWebMvcAutoConfiguration` or `GraphQlWebFluxAutoConfiguration` for example config.
The Spring GraphQL repository contains a WebFlux
{repository}/samples/webflux-websocket[WebSocket sample] application.
[[web-interceptor]]
=== `WebInterceptor` API
[[web-interception]]
=== Web Interception
Web transport handlers for <<web-http>> and for <<web-websocket>> delegate to a
`WebGraphQlHandler` that represents a chain of `WebInterceptor` components, followed by a
`GraphQlSource` that actually invokes the GraphQL Java engine.
<<web-http>> and <<web-websocket>> transport handlers delegate to a common Web
interception chain for request execution. The chain consists of a sequence of
`WebInterceptor` components, followed by a `GraphQlService` that invokes the GraphQL
Java engine.
A `WebInterceptor` can be used to examine HTTP request input and potentially change the
`ExecutionInput` passed to `graphql.GraphQL`:
`WebInteceptor` is as a common contract to use in both Spring MVC and WebFlux
applications. Use it to intercept requests, inspect HTTP request headers, or to register a
transformation of the `graphql.ExecutionInput`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -96,8 +130,8 @@ class MyInterceptor implements WebInterceptor {
}
----
A `WebInterceptor` can be used to inspect and potentially modify the `ExecutionResult`
or add an HTTP response header:
Use `WebInterceptor` also to intercept responses, add HTTP response headers, or transform
the `graphql.ExecutionResult`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -115,57 +149,184 @@ class MyInterceptor implements WebInterceptor {
}
----
`WebGraphQlHandler` provides a builder to assemble the processing chain given a
a set of `WebInterceptor` components and a `GraphQlSource`. This handler is then passed
to one of the web transport handlers. The Boot starter does all this by detecting beans
of type `WebInterceptor` and using them to build the processing chain, see
<<boot-graphql-web>> for details, or look in the `GraphQlWebMvcAutoConfiguration` or
`GraphQlWebFluxAutoConfiguration` classes for example configuration.
`WebGraphQlHandler` provides a builder to initialize the Web interception chain. After
you build the chain, you can use the resulting `WebGraphQlHandler` to initialize the HTTP
or WebSocket transport handlers. The Boot starter configures all this,
see <<boot-graphql-web>> for details, or check `GraphQlWebMvcAutoConfiguration` or
`GraphQlWebFluxAutoConfiguration` for example config.
[[execution]]
== Query Execution
== Query Execution
TODO...
`GraphQlService` is the main Spring GraphQL abstraction to call GraphQL Java to execute
requests. Underlying transports, such as the <<web-transports>>, delegate to `GraphQlService` to
handle requests.
The main implementation, `ExecutionGraphQlService`, is a thin facade around the
invocation of `graphql.GraphQL`. It is configured with a `GraphQlSource` for access to
the `graphql.GraphQL` instance.
[[execution-configuring]]
=== Configuring the GraphQL Engine
TODO...
[[execution-graphqlsource]]
=== `GraphQLSource`
`GraphQlSource` is a core Spring GraphQL abstraction for access to the
`graphql.GraphQL` instance to use for request execution. It provides a builder API to
initialize GraphQL Java and build a `GraphQlSource`.
The default `GraphQlSource` builder, accessible via `GraphQlSource.builder()`, enables
support for <<execution-reactive-datafetcher>>, <<execution-context>>, and
<<execution-exceptions>>.
[[execution-datafetcher]]
=== `DataFetcher` Support
TODO...
[[execution-reactive-datafetcher]]
=== Reactive `DataFetcher`
The default `GraphQlSource` builder enables support for a `DataFetcher` to return `Mono`
or `Flux`. Both return types are adapted to a `CompletableFuture` with `Flux` values
aggregated and turned into a List, unless the request is a GraphQL subscription request,
in which case the return value remains a Reactive Streams `Publisher` for streaming
GraphQL responses.
A reactive `DataFetcher` can rely on access to Reactor context propagated from the
transport layer, such as from a WebFlux request handling, see
<<execution-context-webflux, WebFlux Context>>.
[[execution-context]]
=== Context Management
=== Context Propagation
Spring GraphQL provides support to transparently propagate context from the <<web-transports>>,
through the GraphQL engine, and to `DataFetcher` and other components it invokes.
This includes both `ThreadLocal` context from the Spring MVC request handling thread and
Reactor `Context` from the WebFlux processing pipeline.
[[execution-context-webmvc]]
==== WebMvc
A `DataFetcher` and other components invoked by GraphQL Java may not always execute on
the same thread as the Spring MVC handler, for example if an asynchronous
<<web-interception, `WebInterceptor`>> or `DataFetcher` switches to a different thread.
Spring GraphQL supports propagating `ThreadLocal` values from the Servlet container
thread to the thread a `DataFetcher` and other components invoked by the GraphQL engine
execute on. To do this, an application needs to create a `ThreadLocalAccessor` to extract
`ThreadLocal` values of interest:
[source,java,indent=0,subs="verbatim,quotes"]
----
public class RequestAttributesAccessor implements ThreadLocalAccessor {
private static final String KEY = RequestAttributesAccessor.class.getName();
@Override
public void extractValues(Map<String, Object> container) {
container.put(KEY, RequestContextHolder.getRequestAttributes());
}
@Override
public void restoreValues(Map<String, Object> values) {
if (values.containsKey(KEY)) {
RequestContextHolder.setRequestAttributes((RequestAttributes) values.get(KEY));
}
}
@Override
public void resetValues(Map<String, Object> values) {
RequestContextHolder.resetRequestAttributes();
}
}
----
A `ThreadLocalAccessor` can be registered in the <<web-interception,WebGraphHandler>>
builder. The Boot starter detects beans of this type and automatically registers them for
Spring MVC application, see <<boot-graphql-web>>.
[[execution-context-webflux]]
==== WebFlux
A <<execution-reactive-datafetcher>> can rely on access to Reactor context that
originates from the WebFlux request handling chain. This includes Reactor context
added by <<web-interception, WebInterceptor>> components.
TODO...
[[execution-exceptions]]
=== Exception Resolution
TODO...
GraphQL Java applications can register a `DataFetcherExceptionHandler` to decide how to
represent exceptions from the data layer in the "errors" section of the GraphQL response.
Spring GraphQL has a built-in `DataFetcherExceptionHandler` that is configured for use
by the <<execution-graphqlsource>> builder. It enables applications to register one or
more Spring `DataFetcherExceptionResolver` components that are invoked sequentially
until one resolves the `Exception` to a list of `graphql.GraphQLError` objects.
A `GraphQLError` can be assigned an `graphql.ErrorClassification`. Spring GraphQL
defines an `ErrorType` enum with common, error classification categories:
- `BAD_REQUEST`
- `UNAUTHORIZED`
- `FORBIDDEN`
- `NOT_FOUND`
- `INTERNAL_ERROR`
Applications can use this to classify errors. If an error remains unresolved, by
default it is marked as `INTERNAL_ERROR`.
[[data]]
== Data Integrations
TODO...
== Data Integration
[[data-querydsl]]
=== QueryDsl
=== Querydsl
TODO...
Spring GraphQL supports use of http://www.querydsl.com/[Querydsl] to fetch data through
the Spring Data
https://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.extensions[Querydsl extension].
For example, declare a repository as `QuerydslPredicateExecutor`:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface AccountRepository extends QuerydslPredicateExecutor<Account> {
}
----
Then use it to create a `DataFetcher`:
[source,java,indent=0,subs="verbatim,quotes"]
----
// For single result queries
DataFetcher<Account> dataFetcher =
QuerydslDataFetcher.builder(repository).single();
// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
QuerydslDataFetcher.builder(repository).many();
----
The `DataFetcher` builds a Querydsl `Predicate` from GraphQL request parameters, and
uses it to fetch data. Spring Data supports `QuerydslPredicateExecutor` for JPA,
MongoDB, and LDAP.
If the repository is `ReactiveQuerydslPredicateExecutor`, the builder returns
`DataFetcher<Mono<Account>>` or `DataFetcher<Flux<Account>>`. Spring Data supports this
variant for MongoDB.
The {repository}/samples/webmvc-http[webmvc-http] sample in the Spring GraphQL repository
uses Querydsl to fetch `artifactRepositories`.
@@ -173,7 +334,19 @@ TODO...
[[data-security]]
== Security
TODO...
The path to a <<web-transports, Web>> GraphQL endpoint can be secured with HTTP
URL security to ensure that only authenticated users can access it. This does not,
however, differentiate among different GraphQL requests on such a shared endpoint on
a single URL.
To apply more fine-grained security, add Spring Security annotations such as
`@PreAuthorize` or `@Secured` to service methods involved in fetching specific parts of
the GraphQL response. This should work due to <<execution-context>> that aims to make
Security, and other context, available at the data fetching level.
The Spring GraphQL repository contains samples for
{repository}/samples/webmvc-http-security[Spring MVC] and for
{repository}/samples/webflux-http-security[WebFlux].
@@ -181,15 +354,231 @@ TODO...
[[testing]]
== Testing
TODO...
You can test GraphQL requests using Spring's `WebTestClient`, just send and receive
JSON, but a number of GraphQL specific details make this approach more cumbersome than it
should be.
[[testing-graphqltester]]
=== `GraphQlTester`
`GraphQlTester` defines a workflow to test GraphQL requests with the following benefits:
- Verify GraphQL responses are 200 (OK).
- Verify no unexpected errors under the "errors" key in the response.
- Decode under the "data" key in the response.
- Use JsonPath to decode different parts of the response.
- Test subscriptions.
To create `GraphQlTester`, you only need a `GraphQlService`, and no transport:
[source,java,indent=0,subs="verbatim,quotes"]
----
GraphQlSource graphQlSource = GraphQlSource.builder()
.schemaResources(...)
.runtimeWiring(...)
.build();
GraphQlService graphQlService = new ExecutionGraphQlService(graphQlSource);
GraphQlTester graphQlTester = GraphQlTester.builder(graphQlService).build();
----
[[testing-webgraphqltester]]
=== `WebGraphQlTester`
`WebGraphQlTester` extends `GraphQlTester` to add a workflow and configuration specific
to <<web-transports>>. You need one of the following inputs to create it:
- `WebTestClient` -- perform requests as an HTTP client, either against <<web-http>>
handlers without a server, or against a live server.
- `WebGraphQlHandler` -- perform requests through the <<web-interception>> chain used
by both <<web-http>> and <<web-websocket>> handlers, which in effect is testing without
a Web framework. One reason to use this is for <<testing-subscriptions>>.
For Spring WebFlux without a server, you can point to your Spring configuration:
[source,java,indent=0,subs="verbatim,quotes"]
----
ApplicationContext context = ... ;
WebTestClient client =
WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
----
For Spring MVC without a server, use the `MockMvcWebTestClient` builder:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebApplicationContext context = ... ;
WebTestClient client =
MockMvcWebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
----
For tests against a live, running server:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebTestClient client =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
----
[[testing-queries]]
=== Queries
Below is an example query test using
https://github.com/json-path/JsonPath[JsonPath] to extract all release versions in the
GraphQL response.
[source,java,indent=0,subs="verbatim,quotes"]
----
String query = "{" +
" project(slug:\"spring-framework\") {" +
" releases {" +
" version" +
" }"+
" }" +
"}";
graphQlTester.query(query)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
----
The JsonPath is relative to the "data" section of the response.
[[testing-errors]]
=== Errors
Tests cannot use verify data, if there are errors under the "errors" key in the response
has errors. If necessary to ignore an error, use an error filter `Predicate`:
[source,java,indent=0,subs="verbatim,quotes"]
----
graphQlTester.query(query)
.execute()
.errors()
.filter(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
----
An error filter can be registered globally and apply to all tests:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
.errorFilter(error -> ...)
.build();
----
Or inspect all errors directly and that also marks them as filtered:
[source,java,indent=0,subs="verbatim,quotes"]
----
graphQlTester.query(query)
.execute()
.errors()
.satisfy(errors -> {
// ...
});
----
If a request does not have any response data (e.g. mutation), use `executeAndVerify`
instead of `execute` to verify there are no errors in the response:
[source,java,indent=0,subs="verbatim,quotes"]
----
graphQlTester.query(query).executeAndVerify();
----
[[testing-subscriptions]]
=== Subscriptions
The `executeSubscription` method defines a workflow specific to subscriptions which return
a stream of responses instead of a single response.
To test subscriptions, you can create `GraphQlTester` with a `GraphQlService`, which
calls `graphql.GraphQL` directly and that returns a stream of responses:
[source,java,indent=0,subs="verbatim,quotes"]
----
GraphQlService service = ... ;
GraphQlTester graphQlTester = GraphQlTester.builder(service).build();
Flux<String> result = graphQlTester.query("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode each response
----
The `StepVerifier` from Project Reactor is useful to verify a stream:
[source,java,indent=0,subs="verbatim,quotes"]
----
Flux<String> result = graphQlTester.query("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class);
StepVerifier.create(result)
.expectNext("Hi")
.expectNext("Bonjour")
.expectNext("Hola")
.verifyComplete();
----
To test with the <<web-interception>> chain, you can create `WebGraphQlTester` with a
`WebGraphQlHandler`:
[source,java,indent=0,subs="verbatim,quotes"]
----
GraphQlService service = ... ;
WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
.interceptor((input, next) -> next.handle(input))
.build();
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler).build();
----
Currently, Spring GraphQL does not support testing with a WebSocket client, and it
cannot be used for integration test of GraphQL over WebSocket requests.
[[boot-graphql]]
== Boot config
== Boot Starter
This project is tested against Spring Boot 2.4+.
This project works on Spring Boot 2.4+.
@@ -217,7 +606,7 @@ GraphQL transports you want to use:
|===
In the generated project, add the starter `graphql-spring-boot-starter` manually:
In the generated project, add `graphql-spring-boot-starter` manually:
[source,groovy,indent=0,subs="verbatim,quotes",role="primary"]
.Gradle
@@ -270,18 +659,18 @@ repositories {
----
[NOTE]
.GraphQL Spring Boot Starter Group Id
.Boot Starter Group Id
====
The starter is scheduled to move from the Spring GraphQL repository to the Spring Boot
repository, after Spring Boot 2.6 is released. The starter group id will then change
from `org.springframework.experimental` to `org.springframework.boot` and will be
released in Spring Boot 2.7 building on Spring GraphQL 1.0.
The Boot starter will move from the Spring GraphQL repository to the Spring Boot
repository, after Spring Boot 2.6 is released. The group id for the starter will then
change from `org.springframework.experimental` to `org.springframework.boot` and will be
released in Spring Boot 2.7.
====
[[boot-graphql-schema]]
=== GraphQL Schema
=== Schema
By default, GraphQL schema files are expected to be in `src/main/resources/graphql` and have
the extension ".graphqls", ".graphql", ".gql", or ".gqls". You can customize the
@@ -328,7 +717,7 @@ public class PersonDataWiring implements RuntimeWiringCustomizer {
[[boot-graphql-web]]
=== Web Transports
=== Web Endpoints
The GraphQL HTTP endpoint is at HTTP POST "/graphql" by default. The path can be customized:
@@ -348,20 +737,24 @@ spring.graphql.websocket.path=/graphql
spring.graphql.websocket.connection-init-timeout=60s
----
The GraphQL WebSocket endpoint is not enabled by default. To enable it:
The GraphQL WebSocket endpoint is off by default. To enable it:
- For a Servlet application, add the WebSocket starter `spring-boot-starter-websocket`.
- For a WebFlux application, set the `spring.graphql.websocket.path` application property.
`WebInterceptor` beans declared in Spring configuration are detected and registered to
intercept for both GraphQL requests over HTTP and over WebSocket.
Declare a `WebInterceptor` bean to have it registered in the<<web-interception>> for
GraphQL over HTTP and WebSocket requests.
Declare a `ThreadLocalAccessor` bean to assist with the propagation of `ThreadLocal`
values of interest in <<execution-context-webmvc>>.
[[boot-graphql-graphiql]]
=== GraphiQL Page
=== GraphiQL
The Spring Boot starter includes a https://github.com/graphql/graphiql[GraphiQL] page
that is exposed at "/graphiql" by default. You can configure that as follows:
that is exposed at "/graphiql" by default. You can configure this as follows:
[source,properties,indent=0,subs="verbatim,quotes"]
----
@@ -393,7 +786,7 @@ management.endpoints.web.exposure.include=health,metrics,info
[[boot-graphql-metrics-request-timer]]
==== GraphQL Request Timer
==== Request Timer
A Request metric timer is available at `/actuator/metrics/graphql.request`.
@@ -408,7 +801,7 @@ A Request metric timer is available at `/actuator/metrics/graphql.request`.
[[boot-graphql-metrics-datafetcher-timer]]
==== GraphQL `DataFetcher` Timer
==== `DataFetcher` Timer
A `DataFetcher` metric timer is available at `/actuator/metrics/graphql.datafetcher`.
@@ -427,7 +820,7 @@ A `DataFetcher` metric timer is available at `/actuator/metrics/graphql.datafetc
[[boot-graphql-metrics-error-counter]]
==== GraphQL Error Counter
==== Error Counter
A GraphQL error metric counter is available at `/actuator/metrics/graphql.error`.
@@ -498,7 +891,7 @@ public class MockMvcGraphQlTests {
}
----
Subscriptions can be tested without a WebSocket layer as shown below:
Subscriptions can be tested without WebSocket as shown below:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -536,8 +929,8 @@ chain and then calls GraphQL Java which returns a Reactive Streams `Publisher`.
[[samples]]
== Samples
This Spring GraphQL repository contains
https://github.com/spring-projects/spring-graphql/tree/main/samples[sample applications] for various scenarios.
This Spring GraphQL repository contains {repository}/samples[sample applications] for
various scenarios.
You can run those by cloning this repository and running main application classes from
your IDE or by typing the following on the command line: