Skip to main content

Overview

The Jetty Starter demonstrates how to integrate Viaduct with a lightweight HTTP server using Eclipse Jetty servlet container. This tutorial shows you how to:
  • Set up a GraphQL-over-HTTP endpoint using servlets
  • Handle both POST and GET requests
  • Serve the GraphiQL interactive IDE
  • Parse and execute GraphQL queries from HTTP requests

What You’ll Learn

  • Creating a custom GraphQL servlet with Viaduct
  • Setting up Jetty embedded server
  • Handling different content types (JSON, GraphQL)
  • Implementing GraphiQL for interactive query testing
  • Module-based architecture with automatic schema generation

Prerequisites

  • Java JDK 21 installed
  • JAVA_HOME environment variable set correctly or java in your PATH

Project Structure

jetty-starter/
├── src/main/kotlin/com/example/viadapp/
│   ├── JettyViaductApp.kt         # Main application & server setup
│   ├── ViaductServlet.kt          # GraphQL HTTP endpoint
│   └── GraphiQLServlet.kt         # GraphiQL UI endpoint
├── resolvers/                      # GraphQL resolver module
│   └── src/main/
│       ├── kotlin/.../resolvers/
│       │   └── HelloWorldResolvers.kt
│       └── viaduct/schema/
│           └── schema.graphqls
└── src/test/kotlin/                # Integration tests
1
Create the Viaduct Servlet
2
The ViaductServlet handles GraphQL requests over HTTP. Here’s the core implementation in ViaductServlet.kt:16:
3
class ViaductServlet(
    private val viaduct: Viaduct
) : HttpServlet() {
    private val objectMapper = ObjectMapper()

    override fun doPost(
        req: HttpServletRequest,
        resp: HttpServletResponse
    ) {
        val contentType = req.contentType?.lowercase()?.split(";")?.get(0)?.trim()

        when (contentType) {
            null, "application/json", "application/graphql+json" -> handleJsonPost(req, resp)
            "application/graphql" -> handleGraphQLPost(req, resp)
            else -> sendError(resp, 415, "Unsupported Content-Type: $contentType")
        }
    }

    private fun executeGraphQL(
        query: String,
        variables: Map<String, Any>,
        operationName: String?,
        resp: HttpServletResponse
    ) {
        val executionInput = ExecutionInput.create(
            operationText = query,
            variables = variables,
            operationName = operationName
        )
        
        val result = runBlocking {
            viaduct.executeAsync(executionInput).join()
        }
        
        val statusCode = if (result.errors.isNotEmpty()) 400 else 200
        sendJson(resp, statusCode, result.toSpecification())
    }
}
4
Key Features:
5
  • Supports application/json and application/graphql content types
  • Handles both POST and GET requests
  • Returns proper HTTP status codes (200 for success, 400 for errors)
  • Uses viaduct.executeAsync() for asynchronous query execution
  • 6
    Set Up the Jetty Server
    7
    The main application in JettyViaductApp.kt:14 creates and configures the server:
    8
    class JettyViaductApp(private val port: Int = 8080) {
        private val server: Server
    
        init {
            // Create a Viaduct engine using BasicViaductFactory
            val viaduct = BasicViaductFactory.create(
                tenantRegistrationInfo = TenantRegistrationInfo(
                    tenantPackagePrefix = "com.example.viadapp"
                )
            )
    
            // Create the servlets
            val viaductServlet = ViaductServlet(viaduct)
            val graphiqlServlet = GraphiQLServlet()
    
            // Set up Jetty server
            server = Server(port)
            val context = ServletContextHandler(ServletContextHandler.NO_SESSIONS)
            context.contextPath = "/"
            context.addServlet(ServletHolder(viaductServlet), "/graphql")
            context.addServlet(ServletHolder(graphiqlServlet), "/graphiql")
    
            server.handler = context
        }
    
        fun start() {
            server.start()
        }
    }
    
    fun main(argv: Array<String>) {
        val app = JettyViaductApp()
        app.start()
        app.join()
    }
    
    9
    Key Points:
    10
  • BasicViaductFactory automatically discovers schemas and resolvers
  • Two servlets are registered: /graphql for queries, /graphiql for the UI
  • The server is lightweight and starts quickly
  • 11
    Define Schema and Resolvers
    12
    The GraphQL schema is defined in resolvers/src/main/viaduct/schema/schema.graphqls:
    13
    extend type Query {
      greeting: String @resolver
      author: String @resolver
    }
    
    14
    Resolvers are implemented in resolvers/src/main/kotlin/.../HelloWorldResolvers.kt:
    15
    @Resolver
    class GreetingResolver : QueryResolvers.Greeting() {
        override suspend fun resolve(ctx: Context): String {
            return "Hello from Jetty + Viaduct!"
        }
    }
    
    @Resolver
    class AuthorResolver : QueryResolvers.Author() {
        override suspend fun resolve(ctx: Context): String {
            return "Viaduct GraphQL with Jetty"
        }
    }
    
    16
    Start the Server
    17
    Run the application:
    18
    ./gradlew run
    
    19
    The server will start on http://localhost:8080.
    20
    Test with curl (JSON)
    21
    Send a GraphQL query via POST with JSON:
    22
    curl 'http://localhost:8080/graphql' \
      -H 'content-type: application/json' \
      --data-raw '{"query":"{ greeting }"}'
    
    23
    Response:
    24
    {"data":{"greeting":"Hello from Jetty + Viaduct!"}}
    
    25
    Test with curl (Multiple Fields)
    26
    Query multiple fields:
    27
    curl 'http://localhost:8080/graphql' \
      -H 'content-type: application/json' \
      --data-raw '{"query":"query HelloWorld { greeting author }"}'
    
    28
    Response:
    29
    {
      "data": {
        "greeting": "Hello from Jetty + Viaduct!",
        "author": "Viaduct GraphQL with Jetty"
      }
    }
    
    30
    Test with GET Requests
    31
    The servlet also supports GET requests with query parameters:
    32
    curl 'http://localhost:8080/graphql?query=%7B%20greeting%20%7D'
    
    33
    Response:
    34
    {"data":{"greeting":"Hello from Jetty + Viaduct!"}}
    
    35
    Use GraphiQL
    36
    Open your browser and navigate to:
    37
    http://localhost:8080/graphiql
    
    38
    Try this query in the GraphiQL interface:
    39
    query HelloWorld {
      greeting
      author
    }
    
    40
    Expected Response:
    41
    {
      "data": {
        "greeting": "Hello from Jetty + Viaduct!",
        "author": "Viaduct GraphQL with Jetty"
      }
    }
    
    42
    Run Tests
    43
    Execute the integration tests:
    44
    ./gradlew test
    
    45
    The tests start an embedded Jetty server and verify GraphQL endpoint behavior.

    How It Works

    Request Flow

    1. HTTP Request: Client sends a POST or GET request to /graphql
    2. Content Type Detection: Servlet determines if it’s JSON or raw GraphQL
    3. Query Parsing: Extract query, variables, and operation name
    4. ExecutionInput Creation: Build Viaduct’s execution input object
    5. Query Execution: viaduct.executeAsync() processes the query
    6. Response Serialization: Convert result to JSON and send HTTP response

    Supported Content Types

    • application/json: Standard GraphQL-over-HTTP (POST)
    • application/graphql: Raw GraphQL query string (POST)
    • Query parameters: For simple GET requests

    Error Handling

    The servlet returns appropriate HTTP status codes:
    • 200 OK: Successful query execution
    • 400 Bad Request: GraphQL errors (e.g., validation failures)
    • 415 Unsupported Media Type: Invalid content type
    • 500 Internal Server Error: Execution exceptions

    Module-Based Architecture

    The project separates concerns:
    1. Main Application: Server setup and servlet configuration
    2. Resolvers Module: Schema and resolver implementations
    3. Test Suite: Integration tests for the HTTP endpoint
    BasicViaductFactory automatically:
    • Scans for .graphqls schema files
    • Generates resolver base classes at compile time
    • Discovers @Resolver annotated classes at runtime

    Next Steps

    Build docs developers (and LLMs) love