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_HOMEenvironment variable set correctly orjavain your PATH
Project Structure
The
ViaductServlet handles GraphQL requests over HTTP. Here’s the core implementation in ViaductServlet.kt:16: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())
}
}
application/json and application/graphql content typesviaduct.executeAsync() for asynchronous query executionclass 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()
}
BasicViaductFactory automatically discovers schemas and resolvers/graphql for queries, /graphiql for the UI@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"
}
}
curl 'http://localhost:8080/graphql' \
-H 'content-type: application/json' \
--data-raw '{"query":"{ greeting }"}'
curl 'http://localhost:8080/graphql' \
-H 'content-type: application/json' \
--data-raw '{"query":"query HelloWorld { greeting author }"}'
How It Works
Request Flow
- HTTP Request: Client sends a POST or GET request to
/graphql - Content Type Detection: Servlet determines if it’s JSON or raw GraphQL
- Query Parsing: Extract query, variables, and operation name
- ExecutionInput Creation: Build Viaduct’s execution input object
- Query Execution:
viaduct.executeAsync()processes the query - 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:- Main Application: Server setup and servlet configuration
- Resolvers Module: Schema and resolver implementations
- Test Suite: Integration tests for the HTTP endpoint
BasicViaductFactory automatically:
- Scans for
.graphqlsschema files - Generates resolver base classes at compile time
- Discovers
@Resolverannotated classes at runtime
Next Steps
- Explore the Ktor Starter Tutorial for a Kotlin-native server framework
- Learn about Micronaut integration for dependency injection
- Review the Star Wars Tutorial for complex data modeling
- Read about Error Handling in production applications