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):
@Configuration
@Import(ViaductResolverRegistrar::class)
class ViaductConfiguration(
private val codeInjector: SpringTenantCodeInjector
) {
@Bean
fun viaduct(): Viaduct {
/**
* The BasicViaductFactory is a utility to create a Viaduct instance with minimal configuration.
*/
return BasicViaductFactory.create(
/**
* StarWars application defines two scoped schemas:
*
* 1. PUBLIC_SCHEMA: the base schema with only the default scope
* 2. PUBLIC_SCHEMA_WITH_EXTRAS: the base schema with the extras header
*/
// tag::schema_registration[8] Schema registration
schemaRegistrationInfo = SchemaRegistrationInfo(
scopes = listOf(
DEFAULT_SCHEMA_ID.toSchemaScopeInfo(),
EXTRAS_SCHEMA_ID.toSchemaScopeInfo(),
),
packagePrefix = "com.example.starwars", // Scan the entire com.example.starwars package for graphqls resources
resourcesIncluded = ".*\\.graphqls"
),
/**
* Tenant registration info is required to let Viaduct discover tenant-specific code such as resolvers.
*
* In this configuration, we specify to scan for tenant code in the com.example.starwars package.
*/
tenantRegistrationInfo = TenantRegistrationInfo(
tenantPackagePrefix = "com.example.starwars", // Scan the entire com.example.starwars package for tenant-specific code
tenantCodeInjector = codeInjector
)
)
}
PUBLIC_SCHEMAandPUBLIC_SCHEMA_WITH_EXTRASare schema IDs used by the demo.packagePrefixandresourcesIncludedtells Viaduct where to discover SDL and generated types.- 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:
@RestController
class ViaductRestController {
@Autowired
lateinit var viaduct: Viaduct
@PostMapping("/graphql")
suspend fun graphql(
@RequestBody request: Map<String, Any>,
@RequestHeader headers: HttpHeaders
): ResponseEntity<Map<String, Any>> {
val executionInput = createExecutionInput(request)
// tag::run_query[7] Runs the query example
val scopes = parseScopes(headers)
val schemaId = determineSchemaId(scopes)
val result = viaduct.executeAsync(executionInput, schemaId).await()
return ResponseEntity.status(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
packagePrefixaligned with generated code (com.example.starwars...). - 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.