Home Android & Kotlin Tutorials

Ktor and GraphQL: Getting Started

Learn how to create a GraphQL server using Ktor

5/5 2 Ratings

Version

  • Kotlin 1.4, IntelliJ IDEA

Ktor is a framework for easily building connected applications — mobile, web, browser and desktop apps. Ktor applications are written in Kotlin and give developers the ability to quickly build asynchronous web applications.

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, giving clients the power to ask for exactly what they need and nothing more.

In this tutorial, you’ll focus on:

  • Using Ktor to create a GraphQL API using the KGraphQL library.
  • Using KGraphQL rich DSL to set up the GraphQL schemas.
  • Executing basic operations (queries and mutations) around built-in data.

You’ll learn more about GraphQL by creating a project named RWFootballPlayersGraphQL, which will serve information about soccer players through a GraphQL interface.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

You’ll need IntelliJ IDEA with the Ktor plugin installed. Then, open the starter project and wait until the IDE finishes loading the project dependencies and indexing the project. Now, you have a fully functional GraphQL server.

Start your server by pressing the green play icon beside the main function. Test to make sure your server is running by going to localhost:8080/graphql. You should see the following page in your browser.

Start project — Playground

With your server up and running, next you’ll dive into what exactly is GraphQL.

What Is GraphQL?

GraphQL is a query language for your API and a server-side runtime for handling queries. Think of GraphQL as a facade around your existing API or database. GraphQL also leverages the use of a type system to define your backing data. GraphQL isn’t tied to any specific database or storage engine. Instead, it is backed by your existing code and data.

API developers use GraphQL to create a schema to describe all the possible data that clients can query through that service. A GraphQL schema consists of object types, which define the kinds of objects you can request and what fields they have. As queries come in, GraphQL validates the queries against the schema. GraphQL then executes the validated queries. The API developer attaches each field in a schema to a function called a resolver. During execution, the resolver is called to produce the value. You’ll create a resolver later in this tutorial.

Distinguishing Between GraphQL and REST

The main difference between GraphQL and REST is that REST is an architectural concept for network-based software. GraphQL, on the other hand, is a query language, a specification and a set of tools that operates over a single endpoint using HTTP. There are other differences between the two technologies:

Table showing differences between GraphQL and REST

But they have similarities as well. Both provide information to front-end clients in JSON and have ways to identify if an operation is going to read or write data. Both also end up calling functions on the server to perform different kinds of requests. The following diagram from the ApolloGraphQL blog summarizes the difference.

Rest VS GraphQL

Where REST tends to have multiple endpoints, GraphQL has a single endpoint.

What Is Ktor?

Ktor is an asynchronous framework for quickly creating web applications in Kotlin with minimal effort. You can develop microservices, web applications and more. Ktor describes itself as fun, free and open source.

Some of the more remarkable reasons to develop with Ktor include that it is:

  • Lightweight: Use what you need. Ktor allows you to transparently configure only the functionality your project requires.
  • Extensible: Extend what you need. With a configurable pipeline, you can create the extensions you need and place them anywhere you want.
  • Multiplatform: Run it where you need it. Built from the ground up with Kotlin Multiplatform technology, you can deploy Ktor applications anywhere.
  • Asynchronous: Scale as you need it. Using Kotlin coroutines, Ktor is truly asynchronous and highly scalable.

For this tutorial, you’ll work with Ktor as your server-side framework to build this project.

Developing the Project

GraphQL has the ability to interface with all sorts of data sources, including databases, REST endpoints, etc… However, to manage the scope of this tutorial, you’ll work with data stored in memory. After you’ve opened up the starter project in your IDE, the first step is to set up the object model class before exposing the GraphQL models.

Setting up Model Class

Before creating a GraphQL schema, it’s necessary to define the object model that will be exposed by the service. Start by creating a models folder in the src directory. Then, create the model.kt file in models directory. Remove any Models class created for you and replace with:

// 1
enum class Position {
  GK, // Goalkeeper
  DEF, // Defender
  MID, // Midfielder
  FRW  // Forward
}

// 2
data class Player(var uid: String, var name: String, var team: String, var position: Position)

// 3
data class PlayerInput(val name: String, val team: String, val position: Position)

Here’s what the above code defines:

  1. The Position enum class describes the different positions for a player.
  2. The Player data class provides the attributes for the model.
  3. The PlayerInput is the model that you’re going to send when you want to operate with the data source.

Adding Built-in Data

After you define the object model, it’s time to create the built-in data that simulates a database. Create a folder named data in the src directory. Then, create a file named database.kt to store the list of players. Add the following code:

import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.Position
    
