Skip to content

Scaffold

This guide walks you through creating a Viaduct application using the scaffold task, which generates a complete Ktor + Viaduct GraphQL project from a single command.

Getting Started

The scaffold task generates a ready-to-run Ktor application with:

  • GraphQL endpoint (/graphql)
  • GraphiQL UI (/graphiql)
  • Health check endpoint (/health)
  • A sample "hello world" resolver

Prerequisites

  • Java 21 must be on the path or available via JAVA_HOME
  • Gradle must be installed (the scaffold will generate wrapper files)

Running the Scaffold

First, clone a repository that has the Viaduct application plugin applied. The simplest is the CLI starter:

git clone https://github.com/viaduct-graphql/cli-starter.git
cd cli-starter

Then run the scaffold task with your desired package prefix:

./gradlew scaffold -PpackagePrefix=com.example.myapp -PoutputDir=../my-app

This creates a complete project in ../my-app/.

Running the Generated Application

Navigate to your new project and run it:

cd ../my-app
./gradlew run

The server starts with:

  • GraphQL endpoint at http://localhost:8080/graphql
  • GraphiQL UI at http://localhost:8080/graphiql
  • Health check at http://localhost:8080/health

Try the greeting query in GraphiQL:

{
  greeting
}

Generated Project Structure

my-app/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
├── gradlew
├── gradlew.bat
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
└── src/
    └── main/
        ├── kotlin/
        │   └── com/example/myapp/
        │       ├── Main.kt
        │       ├── ktorplugins/
        │       │   ├── ContentNegotiation.kt
        │       │   └── Routing.kt
        │       └── resolvers/
        │           └── GreetingResolver.kt
        ├── resources/
        │   └── application.conf
        └── viaduct/
            └── schema/
                └── schema.graphqls

Key Generated Files

Main.kt

The entry point uses Ktor's EngineMain. Here's a similar pattern from the ktor-starter:

package com.example.viadapp

import io.ktor.server.application.Application

const val SCHEMA_ID: String = "publicSchema"

fun main(argv: Array<String>) {
    io.ktor.server.jetty.EngineMain.main(argv)
}

fun Application.module() {
    configurePlugins()
    configureRouting()

View full file on GitHub

schema.graphqls

The GraphQL schema extends the base Query type with a greeting field. The @resolver directive indicates that a resolver class implements this field:

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

View full file on GitHub

resolvers/GreetingResolver.kt

A resolver implements the greeting query. Here's the pattern from the cli-starter:

@Resolver
class GreetingResolver : QueryResolvers.Greeting() {
    override suspend fun resolve(ctx: Context): String {
        return "Hello, World!"
    }
}

View full file on GitHub

ktorplugins/ContentNegotiation.kt

Configures Jackson for JSON serialization:

package com.example.viadapp

import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation

fun Application.configurePlugins() {
    if (pluginOrNull(ContentNegotiation) == null) {
        install(ContentNegotiation) {
            jackson {
                enable(SerializationFeature.INDENT_OUTPUT)
            }
        }
    }
}

View full file on GitHub

ktorplugins/Routing.kt

Configures the GraphQL endpoint using BasicViaductFactory. The scaffold generates routes for /health, /graphql and /graphiql:

package com.example.viadapp

import com.example.viadapp.injector.ViaductConfiguration
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.coroutines.future.await
import viaduct.service.api.ExecutionInput
import viaduct.service.api.ExecutionResult

fun Application.configureRouting() {
    val viaduct = ViaductConfiguration.viaductService

    routing {
        get("/graphiql") {
            val resource = this::class.java.classLoader.getResource("graphiql/index.html")
            if (resource != null) {
                call.respondText(resource.readText(), ContentType.Text.Html)
            } else {
                call.respond(HttpStatusCode.NotFound, "GraphiQL not found")
            }
        }

        route("/graphql") {
            post {
                @Suppress("UNCHECKED_CAST")
                val request = call.receive<Map<String, Any?>>() as Map<String, Any>

                // Validate query parameter
                val query = request["query"] as? String
                if (query == null) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        mapOf("errors" to listOf(mapOf("message" to "Query parameter is required and must be a string")))
                    )
                    return@post
                }

                @Suppress("UNCHECKED_CAST")
                val executionInput = ExecutionInput.create(
                    operationText = query,
                    variables = (request["variables"] as? Map<String, Any>) ?: emptyMap(),
                )

                val result: ExecutionResult = viaduct.executeAsync(executionInput).await()

                when {

View full file on GitHub

Customizing Your Application

After scaffolding, the generated code is entirely yours to modify:

  1. Add types: Extend schema.graphqls with your types and queries
  2. Add resolvers: Create new resolver classes in the resolvers/ package
  3. Add routes: Extend ktorplugins/Routing.kt with custom endpoints
  4. Configure the server: Modify application.conf for port, host, etc.

Development Mode

For development with auto-restart on code changes:

./gradlew --continuous run

What's Next

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