Build From Scratch

Create a Viaduct application from the ground up

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

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:


val viaductVersion: String by settings

// When part of composite build, use local gradle-plugins
// When standalone, use Maven Central (only after version is published)
pluginManagement {
   if (gradle.parent != null) {
       includeBuild("../../gradle-plugins")
   } else {
       repositories {
           mavenCentral()
           gradlePluginPortal()
       }
   }
}

dependencyResolutionManagement {
   repositories {
       mavenCentral()
       gradlePluginPortal()
   }
   versionCatalogs {
       create("libs") {
           // This injects a dynamic value that your TOML can reference.
           version("viaduct", viaductVersion)
       }
   }
}

Create a gradle.properties file to specify the Viaduct version:

viaductVersion=0.7.0-SNAPSHOT

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

[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 {
   kotlin("jvm") version "1.9.24"
   alias(libs.plugins.viaduct.application)
   alias(libs.plugins.viaduct.module)
   application
}

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

viaductModule {
   modulePackageSuffix.set("resolvers")
}

dependencies {
   implementation("ch.qos.logback:logback-classic:1.3.7")
   implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")

   implementation("com.fasterxml.jackson.core:jackson-databind:2.9.10")

   testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
   testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
   testImplementation("org.junit.platform:junit-platform-launcher:1.9.2")
   testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2")
}

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

tasks.test {
   useJUnitPlatform()
}

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
}

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())
   )
}

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"
   }
}

Building and running the Application

Build your application:

./gradlew build

Run the application:

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

You should see the GraphQL response with your greeting!

What’s Next

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