val players = mutableListOf(
  Player("abc123", "Sergio Ramos", "Real Madrid", Position.DEF),
  Player("abc124", "Lionel Messi", "Barcelona", Position.FRW),
  Player("abc125", "Cristiano Ronaldo", "Juventus", Position.FRW),
  Player("abc126", "Leon Goretzka", "Bayern Munich", Position.MID),
  Player("abc127", "Manuel Neuer", "Bayern Munich", Position.GK),
  Player("abc128", "Neymar Jr", "PSG", Position.FRW),
  Player("abc129", "Casemiro", "Real Madrid", Position.MID)
)

Notice that you’re creating a mutable list of Players. This is so you can make modifications through GraphQL calls in a later section.

With the data created, you’ll turn your focus on wrapping an interface around the data to facilitate reading/writing to/from the list. To do this, you’ll make use of the Repository pattern.

Adding Repository Pattern

The Repository pattern separates the data access logic and maps it to the business entities in the business logic. The data access logic and business logic layers communicate via interfaces. So it’s time to create the repository. First, create the repository directory. Then, create the IPlayerRepository.kt file. Add:

import com.raywenderlich.kgraphqlfootball.data.players
import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.PlayerInput
import com.raywenderlich.kgraphqlfootball.models.Position

interface IPlayerRepository {
  fun createPlayer(player: Player)
  fun deletePlayer(uid: String)
  fun listPlayers() : List<Player>
  fun filterPlayersByPosition(position: Position): List<Player>
  fun filterPlayersByTeam(team: String): List<Player>
  fun updatePlayer(uid: String, playerInput: PlayerInput)
}

This interface defines the functionality of the player repository. This interface can be reused to abstract calls to a database, or another REST API. In this project, you’ll
define the implementation for each method. Add the following below the interface:

class PlayerRepository : IPlayerRepository {
  override fun createPlayer(player: Player) {
    players.add(player)
  }
    
  override fun deletePlayer(uid: String) {
    players.removeIf { it.uid == uid }
  }
    
  override fun listPlayers(): List<Player> {
    return players
  }
    
  override fun filterPlayersByPosition(position: Position):List<Player> {
    return players.filter { it.position == position }
  }
    
  override fun filterPlayersByTeam(team: String): List<Player> {
    return players.filter { it.team == team }
  }
    
  override fun updatePlayer(uid: String, playerInput: PlayerInput) {
    players.find { it.uid == uid }?.apply {
      name = playerInput.name
      position = playerInput.position
      team = playerInput.team
    }
  }
}

For now, you’re working with built-in data from the list of players. So the operations you’re going to implement are going to be from this local list. You now have all the classes needed to expose your data with GraphQL. Well done!

Ktor Client with GraphQL

With the object model and the interface to the model complete, you’ll turn your focus on wrapping the interface for GraphQL. Specifically, you’ll be using KGraphQL to wrap your interface. KGraphQL is a Kotlin implementation of GraphQL. It provides a rich DSL to set up the GraphQL schema. You’ll start by defining the schema.

Creating a Schema

Your GraphQL server uses a schema to describe the shape of your data graph. This schema defines a hierarchy of types with fields that populate from your back-end data stores. The schema also specifies exactly which queries and mutations are available for clients to execute against your data graph. Start by creating the SchemaGraphQL.kt file in the src directory and add:

import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.PlayerInput
import com.raywenderlich.kgraphqlfootball.models.Position
import com.raywenderlich.kgraphqlfootball.respository.IPlayerRepository
import com.raywenderlich.kgraphqlfootball.respository.PlayerRepository

fun SchemaBuilder.schemaValue() {
  // 1
  val repository: IPlayerRepository = PlayerRepository()

  // TODO: Queries and mutations will go here ...
    
  // 2
  inputType<PlayerInput>{
    description = "The input of the player without the identifier"
  }
  // 3
  type<Player>{
   description = "Player object with the attributes name, team, position and identifier"
  }
  // 4
  enum<Position>()
}

In the code above, you have to define two steps:

  1. Define the repository for players that you implemented in the previous section.
  2. The inputType works as a object type for input data models on the GraphQL schema.
  3. Registers the Kotlin data classes with the type method to include it in the created schema type system.
  4. Registers the Kotlin enum with the enum method to include it in the created schema type system.

If you restart your server in IntelliJ, and refresh your browser, you should notice a Schema tab to the far right. If you click on it, you should see your models described in the panel shown. You can see it in the screenshot below:

Schema

You’ll notice that any description you fill when describing your model is also shown here. This can be useful for developers who might use your interface to better understand the models provided by your endpoint.

With the data types defined in the schema, the next step will be to add your first query.

Fetching Data with Queries

A GraphQL query simply reads or fetches values from your datastore. These queries help reduce over-fetching of data. Unlike a REST API, GraphQL allows a user to select which fields to fetch from the server. This means smaller queries and less traffic over the network, reducing the response time.

