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.
- Creating a GraphQL API
- Setting up KGraphQL
- GraphiQL Playground
- Query and type
- Arguments
- Mutation
- Running without Playground
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 dataMutation
: For writing dataSubscription
: For fetching updates when data changes. (think RxJavaObservables
)
(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)) } }
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.
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.
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 } }
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:
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 }) }
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.
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.
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