Inicio Information Technology Intro to Ktor: The server-side stack

Intro to Ktor: The server-side stack

0
Intro to Ktor: The server-side stack



My earlier article introduced Ktor and some of its basic features for constructing internet purposes. Now, we’ll increase the instance software developed in that article by including persistent information and HTMX, which can present extra interactive views. This provides us a setup with a variety of energy in a comparatively easy stack.

Please see the previous article for the instance software code and setup. We’ll construct on that instance right here.

Add persistence to the Ktor-HTMX software

Step one towards making our software extra highly effective is so as to add persistent information. The preferred solution to work together with an SQL database in Kotlin is with the Exposed ORM framework. It provides us a few methods to work together with the database, utilizing both a DAO mapping or a DSL. Kotlin’s native syntax means the general really feel of utilizing the ORM mapping layer has much less overhead than others you might need encountered.

We’ll want so as to add just a few dependencies to our construct.gradle.kt, along with these we have already got:


dependencies {
  // present deps...
    implementation("org.jetbrains.uncovered:exposed-core:0.41.1")
  implementation("org.jetbrains.uncovered:exposed-jdbc:0.41.1") 
  implementation("com.h2database:h2:2.2.224")
}

You’ll discover we’ve included the uncovered core and JDBC libraries, in addition to a driver for the in-memory H2 database. We’ll use H2 as a easy persistence mechanism that may simply be converted to an exterior SQL database like Postgres in a while.

Add companies

To begin with, we’ll create a few easy companies that work together with a predominant service, which talks to the database. Right here’s our QuoteSchema.kt file up to now, which units up the database schema and gives service features for interacting with it:


// src/predominant/kotlin/com/instance/plugins/QuoteSchema.kt
bundle com.instance.plugins

import kotlinx.coroutines.*
import org.jetbrains.uncovered.sql.*
import org.jetbrains.uncovered.sql.transactions.transaction

object Quotes : Desk() {
    val id: Column = integer("id").autoIncrement()
    val quote = textual content("quote")
    val writer = textual content("writer")

    override val primaryKey = PrimaryKey(id, title = "PK_Quotes_ID")
}

information class Quote(val id: Int? = null, val quote: String, val writer: String)

class QuoteService {
    droop enjoyable create(quote: Quote): Int = withContext(Dispatchers.IO) {
      transaction {
        Quotes.insert {
          it[this.quote] = quote.quote
          it[this.author] = quote.writer
        } get Quotes.id
      } ?: throw Exception("Unable to create quote")
    }
    droop enjoyable checklist(): Record = withContext(Dispatchers.IO) {
        transaction {
            Quotes.selectAll().map {
                Quote(
                    id = it[Quotes.id],
                    quote = it[Quotes.quote],
                    writer = it[Quotes.author]
                )
            }
        }
    }
}

There’s lots happening on this file, so let’s take it step-by-step. The very first thing we do is declare a Quotes object that extends DeskDesk is part of the Uncovered framework and lets us outline a desk within the database. It does a variety of work for us primarily based on the 4 variables we outline: id, quote, writer, and major key. The id component can be auto-generated for an auto-increment major key, whereas the opposite two can have their acceptable column varieties (textual content turns into string, for instance, relying on the database’s dialect and driver). 

Uncovered can be sensible sufficient to solely generate the desk if it doesn’t exist already.

Subsequent, we declare a knowledge class referred to as Quote, utilizing the constructor model. Discover id is marked as non-obligatory (since it will likely be auto-generated). 

Then, we create a QuoteService class with two suspendable features: create and checklist. These are each interacting with the concurrent help in Kotlin, utilizing the IO dispatcher. These strategies are optimized for IO-bound concurrency, which is suitable for database entry. 

Inside every service technique, now we have a database transaction, which does the work of both inserting a brand new Quote or returning a Record of Quotes.

Routes

Now let’s make a Database.kt file that pulls within the QuoteService and exposes endpoints for interacting with it. We’ll want a POST for creating quotes and a GET for itemizing them.


//src/predominant/kotlin/com/instance/plugins/Database.kt 
bundle com.instance.plugins

import io.ktor.http.*
import io.ktor.server.software.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.sql.*
import kotlinx.coroutines.*
import org.jetbrains.uncovered.sql.*
import org.jetbrains.uncovered.sql.transactions.transaction

enjoyable Software.configureDatabases() {
    val database = Database.join(
        url = "jdbc:h2:mem:take a look at;DB_CLOSE_DELAY=-1",
        person = "root",
        driver = "org.h2.Driver",
        password = "",
    )
    transaction {
        SchemaUtils.create(Quotes)
    }
    val quoteService = QuoteService() 
    routing {
        put up("/quotes") {
          val parameters = name.receiveParameters()
          val quote = parameters["quote"] ?: ""
          val writer = parameters["author"] ?: ""
 
          val newQuote = Quote(quote = quote, writer = writer) 
 
          val id = quoteService.create(newQuote)
          name.reply(HttpStatusCode.Created, id)
        }
        get("/quotes") {
            val quotes = quoteService.checklist()
            name.reply(HttpStatusCode.OK, quotes)
        }
    }
}

