Documentation Index
Fetch the complete documentation index at: https://mintlify.com/airbnb/viaduct/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Micronaut Starter demonstrates how to integrate Viaduct with Micronaut’s powerful dependency injection (DI) framework. This tutorial is essential for building production-ready applications where resolvers need access to services, repositories, and other dependencies.What You’ll Learn
- Integrating Viaduct with Micronaut’s DI container
- Using dependency injection in GraphQL resolvers
- Separating development and production code with Gradle source sets
- Fast development mode with ViaductServer
- Production-ready configuration without the development server
Prerequisites
- Java JDK 21 installed
JAVA_HOMEenvironment variable set correctly orjavain your PATH- Understanding of dependency injection concepts
Project Structure
Key Concepts
Production vs Development
Production Build:- Includes only
src/main/kotlin(production code) - Does NOT include
viaduct-servedependency - Suitable for deployment with a full HTTP server
- Includes both
src/main/kotlinandsrc/dev/kotlin - Includes
viaduct-servedependency for GraphiQL - Fast iteration with automatic schema reloading
package com.example.viadapp.production
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import viaduct.service.BasicViaductFactory
import viaduct.service.TenantRegistrationInfo
import viaduct.service.api.Viaduct
@Factory
class ViaductConfiguration(
private val micronautTenantCodeInjector: MicronautTenantCodeInjector
) {
@Bean
fun providesViaduct(): Viaduct {
return BasicViaductFactory.create(
tenantRegistrationInfo = TenantRegistrationInfo(
tenantPackagePrefix = "com.example.viadapp",
tenantCodeInjector = micronautTenantCodeInjector
)
)
}
}
@Factory marks this class as a bean factory@Bean method provides the Viaduct instancetenantCodeInjector enables DI for resolver classesMicronautTenantCodeInjector@Singleton
class MicronautTenantCodeInjector(
private val beanContext: BeanContext
) : TenantCodeInjector {
override fun <T : Any> getInstance(clazz: KClass<T>): T {
return beanContext.getBean(clazz.java)
}
}
import jakarta.inject.Singleton
@Singleton
class GreetingService {
fun getGreeting(): String = "Hello from Micronaut!"
}
@Resolver
@Singleton
class GreetingResolver(
private val greetingService: GreetingService // Injected dependency
) : QueryResolvers.Greeting() {
override suspend fun resolve(ctx: Context): String {
return greetingService.getGreeting()
}
}
@Singletonimport viaduct.serve.ViaductServerConfiguration
import io.micronaut.context.ApplicationContext
@ViaductServerConfiguration
class MicronautViaductFactory : ViaductFactory {
override fun create(): Viaduct {
// Start only the Micronaut DI container (not the full HTTP server)
val context = ApplicationContext.run()
// Get the Viaduct instance from DI
return context.getBean(Viaduct::class.java)
}
}
@ViaductServerConfigurationApplicationContext (DI only, no HTTP server)Viaduct bean from the containerhttp://localhost:8080sourceSets {
// Development-only source set
create("dev") {
kotlin.srcDir("src/dev/kotlin")
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}
}
// Dev configuration extends from main
val devImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
dependencies {
// Production dependencies
implementation("io.micronaut:micronaut-inject")
implementation("io.micronaut:micronaut-context")
// Development-only dependency
devImplementation("com.airbnb.viaduct:viaduct-serve")
}
// Serve task includes dev classes
tasks.named<JavaExec>("serve") {
classpath += sourceSets["dev"].output
classpath += sourceSets["dev"].runtimeClasspath
}
dev source set is separate from maindevImplementation dependencies are excluded from production buildsserve task explicitly includes dev classes and dependencieshttp://localhost:8080/graphiqlHow Dependency Injection Works
Resolver Instantiation Flow
- Query Execution: Viaduct needs a resolver instance
- DI Request: Calls
tenantCodeInjector.getInstance(ResolverClass::class) - Micronaut Resolution:
MicronautTenantCodeInjectorasksBeanContextfor the bean - Dependency Injection: Micronaut creates the resolver and injects its dependencies
- Caching: The instance is cached by Micronaut’s singleton scope
Injecting Complex Dependencies
You can inject any Micronaut bean into resolvers:Development Workflow
Fast Iteration Cycle
- Start development server:
./gradlew --continuous serve - Modify resolvers or schema
- Gradle auto-recompiles
- Refresh GraphiQL to test changes
Production Deployment
- Build production JAR:
./gradlew build - Deploy to your server
- Run with Micronaut HTTP server:
Why Use Micronaut with Viaduct?
- Dependency Injection: Resolvers can access databases, services, and external APIs
- Compile-Time DI: Micronaut performs DI at compile time (no reflection)
- Fast Startup: Minimal overhead compared to Spring
- Cloud-Native: Built for microservices and serverless
- Clean Separation: Development tools don’t bloat production artifacts
Next Steps
- Review the Star Wars Tutorial which also uses Micronaut DI
- Learn about Custom Context for request-scoped data
- Study Batch Resolvers with injected dependencies
- Read about Testing with Micronaut test support