Skip to content

Server Integration

Viaduct is a pure GraphQL execution engine. It does not include an HTTP server, a message broker client, or any other transport layer. Its contract is simple: call execute with an instance of ExecutionInput and get back an ExecutionResult.

// Build the input
val input = ExecutionInput.create(
    operationText = query,
    variables = variables,
    requestContext = yourRequestContext,
)

// Execute
val result = viaduct.execute(input)

// Format result as a GraphQL-spec-compliant JSON object
val responseBody = result.toSpecification()

execute is a Kotlin suspending function — it runs in a coroutine and does not block a thread while resolvers are executing. Use executeAsync if you're not using a coroutine-based server.

Constructing a Viaduct Instance

There are two ways to construct a Viaduct instance: using the simpler BasicViaductFactory and using the more feature-rich ViaductBuilder.

Both of these classes use resource files generated by Viaduct's application and module plugins to configure both the schema served by a Viaduct application and the resolvers that implement that schema. See Getting Started for a basic introduction to these plugins, and Multi-tenancy for more advanced uses.

ViaductBuilder provides other mechanisms for configuration, such as configuration of dependency injection and observability telemetry. These are covered by other subsections of this Service Engineering section.

Request-Scoped Data

The entry point that calls Viaduct is responsible for extracting request-scoped data such as caller identity, auth tokens, locale, and tenant information from the incoming transport request.

For production services, prefer putting this data into framework-managed request-scoped beans and consuming those beans from resolvers through Dependency Injection. This keeps resolver access type-safe and lets the web framework manage request lifetimes.

For simple integrations, ExecutionInput.requestContext can carry deployment-specific context directly into resolver execution. The value is exposed to resolvers as ctx.requestContext and is typed as Any?, so callers and resolvers must agree on the object shape. Values passed this way should be immutable or thread-safe because resolvers for the same operation may execute concurrently.

HTTP Embedding

Getting Started demonstrates how to embed Viaduct into a Ktor HTTP server. Here is how you might embed it into a Micronaut-based one:

@Controller
class ViaductRestController(
    private val viaduct: Viaduct,
) {
    @Post("/graphql")
    suspend fun graphql(
        @Body request: Map<String, Any>,
    ): HttpResponse<Map<String, Any?>> {
        val input = ExecutionInput.create(
            operationText = request["query"] as String,
            variables = (request["variables"] as? Map<String, Any>) ?: emptyMap(),
        )
        val result = viaduct.execute(input, determineSchemaId(scopesHeader))
        return HttpResponse.ok(result.toSpecification())
    }
}

Our Viaduct demo apps website contains integrations of Viaduct with a variety of web servers.

Messaging Embedding

Embedding Viaduct into a message-based transport is largely the same:

class GraphQLMessageConsumer(
    private val viaduct: Viaduct,
) {
    suspend fun onMessage(message: IncomingMessage) {
        val input = ExecutionInput.create(
            operationText = message.query,
            variables = message.variables,
            requestContext = message.headers,
        )
        val result = viaduct.execute(input)
        message.reply(result.toSpecification())
    }
}