We start through the use of Database.join from the Uncovered framework to create a database connection utilizing normal H2 parameters. Then, inside a transaction we create the Quotes schema, utilizing our Quotes class we outlined in QuoteSchema.kt.

Subsequent, we create two routes utilizing the syntax we developed in the first stage of this example and counting on the create and checklist features and Quote class from QuoteSchema.

Don’t overlook to incorporate the brand new perform in Software.kt:


// src/predominant/kotlin/com/instance/Software.kt 
bundle com.instance

import com.instance.plugins.*
import io.ktor.server.software.*
import io.ktor.server.response.*
import io.ktor.server.routing.*


enjoyable predominant(args: Array) {
    io.ktor.server.netty.EngineMain.predominant(args)
}

enjoyable Software.module() {

  configureTemplating()
  //configureRouting()
  set up(RequestLoggingPlugin)

  configureDatabases()
}

Discover I’ve commented out the previous configureRouting() name, so it received’t battle with our new routes.

To do a fast take a look at of those routes, we will use the curl command-line device. This line inserts a row:


$ curl -X POST -H "Content material-Sort: software/x-www-form-urlencoded" -H "Host: localhost:8080" -d "quote=FooBar.&writer=William+Shakespeare" http://localhost:8080/quotes

And this one outputs the prevailing rows:


$ curl http://localhost:8080/quotes

Utilizing HTMX for interactive views

Now let’s soar proper into making a UI to work together with the companies utilizing HTMX. We wish a web page that lists the prevailing quotes and a type that we will use to submit a brand new quote. The quote can be dynamically inserted into the checklist on the web page, and not using a web page reload.

To realize these objectives, we’ll want a route that attracts all the pieces on the outset after which one other route that accepts the shape POST and returns the markup for the newly inserted quote. We’ll add these to the Database.kt routes for simplicity.

Right here is the /quotes-htmx web page that provides us the preliminary checklist and type:


get("/quotes-htmx") {
        val quotes = quoteService.checklist()    
        name.respondHtml {
          head {
            script(src = "https://unpkg.com/htmx.org@1.9.6") {} 
          }
        physique {
          h1 { +"Quotes (HTMX)" }
          div {
            id = "quotes-list"
            quotes.forEach { quote ->
              div {
                p { +quote.quote }
                p { +"― ${quote.writer}" }
              }
            }
          }
          type(technique = FormMethod.put up, motion = "/quotes", encType = FormEncType.applicationXWwwFormUrlEncoded) {
            attributes["hx-post"] = "/quotes"
            attributes["hx-target"] = "#quotes-list"
            attributes["hx-swap"] = "beforeend" 
            div {
              label { +"Quote:" }
              textInput(title = "quote")
            }
            div {
              label { +"Creator:" }
              textInput(title = "writer")
            }
            button(sort = ButtonType.submit) { +"Add Quote" }
          }
        }
      }
    }

First, we seize the checklist of quotes from the service. Then we begin outputting the HTML, starting with a head component that features the HTMX library from a CDN. Subsequent, we open a physique tag and render a title (H1) component adopted by a div with the id of quotes-list. Discover that id is dealt with as a name from contained in the div block, as a substitute of as an attribute on div

Inside quotes-list, we iterate over the quotes assortment and output a div with every quote and writer. (Within the Express version of this application, we used a UL and checklist gadgets. We may have performed the identical right here.)

After the checklist comes the shape, which units a number of non-standard attributes (hx-post, hx-target, and hx-swap) on the attributes assortment. These can be set on the output HTML type component.

Now all we want is a /quotes route to just accept the incoming quotes from POST and reply with an HTML fragment that represents the brand new quote to be inserted into the checklist:


put up("/quotes") {
      val parameters = name.receiveParameters()
      val quote = parameters["quote"] ?: ""
      val writer = parameters["author"] ?: ""
      val newQuote = Quote(quote = quote, writer = writer)
      val id = quoteService.create(newQuote)
      val createdQuote = quoteService.learn(id) 
      name.respondHtml(HttpStatusCode.Created) { 
        physique{
        div {
          p { +createdQuote.quote }
          p { +"― ${createdQuote.writer}" }
        }
    }
  }

That is fairly easy. One wrinkle is that Kotlin’s HTML DSL doesn’t wish to ship an HTML fragment, so now we have to wrap our quote markup in a physique tag, which shouldn’t be there. (There’s a easy workaround we’re skipping for simplicity, present in this project referred to as respondHtmlFragment). It appears doubtless that producing HTML fragments will ultimately turn into a normal a part of the HTML DSL.

Aside from that, we simply parse the shape and use the service to create a Quote after which use the brand new Quote to generate the response, which HTMX will use to replace the UI dynamically.

Conclusion

We went quick and lean with this instance, to discover the essence of Ktor. Nevertheless, now we have all the weather of a extremely performant and dynamic stack with out a lot overhead. As a result of Kotlin is constructed on prime of the JVM it provides you entry to all the pieces Java does. That, coupled with its highly effective union of object-oriented and practical programming, and DSL capabilities, makes Kotlin a compelling server-side language. You need to use it for constructing purposes with conventional RESTful JSON endpoints, or with dynamic HTMX-powered UIs, as we’ve seen right here.

See my GitHub repository for the entire supply code for the Ktor-HTMX software instance.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!
Por favor ingrese su nombre aquí