Up until recently, REST was the de-facto design for creating a backend API. Over the past couple of years, GraphQL has grown in popularity as an alternative to REST.
Similarly, in mobile development, Kotlin has started gaining recognition among Android developers as an alternative to Java. The popularity of Kotlin has seen it being used in other technologies like backend and cross-platform app development.
In this article, we will take a look at setting up GraphQL
in Ktor
and exposing an existing datasource as a GraphQL
API.
Before we head into the details, here’s a rundown of the solutions we’ll be discussing in this article.
Ktor is an asynchronous framework used to create web applications. It allows developers to easily create a backend API in Kotlin for mobile or web clients.
GraphQL is a language for querying data, which it does by exposing data from an underlying datasource. GraphQL does not query a table or database; it is agnostic of where the actual data resides (SQL/NoSQL databases, files, microservices, REST APIs, etc.)
GraphQL has three main operations:
Query
: For fetching dataMutation
: For writing dataSubscription
: For fetching updates when data changes. (think RxJava Observables
)(Note: These are just specifications and not a rule — a backend server can choose to mutate its data in a query
operation.)
Nullability as a feature is built into both Kotlin
and GraphQL
. In Kotlin, a nullable field is denoted with ?
, and in GraphQL a non-nullable field is displayed as !
.
// Kotlin val name: String? // -> nullable val age: String // -> non-nullable
// GraphQL name: String // -> nullable age: String! // -> non nullable
We can create a starter Ktor project from here, or use the IntelliJ plugin (for IDEA Ultimate Edition).
We will use the KGraphQL
library to expose data as a GraphQL API. I will be using an existing MySQL
database with [SQLDelight]
from a previous project as an underlying datasource.
The database contains a list of movies that is exposed as a GET
query in the /movies
endpoint.
val moviesRepository by inject<MovieRepository>() routing { get("/movies") { call.respond(moviesRepository.getAllTheMovies(Long.MAX_VALUE)) } }
The Movie
model class looks like this:
@Serializable data class Movie( val id: Int, val name: String?, val genre: String?, val leadStudio: String?, val audienceScore: Int?, val profitability: Double?, val rottenTomatoes: Int?, val worldwideGross: String?, val year: Int? )
Add KGraphQL
dependencies to the build.gradle
or build.gradle.kts
file:
implementation("com.apurebase:kgraphql:0.17.14") implementation("com.apurebase:kgraphql-ktor:0.17.14")
Create a file called KGraphQL.kt
:
import com.apurebase.kgraphql.GraphQL import io.ktor.application.Application import io.ktor.application.install fun Application.configureGraphQL() { install(GraphQL) { playground = true schema { // TODO: next section } } }
Then, add the above extension function to Ktor’s Application.kt
file:
fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { configureKoin() configureHTTP() configureSerialization() configureRouting() configureGraphQL() // <- Here }.start(wait = true) }
Now, when we run the application and hit the /graphql
endpoint, we are presented with a GraphiQL (pronounced “graphical”) playground. This allows us to test our GraphQL
queries during development.
We can register our Movie
data class as a GraphQL
output type by adding it to the schema
block of the KGraphQL.kt
file.
schema { type<Movie>() { description = "Movie object" } }
Now, in our GraphiQL playground, we can see the details in the “SCHEMA” tab.
To create a simple query that returns the list of movies, we will add the following code in our schema
block:
query("movies") { description = "Returns a list of movie" resolver { -> movieRepository.getAllTheMovies(Long.MAX_VALUE) } }
(Note: Here, movieRepository.getAllTheMovies()
internally queries the MySql database. You can change it to whatever data source you would like.)
Now, let’s write our first GraphQL
query in the playground and click the “Run” (presented as a “Play” symbol) button.
query { movies { id name genre year } }
To add an argument to the movies
query, we can modify our resolver. We will add the parameter first
to limit the number of movies returned.
resolver { first: Long -> movieRepository.getAllTheMovies(first) }
To run our previous query, we need to provide a value for first
:
query { movies(first: 2) { id name genre year } }
This is a required argument, without which GraphQL
will throw an error like this:
To make the argument optional, we can provide a default value to our resolver using the withArgs
closure, demonstrated here:
resolver { first: Long -> movieRepository.getAllTheMovies(first) }.withArgs { arg<Long> { name = "first"; defaultValue = 10L } }
Now, if we run the movies
query without the first
argument, it will use the default value of 10.
Similarly, we can create another query to fetch a single movie that matches with id
.
query("movie") { description = "Returns a movie matched by id or null if id is invalid" resolver { id: Int -> movieRepository.getMovieById(id) } }
We can create a mutation operation similar to our query operation:
schema { //... mutation("addMovie") { description = "Adds a new movie and return" resolver { movie: Movie -> movieRepository.createMovie(movie) } } }
Running the above schema throws the following error:
"error": "Introspection must provide output type for fields, but received: [Movie!]!."
To use a custom class as the resolver argument’s type, we need to register it as inputType
. This is different from the type<movie>{..}
registration we did earlier.
inputType<Movie> { name = "movieInput" } mutation("addMovie") { description = "Adds a new movie and returns the id" resolver { movie: Movie -> movieRepository.createMovie(movie) } }
Running the below query adds a new row to our database:
mutation { addMovie(movie: { name: "Nobody" genre: "Thriller" leadStudio: "Universal Pictures" year: 2021 worldwideGross: "$56.7", rottenTomatoes: 84, profitability: null, id: null, audienceScore: null }) }
We can also use the “Query variables” tab in GraphiQL Playground and pass the movie
object as a JSON-encoded variable:
Similarly, to update an existing movie, we can create another mutation — this time, instead of returning just an id
, we are returning the movie object.
(Note: In a mutation operation, it’s up to us to return an id
or a whole movie
object, or anything else depending on the requirement.)
KGraphQL
does not support Subscription
operations at the time of writing this article.
Playground is used during development; we need to turn it off when we deploy our backend application.
fun Application.configureGraphQL() { val movieRepository by inject<MovieRepository>() install(GraphQL) { playground = false schema {..} } }
After restarting the application, if we try to access http:0.0.0.0:8080/graphql
on the browser, we will get a 404 Not Found
error.
To access our GraphQL API, we instead need to send a POST
request.
I’m using the Postman
application to test the endpoint. In the Body
, we can pass the query, as seen earlier, (since Postman has support for GraphQL
) or a JSON-formatted request like below.
{ "query": "query {\n movies {\n id\n name\n genre\n }\n}", "variables": {} }
We can also change the default /graphql
endpoint to something custom, like /movies-graphql
, in the install
block, and restart our application.
install(GraphQL) { playground = false endpoint = "/movies-graphql" schema {..} }
Now our GraphQL API will be served at this endpoint: https:0.0.0.0/movies-graphql
.
The complete source code for this project is available here.
This article showed you how to create a GraphQL server in Ktor and how to expose underlying data sources as GraphQL API endpoints.
We also looked at setting up GraphQL Playground for testing and debugging queries and mutations during development, as well as disabling Playground for the final rollout of production.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
4 Replies to "Creating a GraphQL server with Ktor"
Hi. What version of Ktor did you use to build this GraphQL server. It seems I’m the latest version 2 of Ktor, KGraphQL is broken.
I had used v1.6.7, haven’t tried it in v2. You can check the whole repo here https://github.com/jobinlawrance/ktor-graphql/blob/main/gradle.properties
I had used v1.6.7, haven’t tried it in v2.
You can check the sample repo here https://github.com/jobinlawrance/ktor-graphql/blob/main/gradle.properties
It is unclear to me what the MovieRepository is. Can you elborate on how I set this up?