Kotlin-Ktor-MySql-Webservice Development
GitHub Full Source Code:
https://github.com/Abhinaw/nearbyapi_abhi/tree/master
I was getting demand for Ktor Kotlin Backend Development with Sample MySql Database Code So in this blog I will give you all full access to the source code on my Github URL.
SQL file is also committed to the parent folder and you can easily import and configure the project.
Why Ktor?
Use what you need. Ktor allows you to transparently configure only the functionality your project requires. No magic involved!
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
Scales as you need it. Using Kotlin coroutines, Ktor is truly asynchronous and highly scalable. Use the power of non-blocking development without the callback nightmare.
Java Threads Vs Kotlin Coroutines
I myself as a programmer/developer I tested it by writing code that's what I do. I read somewhere that Courotines is much better than Java threads I did not believe it initially but to test it I did one thing like I have an 8GB RAM Multi-core Windows operating system.
To test it, I wrote a simple Java and Kotlin Program where what i did is like created 100 threads from Fork/Join Pool of threads and the same concept with Kotlin Coroutines and the result was amazing to me.
In the case of Java, my system got crashed and it consumes my all resources when I checked my task manager it was consuming 100% of my system but in the case of Coroutines with the same program, it only consumed my 80% resource and that is an amazing part to me and even the program did not crash. this makes me a little uncomfortable worked on Java for more than 10 years till now and maybe it proves how powerful Kotlin is. As a developer/programmer I believe it because I checked it by doing my hands dirty with it.
Thoughts for Food for Android Developers:
I strongly believe that it will take time to improve like Spring Eco-System but I find it very useful in terms of Mobile Application Development because in every mobile app we need middle-tier for data transaction and there it saves like earlier for that Mobile Developer can not cater that requirement but now they can easily cater the same because they can write the required code for Middle-Tier certainly Android Developers.
And there is one more advantage that I can see is like you can create a single project structure to care both the platforms I mean Front-End and Middle-Tier, Yes there can be an architectural difference but indeed very help full when it comes to CI/CD.
Ktor is a framework to easily build connected applications – web applications, HTTP services, mobile and browser applications. Modern connected applications need to be asynchronous to provide the best experience to users, and Kotlin coroutines provide awesome facilities to do it in an easy and straightforward way.
While not yet entirely there, the goal of Ktor is to provide an end-to-end multiplatform application framework for connected applications. Currently, JVM client and server scenarios are supported, as well as JavaScript, iOS, and Android clients, and we are working on bringing server facilities to native environments, and client facilities to other native targets.
When you create a project in IntelliJ IDE:
Name: Specify a project name.
Location: Specify a directory for your project.
Build System: Choose the desired build system. This can be Gradle with Kotlin or Groovy DSL or Maven.
Website: Specify a domain used to generate a package name.
Artifact: This field shows a generated artifact name.
Ktor Version: Choose the required Ktor version.
Engine: Select an engine used to run a server.
Configuration in: Choose whether to specify server parameters in code or in a HOCON file.
On the next page, you can choose a set of features- building blocks that provide common functionality of a Ktor application, for example, authentication, serialization, and content-encoding, compression, cookie support, and so on.
application.conf:
ktor {
deployment {
port = 8080
}
application {
modules = [ com.jetbrains.handson.chat.server.ApplicationKt.module ]
}
}
Application.Kt:
package com.abhinearby
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.http.cio.websocket.*
import io.ktor.jackson.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.websocket.*
import org.slf4j.event.Level
import java.util.*
import kotlin.collections.LinkedHashSet
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(WebSockets)
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/") }
}
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}
routing {
/* For only Testing*/
get("/json/jackson") {
call.respond(mapOf("hello" to "world"))
}
/********************Near By App Start*********************/
val repository: RegistrationRepository = MySqlRegistrationToDoRepository()
post("/registration")
{
val todoDraft = call.receive<RegistrationDraft>()
val todo = repository.addToRegistrationTable(todoDraft)
call.respond(todo)
}
get("/getallregistrationdata")
{
call.respond(repository.getAllRegisteredTableData())
}
put("/registration/{id}")
{
val todoDraft = call.receive<RegistrationDraft>()
val todoId = call.parameters["id"]?.toIntOrNull()
if (todoId == null) {
call.respond(HttpStatusCode.BadRequest, "id parameter has to be a number!")
return@put
}
val update = repository.updateToRegistrationTable(todoId, todoDraft)
if (update) {
call.respond(HttpStatusCode.OK)
} else {
call.respond(HttpStatusCode.NotFound, "registered user with the id $todoId not found")
}
}
/**************Login********************/
val loginRepo: LoginRepository = MySqlLoginToDoRepository()
post("/login")
{
val todoDraft = call.receive<LoginDraft>()
val todo = loginRepo.validateToLoginTable(todoDraft)
call.respond(todo)
}
get("/getlogindata")
{
val repository: LoginRepository = MySqlLoginToDoRepository()
call.respond(repository.getAllLoginTableData())
}
///////////////////////////Websocket Chat System////////////////////////////
val connections = Collections.synchronizedSet<Connection?>(LinkedHashSet())
webSocket("/chat") {
println("Adding user!")
val thisConnection = Connection(this)
connections += thisConnection
try {
send("You are connected! There are ${connections.count()} users here.")
for (frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText()
val textWithUsername = "[${thisConnection.name}]: $receivedText"
connections.forEach {
it.session.send(textWithUsername)
}
}
} catch (e: Exception) {
println(e.localizedMessage)
} finally {
println("Removing $thisConnection!")
connections -= thisConnection
}
}
/////////////////////////////////////Chat////////////////////////////
}
}
DatanbaseManager.Kt:
package com.abhinearby.database
import org.ktorm.database.Database
import org.ktorm.dsl.eq
import org.ktorm.dsl.insertAndGenerateKey
import org.ktorm.dsl.update
import org.ktorm.entity.sequenceOf
import org.ktorm.entity.toList
import java.sql.Connection
class DatabaseManager {
private val hostname = "127.0.0.1"
private val databaseName = "nearby_abhi"
private val username = "root"
private val password = ""
private val ktormDatabase: Database
init {
val jdbcUrl = "jdbc:mysql://$hostname:3306/$databaseName?user=$username&password=$password&useSSL=false"
ktormDatabase = Database.connect(jdbcUrl)
}
/************Registration for the App*************/
fun addRegistrationDataToDB(draft: RegistrationDraft): Registration {
val insertedId = ktormDatabase.insertAndGenerateKey(DBRegistrationTable)
{
set(DBRegistrationTable.firstname, draft.firstname)
set(DBRegistrationTable.lastname, draft.lastname)
set(DBRegistrationTable.phoneno, draft.phoneno)
set(DBRegistrationTable.address, draft.address)
set(DBRegistrationTable.photo, draft.photo)
set(DBRegistrationTable.username, draft.username)
set(DBRegistrationTable.password, draft.password)
} as Int
val registration = Registration(
insertedId,
draft.firstname,
draft.lastname,
draft.phoneno,
draft.address,
draft.photo,
draft.username,
draft.password
)
insertRow(registration)
return registration
}
private fun insertRow(registration: Registration) {
val id = registration.id
val username = registration.username
val password = registration.password
val fid = registration.id
val sql =
"INSERT INTO `login`(`id`, `username`, `password`, `fid`) VALUES ('$id','$username','$password','$fid')"
ktormDatabase.useConnection { connection: Connection ->
with(connection) {
createStatement().execute(sql)
}
println("inserted!!")
}
}
fun updateRegistrationDataToDB(id: Int, draft: RegistrationDraft): Boolean {
val updatedRows = ktormDatabase.update(DBRegistrationTable)
{
set(DBRegistrationTable.firstname, draft.firstname)
set(DBRegistrationTable.lastname, draft.lastname)
set(DBRegistrationTable.phoneno, draft.phoneno)
set(DBRegistrationTable.address, draft.address)
set(DBRegistrationTable.photo, draft.photo)
set(DBRegistrationTable.username, draft.username)
set(DBRegistrationTable.password, draft.password)
where {
it.id eq id
}
}
return updatedRows > 0
}
fun getAllRegisteredTableData(): List<DBNearByRegistrationEntity> {
return ktormDatabase.sequenceOf(DBRegistrationTable).toList()
}
/**************************Registration End*************************************/
/************************Login **************************/
fun validateLogin(draft: LoginDraft): LoginDraft {
val table = "login"
val sql = "SELECT * FROM $table"
ktormDatabase.useConnection { connection: Connection ->
val rs = connection.createStatement().executeQuery(sql)
while (rs.next()) {
println(
"id: ${rs.getInt("id")}\t" +
"username: $${rs.getString("username")}\t" +
"password: ${rs.getString("password")}\t" +
"fid: $${rs.getDouble("fid")}"
)
val userName = rs.getString("username")
val pass = rs.getString("password")
if (draft.username.equals(userName)
&& draft.password.equals(pass)
) {
break
}
}
}
return draft!!
}
fun getAllLoginTableData(): List<DBNearByLoginEntity> {
return ktormDatabase.sequenceOf(DBLoginTable).toList()
}
}
Entity.kt:
import org.ktorm.entity.Entity
import org.ktorm.schema.Table
import org.ktorm.schema.int
import org.ktorm.schema.varchar
object DBRegistrationTable : Table<DBNearByRegistrationEntity>("dbregistrationtable") {
val id = int("id").primaryKey().bindTo { it.id }
val firstname = varchar("firstname").bindTo { it.firstname }
val lastname = varchar("lastname").bindTo { it.lastname }
var phoneno = varchar("phoneno").bindTo { it.phoneno }
var address = varchar("address").bindTo { it.address }
var photo = varchar("photo").bindTo { it.photo }
var username = varchar("username").bindTo { it.username }
var password = varchar("password").bindTo { it.password }
}
interface DBNearByRegistrationEntity : Entity<DBNearByRegistrationEntity> {
companion object : Entity.Factory<DBNearByRegistrationEntity>()
val id: Int
val firstname: String
val lastname: String
val phoneno: String
val address: String
val photo: String
val username : String
val password : String
}
RegistrationRepository.kt:
import com.abhinearby.entities.registration.Registration
import com.abhinearby.entities.registration.RegistrationDraft
interface RegistrationRepository {
fun addToRegistrationTable(draft: RegistrationDraft): Registration
fun updateToRegistrationTable(id: Int, draft: RegistrationDraft): Boolean
fun getAllRegisteredTableData() : List<Registration>
}
PostMan Webservcie Request-Response Example:
1)http://localhost:8081/getallregistrationdata
[
{
"id": 29,
"firstname": "abhi",
"lastname": "tripathi",
"phoneno": "8130752107",
"address": "siddha happyville",
"photo": "",
"username": "abhi",
"password": "abhi"
},
{
"id": 30,
"firstname": "abhinawtripathi34@gmail.com",
"lastname": "tripathi",
"phoneno": "8130752107",
"address": "cyfydufffi",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 31,
"firstname": "abhinawtripathi34@gmail.com",
"lastname": "tripathi",
"phoneno": "8130752107",
"address": "cyfydufffi",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 32,
"firstname": "buvu",
"lastname": "cyxx",
"phoneno": "753577789",
"address": "vyuvvjvj",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 33,
"firstname": "g7g7",
"lastname": "cyyc",
"phoneno": "8765677999",
"address": "gyuj",
"photo": "",
"username": "tests107@mailsac.com",
"password": "test"
},
{
"id": 34,
"firstname": "g7g7",
"lastname": "cyyc",
"phoneno": "8765677999",
"address": "gyuj",
"photo": "",
"username": "tests107@mailsac.com",
"password": "test"
},
{
"id": 35,
"firstname": "g7g7",
"lastname": "cyyc",
"phoneno": "8765677999",
"address": "gyuj",
"photo": "",
"username": "tests107@mailsac.com",
"password": "test"
},
{
"id": 36,
"firstname": "g7g7",
"lastname": "cyyc",
"phoneno": "8765677999",
"address": "gyuj",
"photo": "",
"username": "tests107@mailsac.com",
"password": "test"
},
{
"id": 37,
"firstname": "test",
"lastname": "test",
"phoneno": "875678887",
"address": "hhjbjhhj",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 38,
"firstname": "test",
"lastname": "test",
"phoneno": "875678887",
"address": "hhjbjhhj",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 39,
"firstname": "test",
"lastname": "test",
"phoneno": "875678887",
"address": "hhjbjhhj",
"photo": "",
"username": "test",
"password": "test"
},
{
"id": 40,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 41,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 42,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 43,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 44,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 45,
"firstname": "",
"lastname": "",
"phoneno": "",
"address": "",
"photo": "",
"username": "",
"password": ""
},
{
"id": 46,
"firstname": "abhi",
"lastname": "sbhi",
"phoneno": "6858845",
"address": "ifdjdffuff kgufpfug bufstzcufuc kvfuk. kuddhjcfucucyddcpjvucyf",
"photo": "",
"username": "abhi",
"password": "abhi"
}
]
2)http://localhost:8081/registration/29 (PUT Servcie)
request:
{
"id": 29,
"firstname": "abhinaw",
"lastname": "tri",
"phoneno": "8130752107",
"address": "howrah",
"photo": "/abc.png",
"username": "abhinaw",
"password": "abhinaw"
}
Response:200OK
3)http://localhost:8081/registration (POST Service)
Websocket Communication System:
///////////////////////////Chat////////////////////////////
val connections = Collections.synchronizedSet<Connection?>(LinkedHashSet())
webSocket("/chat") {
println("Adding user!")
val thisConnection = Connection(this)
connections += thisConnection
try {
send("You are connected! There are ${connections.count()} users here.")
for (frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText()
val textWithUsername = "[${thisConnection.name}]: $receivedText"
connections.forEach {
it.session.send(textWithUsername)
}
}
} catch (e: Exception) {
println(e.localizedMessage)
} finally {
println("Removing $thisConnection!")
connections -= thisConnection
}
}
/////////////////////////////////////Chat////////////////////////////
Very easy to communicate and very simple code for example.
Output Window: