Viaduct and ViaductBuilder
This page explains how the Viaduct runtime is built in the Star Wars demo, referencing the configuration code
(for example, ViaductConfiguration.kt) and the controller that executes requests (ViaductGraphQLController.kt).
Goal: make it clear what the builder registers, how schemas are defined, and what the runtime looks like when it receives an
ExecutionInputto resolve queries and mutations.
High-level flow
- Schema registration (IDs, SDL discovery, and scope sets).
- Module registration (generated types, resolvers, and package conventions).
- Runtime construction via
ViaductBuilder. - Execution: the controller creates an
ExecutionInput(withschemaId,query,variables, etc.) and callsviaduct.executeAsync(...).
Builder configuration
This excerpt mirrors what happens in configuration (names and constants from the demo):
Tag not found: viaduct_configuration in demoapps/starwars/src/main/kotlin/com/example/starwars/service/viaduct/ViaductConfiguration.kt
PUBLIC_SCHEMAandPUBLIC_SCHEMA_WITH_EXTRASare schema IDs used by the demo.grtPackagePrefixandgrtResourcesIncludedare optional test-only overrides for GRT schema file discovery (production uses defaults).tenantPackagePrefixtells Viaduct where to discover generated resolver classes.- The builder creates an immutable runtime that the controller will use to execute requests.
Example: executing requests through the controller
The controller resolves scopes → chooses a schema → builds ExecutionInput → executes:
@Controller
class ViaductRestController(
private val viaduct: Viaduct,
private val securityAccessService: SecurityAccessContext
) {
@Post("/graphql")
suspend fun graphql(
@Body request: Map<String, Any>,
@Header(SCOPES_HEADER) scopesHeader: String?,
@Header("security-access") securityAccess: String?
): HttpResponse<Map<String, Any>> {
securityAccessService.setSecurityAccess(securityAccess)
val executionInput = createExecutionInput(request)
// tag::run_query[7] Runs the query example
val scopes = parseScopes(scopesHeader)
val schemaId = determineSchemaId(scopes)
val result = viaduct.executeAsync(executionInput, schemaId).await()
return HttpResponse.status<Map<String, Any>>(statusCode(result)).body(result.toSpecification())
}
For details on
determineSchemaId(scopes)andcreateExecutionInput(...), see the Scope and Schemas documentation in this set.
Builder best practices
- Declare schema IDs and their scope sets explicitly.
- Keep
tenantPackagePrefixaligned with generated resolver code (com.example.starwars...). - Use default GRT schema discovery in production (don’t set
grtPackagePrefixorgrtResourcesIncluded). - Configure directives and modules in the builder when applicable.
- Avoid conditional logic in the builder; route by scope in the controller instead.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.