You’ll start by adding a method to query all players by a given position. Add the following inside the SchemaBuilder.schemaValue() function:

 // 1
  query("playersByPosition") {
    // 2
    description = "Retrieve all the players by his position"
    // 3
    resolver { position: Position ->
      try {
        // 4
        repository.filterPlayersByPosition(position)
      } catch (e: Exception) {
        emptyList<Player>()
      }
    }
  }

The above code breaks down as follows:

  1. Define a query with a unique name and add a description for the resolver.
  2. Give the method a useful description so developers better understand its usage.
  3. Use a resolver accepting a position and returning a list of Players. Notice that you’ll call the filterPlayersByPosition you added to the IPlayerRepository interface.

In GraphQL every property needs a resolver. The resolver is the piece of system logic, required to resolve the response graph. Since you defined Position enum type, you’re able to accept it as part of the resolver. The final step is to setup the schema when the application is launched.

Open up Application.kt and replace the “Hello, world” query with:

schema { schemaValue() }

Now, run the application and, on the webpage, tap the Schema tab. You’ll see the following result:

playersByPosition

To test the implementation, try to run the playerByPosition query. In the Query section, add:

query fetchPlayerByPosition($position: Position!){
  playersByPosition(position: $position){
    uid
    name
    position
    team
  }
}

Click the Query variables tab and put the following code that represents the object in the argument field. Here, you’re choosing to search for players who are midfielders.

{
  "position": "MID"
}

Click the middle Play button. When you execute this, you should see the following result from the playground:

Result of executing the query on playground

To flesh out the API, add the following players and playersByTeam queries to retrieve a list of all players, and players by team respectively.

  query("players") {
    description = "Retrieve all players"
    resolver { ->
      try {
        repository.listPlayers()
      } catch (e: Exception) {
        emptyList<Player>()
      }
    }
  }
    
  query("playersByTeam") {
    description = "Retrieve all the players by his team"
    resolver { team: String ->
      try {
        repository.filterPlayersByTeam(team)
      } catch (e: Exception) {
        emptyList<Player>()
      }
    }
  }

Notice how the the players doesn’t require any arguments, where as playersByTeam expects a team name as a string.

With queries complete, you can now add the ability to modify or mutate objects using GraphQL. You’ll do that in the next section.

Writing Data with Mutations

In REST, any requests made may cause side-effects on the server. But by convention, it’s recommended to not use GET requests to modify data. GraphQL is similar — any query should not do a data write. However, it’s useful to establish a convention that any operations that cause a write operation should be sent via a mutation.

You’ll first add a mutation to allow creating new players. Add a createPlayer mutation to the SchemaBuilder.schemaValue() function:

 // 1
  mutation("createPlayer") {
    // 2
    description = "Create a new player"
    // 3
    resolver { playerInput: PlayerInput ->
      try {
        // 4
        val uid = java.util.UUID.randomUUID().toString()
        val player = Player(uid, playerInput.name, playerInput.team, playerInput.position)
        repository.createPlayer(player)
        true
      } catch (e: Exception) {
        false
      }
    }
  }

The above code breaks down as follows:

  1. Create a mutation with a unique name.
  2. Give the method a description to help developers better understand the method.
  3. Use a resolver accepting a PlayerInput. Recall that you defined this class earlier with the schema, so it can be used a typed input.
  4. Create the Player object, and store it in the list.

As with queries, you’ll call the respective repository methods for each resolver that you create. In this case, that means actions that’ll affect the result of the data. After implementing the code above and re-running the project again, you’ll see this:

createPlayer

In order to test the createPlayer mutation, replace the query with:

mutation createNewPlayer($playerInput: PlayerInput!){
  createPlayer(playerInput:$playerInput)
}

Inside the Query variables, put the following code that represents the object in the argument field:

{
  "playerInput": {
    "name": "Test player",
    "position": "DEF",
    "team": "Barcelona"
  }
}

When you execute this, expect the following result from the playground:

Result of executing the mutation on playground

Congratulations! You’ve successfully created a GraphQL API! If you want to challenge yourself, try to query your API to retrieve the list of players after creating a new one. Similarly, checkout the completed project for implementations of deletePlayer and updatePlayer to see how to delete and update a player respectively.

Where to Go From Here?

You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

You’ve just learned how to create a GraphQL application using Ktor and KGraphQL! Although you’ve scratched the surface, there are many areas of either Ktor, or KGraphQL you might want to explore.

We hope you enjoyed this tutorial. If you have any questions, please join the discussion below.

Average Rating

5/5

Add a rating for this content

2 ratings

More like this

Contributors

Comments