Skip to content

From Scratch

This guide walks you through creating a Viaduct application from the ground up, giving you a deeper understanding of how Viaduct projects are structured.

Before you begin: Review the Compatibility section to choose appropriate Gradle and Kotlin versions for your project.

Getting Started

We'll build a simple CLI application that demonstrates the core concepts of Viaduct. This will be a single-module project where both the Viaduct application and schema are located inside the src directory.

Project Setup

Create a new directory for your project and navigate into it:

mkdir viaduct-hello-world
cd viaduct-hello-world

Configuring Gradle

Create a settings.gradle.kts file with the following content:

pluginManagement {
   repositories {
       mavenCentral()
       gradlePluginPortal()
   }
}

dependencyResolutionManagement {
   repositories {
       mavenCentral()
   }
   versionCatalogs {
       create("libs")
   }
}

You'll need to create a gradle/libs.versions.toml file:

[versions]
viaduct = "0.7.0"

[plugins]
viaduct-application = { id = "com.airbnb.viaduct.application-gradle-plugin", version.ref = "viaduct" }
viaduct-module = { id = "com.airbnb.viaduct.module-gradle-plugin", version.ref = "viaduct" }

Create a build.gradle.kts file. The key requirement is to include both Viaduct plugins:

// tag::plugins-config[7] How plugins for viaduct are setup.
plugins {
    alias(libs.plugins.kotlinJvm)
    alias(libs.plugins.viaduct.application)
    alias(libs.plugins.viaduct.module)
    application
}

viaductApplication {
    modulePackagePrefix.set("com.example.viadapp")
}

viaductModule {
    modulePackageSuffix.set("resolvers")
}

dependencies {
    implementation(libs.viaduct.api)
    implementation(libs.viaduct.runtime)

    implementation(libs.logback.classic)
    implementation(libs.kotlinx.coroutines.core)
    implementation(libs.jackson.databind)

    testImplementation(libs.junit.jupiter)
    testImplementation(libs.junit.jupiter.engine)
    testImplementation(libs.junit.platform.launcher)
    testImplementation(libs.kotlinx.coroutines.test)

    // Use test fixtures bundle
    testImplementation(libs.viaduct.test.fixtures)
}

application {
    mainClass.set("com.example.viadapp.ViaductApplicationKt")
}

View full file on GitHub

Creating the Schema

Create the directory structure for your schema:

mkdir -p src/main/viaduct/schema

Create src/main/viaduct/schema/schema.graphqls with the following content:

extend type Query {
  greeting: String @resolver
  author: String @resolver
}

View full file on GitHub

Creating the Application Code

Create the directory structure for your Kotlin code:

mkdir -p src/main/kotlin/com/example/viadapp
mkdir -p src/main/kotlin/com/example/viadapp/resolvers

Create src/main/kotlin/com/example/viadapp/ViaductApplication.kt:

package com.example.viadapp

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import com.fasterxml.jackson.databind.ObjectMapper
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import viaduct.service.BasicViaductFactory
import viaduct.service.TenantRegistrationInfo
import viaduct.service.api.ExecutionInput

fun main(argv: Array<String>) {
    val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger
    rootLogger.level = Level.ERROR

    // Create a Viaduct engine using the BasicViaductFactory
    // tag::building-viaduct[5] Building viaduct from BasicViaductFactory
    val viaduct = BasicViaductFactory.create(
        tenantRegistrationInfo = TenantRegistrationInfo(
            tenantPackagePrefix = "com.example.viadapp"
        )
    )

    // Create an execution input
    // tag::create-execution-input[12] Creating an execution input
    val executionInput = ExecutionInput.create(
        operationText = (
            argv.getOrNull(0)
                ?: """
                     query {
                         greeting
                     }
                """.trimIndent()
        ),
        variables = emptyMap(),
    )

    // Run the query
    // tag::viaduct-execute-operation[3] Execute a query through Viaduct.
    val result = runBlocking {
        viaduct.execute(executionInput)
    }

    // [toSpecification] converts to JSON as described in the GraphQL
    // specification.
    val mapper = ObjectMapper().writerWithDefaultPrettyPrinter()
    println(
        mapper.writeValueAsString(result.toSpecification())
    )
}

View full file on GitHub

Create src/main/kotlin/com/example/viadapp/resolvers/HelloWorldResolvers.kt:

package com.example.viadapp.resolvers

import com.example.viadapp.resolvers.resolverbases.QueryResolvers
import viaduct.api.Resolver

// tag::greeting-resolver[6] How to create a resolver
@Resolver
class GreetingResolver : QueryResolvers.Greeting() {
    override suspend fun resolve(ctx: Context): String {
        return "Hello, World!"
    }
}

@Resolver
class AuthorResolver : QueryResolvers.Author() {
    override suspend fun resolve(ctx: Context): String {
        return "Brian Kernighan"
    }
}

View full file on GitHub

Building and running the Application

Build your application:

gradle wrapper --gradle-version 8.14
./gradlew build

Run the application:

./gradlew -q run --args="'{ greeting }'"
or
./gradlew -q run --args="'{ author }'"

What's Next

Continue to Touring the Application to understand the structure of a Viaduct application.