Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web API example using scala-fx and Http4s library. #23

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,16 @@ lazy val scalafxSettings: Seq[Def.Setting[_]] =
"org.scalacheck" %% "scalacheck" % "1.15.4" % Test
)
)

lazy val examples = project
.dependsOn(`scala-fx`)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "3.3.0",
"org.http4s" %% "http4s-dsl" % "0.23.6",
"org.http4s" %% "http4s-blaze-server" % "0.23.6",
"org.http4s" %% "http4s-circe" % "0.23.6",
"io.circe" %% "circe-core" % "0.14.1",
"io.circe" %% "circe-generic" % "0.14.1"
)
)
78 changes: 78 additions & 0 deletions examples/src/main/scala/http4s/MainHttp4s.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package examples.http4s

import org.http4s.blaze.server.BlazeServerBuilder
import scala.concurrent.ExecutionContext.Implicits.global

import cats.effect.IO
import cats.syntax.all._

import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router

import org.http4s.circe._
import org.http4s.circe.CirceEntityCodec.{circeEntityDecoder, circeEntityEncoder}

import io.circe.generic.auto._

import scala.collection.mutable
import cats.effect.ExitCode
import cats.effect.IOApp

case class User(val id: Int, val email: String, val age: Int)

object MemoryRepo:
private val db = mutable.Map[Int, User](
(1 -> User(1, "[email protected]", 27)),
(2 -> User(2, "[email protected]", 34)),
(3 -> User(3, "[email protected]", 41)),
(4 -> User(4, "[email protected]", 48))
)

def findBy(id: Int): Option[User] = db.get(id)
def findAll: List[User] = db.values.toList
def save(user: User): Unit = db.addOne((user.id -> user))
def update(id: Int, user: User): Unit = db.update(id, user)
def delete(id: Int): Option[Unit] = db.remove(id).map(_ => ())
end MemoryRepo

object Service:
def findBy(id: Int): Option[User] = MemoryRepo.findBy(id)
def save(user: User): Unit = MemoryRepo.save(user)
def update(id: Int, user: User): Unit = MemoryRepo.update(id, user)
def findAll: List[User] = MemoryRepo.findAll
def delete(id: Int): Option[Unit] = MemoryRepo.delete(id)
end Service

object Routing extends Http4sDsl[IO]:

private val userRoutes: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root => Ok(IO(Service.findAll))

case GET -> Root / IntVar(userId) =>
Ok(IO(Service.findBy(userId)))

case rq @ POST -> Root =>
rq.asJsonDecode[User].flatMap(user => IO(Service.save(user))).flatMap(Created(_))

case rq @ PUT -> Root / IntVar(userId) =>
rq.asJsonDecode[User].flatMap(user => IO(Service.update(userId, user))).flatMap(Ok(_))

case DELETE -> Root / IntVar(userId) =>
IO(Service.delete(userId)).flatMap(Ok(_))
}

val userService = Router("/users" -> userRoutes).orNotFound

end Routing

object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO](global)
.bindHttp(8080, "localhost")
.withHttpApp(Routing.userService)
.serve
.compile
.drain
.as(ExitCode.Success)
}
100 changes: 100 additions & 0 deletions examples/src/main/scala/http4s/MainHttp4sFx.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package examples.http4s

import fx._

import org.http4s.blaze.server.BlazeServerBuilder
import scala.concurrent.ExecutionContext.Implicits.global

import cats.effect.IO
import cats.syntax.all._
import cats.data.Kleisli

import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router

import org.http4s.circe._
import org.http4s.circe.CirceEntityCodec.{circeEntityDecoder, circeEntityEncoder}

import io.circe.generic.auto._

import scala.collection.mutable

//Assuming the cats IO binding is something like this
object IOBind:
extension [A](io: IO[A])
def bind: A % Bind = {
import cats.effect.unsafe.IORuntime
implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global
io.unsafeRunSync()
}
end IOBind

case class FxUser(val id: Int, val email: String, val age: Int)

object FxMemoryRepo:
private val db = mutable.Map[Int, FxUser](
(1 -> FxUser(1, "[email protected]", 27)),
(2 -> FxUser(2, "[email protected]", 34)),
(3 -> FxUser(3, "[email protected]", 41)),
(4 -> FxUser(4, "[email protected]", 48))
)

def findBy(id: Int): FxUser % Bind % Control[None.type] = db.get(id).bind
def findAll: List[FxUser] % Bind = db.values.toList
def delete(id: Int): Unit % Bind % Control[None.type] = db.remove(id).map(_ => ()).bind
def save(FxUser: FxUser): Unit % Bind = db.addOne((FxUser.id -> FxUser))
def update(id: Int, FxUser: FxUser): Unit % Bind = db.update(id, FxUser)
end FxMemoryRepo

object FxService:
def findBy(id: Int): FxUser % Control[None.type] = FxMemoryRepo.findBy(id)
def delete(id: Int): Unit % Control[None.type] = FxMemoryRepo.delete(id)
def save(FxUser: FxUser): Unit % Bind = FxMemoryRepo.save(FxUser)
def update(id: Int, FxUser: FxUser): Unit % Bind = FxMemoryRepo.update(id, FxUser)
def findAll: List[FxUser] % Bind = FxMemoryRepo.findAll
end FxService

object FxRouting extends Http4sDsl[IO]:

import IOBind._ // Lines 66, 70, 75, 79 Require IO Binding

private val FxUserRoutes: HttpRoutes[IO] % Bind % Control[None.type] = HttpRoutes.of[IO] {
case GET -> Root => Ok(IO(FxService.findAll).bind)

case GET -> Root / IntVar(userId) =>
Ok(IO(FxService.findBy(userId)).bind)

case rq @ POST -> Root => {
val FxUser = rq.asJsonDecode[FxUser].bind
Created(IO(FxService.save(FxUser)).bind)
}

case rq @ PUT -> Root / IntVar(userId) => {
val FxUser = rq.asJsonDecode[FxUser].bind
Ok(IO(FxService.update(userId, FxUser)).bind)
}

case DELETE -> Root / IntVar(userId) =>
Ok(IO(FxService.delete(userId)).bind)
}

val userService: Kleisli[IO, Request[IO], Response[IO]] % Control[None.type] = Router(
"/users" -> FxUserRoutes).orNotFound

end FxRouting

@main def http4sFxExample =
import IOBind._ // effect Require IO Binding
import fx.runtime

val effect: Unit % Bind % Control[None.type] =
BlazeServerBuilder[IO](global)
.bindHttp(8081, "localhost")
.withHttpApp(FxRouting.userService)
.serve
.compile
.drain
.bind

run(effect)