This is a premium alert message you can set from Layout! Get Now!

Creating a GraphQL server with Ktor

0

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.

What to know about Ktor and GraphQL

Before we head into the details, here’s a rundown of the solutions we’ll be discussing in this article.

What is Ktor?

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.

What is GraphQL?

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 data
  • Mutation: For writing data
  • Subscription: 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.)

Similarities between Kotlin and GraphQL

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

Creating a GraphQL API

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))
    }
}

GraphQL Example Screenshot

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?
)

Setting up KGraphQL

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)
}

GraphiQL Playground

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.

GraphiQL Playground Screenshot Example

Query and type

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.

GraphiQL Playground Schema Tab Example Screenshot

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
  }
}

GraphiQL Playground Run Query Example Screenshot

Arguments

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:

GraphiQL Playground Error Thrown Example Screenshot

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) }
}

Mutation

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
  })
}

GraphiQL Custom Class Query Example

We can also use the “Query variables” tab in GraphiQL Playground and pass the movie object as a JSON-encoded variable:

GraphiQL Playground Query Variables Example

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.

GraphiQL Playground Move Object Query Return Example

(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.

Running without Playground

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.

Postman Post Request Demo Screenshot

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.

Conclusion

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.

The post Creating a GraphQL server with Ktor appeared first on LogRocket Blog.



from LogRocket Blog https://ift.tt/wGjEfL6
via Read more

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
Post a Comment

Search This Blog

To Top