├── .scalafmt.conf
├── .gitignore
├── shared
└── src
│ └── app
│ └── Api.scala
├── .travis.yml
├── www
└── index.html
├── jvm
└── src
│ └── app
│ ├── ApiImpl.scala
│ └── Server.scala
├── js
└── src
│ └── app
│ ├── Client.scala
│ └── Main.scala
├── README.md
└── mill
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = "2.3.2"
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | .idea
3 | .idea_modules
4 | .metals
5 | .bloop
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/shared/src/app/Api.scala:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | trait Api[F[_]] {
4 | def increment(i: Int): F[Int]
5 |
6 | def decrement(i: Int): F[Int]
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | node_js:
3 | - 12
4 | script:
5 | - ./mill -i __.compile
6 | - ./mill -i __.fastOpt
7 | cache:
8 | directories:
9 | - $HOME/.mill
10 | - $HOME/.cache
11 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Scala Fullstack app
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/jvm/src/app/ApiImpl.scala:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import scala.concurrent.Future
4 |
5 | object ApiImpl extends Api[Future] {
6 | def increment(i: Int): Future[Int] = Future.successful(i + 1)
7 |
8 | def decrement(i: Int): Future[Int] = Future.successful(i - 1)
9 | }
10 |
--------------------------------------------------------------------------------
/js/src/app/Client.scala:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import boopickle.Default._
4 | import chameleon.ext.boopickle._
5 | import java.nio.ByteBuffer
6 | import covenant.http.HttpClient
7 | import scala.concurrent.Future
8 | import scalajs.concurrent.JSExecutionContext.Implicits.queue
9 |
10 |
11 | object Client {
12 | val client = HttpClient[ByteBuffer]("/api")
13 | val api: Api[Future] = client.wire[Api[Future]]
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scala-fullstack
2 |
3 | Full stack Scala skeleton project using Akka-http, Scala.js, Laminar, Sloth, Boopickle.
4 |
5 | It uses the [Mill build tool](http://www.lihaoyi.com/mill/).
6 |
7 | To start the server:
8 |
9 | ```bash
10 | ./mill -watch jvm.runBackground
11 | ```
12 |
13 | in another shell you need to generate the javascript output:
14 |
15 | ```bash
16 | ./mill -watch js.fastOpt
17 | ```
18 |
19 | then you can open the served page at `http://localhost:8080`.
20 |
--------------------------------------------------------------------------------
/js/src/app/Main.scala:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import com.raquo.laminar.api.L._
4 | import org.scalajs.dom.document
5 |
6 | import scala.util.Try
7 |
8 | object Main {
9 | val nameInput = input(
10 | placeholder := "Insert your name"
11 | )
12 |
13 | val nameStream = nameInput.events(onInput).mapTo(nameInput.ref.value)
14 |
15 | val incrementInput = input(
16 | placeholder := "Insert a number"
17 | )
18 |
19 | val incrementStream =
20 | incrementInput.events(onInput).mapTo(incrementInput.ref.value)
21 |
22 | val resultStream: EventStream[Int] = incrementStream.map { s =>
23 | val optionInt = Try(s.toInt).toOption
24 | val result = optionInt.map(Client.api.increment) //executed on server!
25 | result
26 | .map { f =>
27 | EventStream.fromFuture(f)
28 | }
29 | .getOrElse(EventStream.fromSeq(Seq.empty, emitOnce = true))
30 | }.flatten
31 |
32 | val app = div(
33 | nameInput,
34 | p("Hello ", child.text <-- nameStream),
35 | h3("Server implemented function:"),
36 | incrementInput,
37 | p("result from server: ", child.text <-- resultStream.map(_.toString))
38 | )
39 |
40 | def main(args: Array[String]): Unit = {
41 | render(document.body, app)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/jvm/src/app/Server.scala:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import java.nio.ByteBuffer
4 |
5 | import akka.actor.ActorSystem
6 | import akka.event.Logging
7 | import akka.http.scaladsl.Http
8 | import akka.http.scaladsl.model.headers.`Access-Control-Allow-Origin`
9 | import akka.http.scaladsl.server.Directives._
10 | import akka.http.scaladsl.server.directives.DebuggingDirectives
11 | import akka.stream.ActorMaterializer
12 | import covenant.http.AkkaHttpRoute
13 | import covenant.http.ByteBufferImplicits._
14 | import sloth.Router
15 | import boopickle.Default._
16 | import chameleon.ext.boopickle._
17 | import cats.implicits._
18 |
19 | import scala.concurrent.Future
20 |
21 | object Server {
22 | implicit val system = ActorSystem("my-system")
23 | implicit val materializer = ActorMaterializer()
24 | implicit val executionContext = system.dispatcher
25 |
26 | val router = Router[ByteBuffer, Future].route[Api[Future]](ApiImpl)
27 |
28 | val apiRoute = pathPrefix("api")(AkkaHttpRoute.fromFutureRouter(router))
29 |
30 | val filesRoutes = pathPrefix("") {
31 | getFromDirectory("www")
32 | } ~ path("") {
33 | getFromFile("www/index.html")
34 | } ~ path("index.js") {
35 | getFromFile("out/js/fastOpt/dest/out.js")
36 | }
37 |
38 | val loggedRoute =
39 | DebuggingDirectives.logRequestResult("Client Rest", Logging.InfoLevel)(
40 | filesRoutes ~ apiRoute
41 | )
42 |
43 | def main(args: Array[String]): Unit = {
44 | Http().bindAndHandle(loggedRoute, interface = "0.0.0.0", port = 8080)
45 |
46 | println(s"Server online at http://0.0.0.0:8080/")
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/mill:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # This is a wrapper script, that automatically download mill from GitHub release pages
4 | # You can give the required mill version with MILL_VERSION env variable
5 | # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION
6 | DEFAULT_MILL_VERSION=0.8.0-6-bbbf19
7 |
8 | set -e
9 |
10 | if [ -z "$MILL_VERSION" ] ; then
11 | if [ -f ".mill-version" ] ; then
12 | MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)"
13 | elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then
14 | MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2)
15 | else
16 | MILL_VERSION=$DEFAULT_MILL_VERSION
17 | fi
18 | fi
19 |
20 | if [ "x${XDG_CACHE_HOME}" != "x" ] ; then
21 | MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download"
22 | else
23 | MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download"
24 | fi
25 | MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}"
26 |
27 | version_remainder="$MILL_VERSION"
28 | MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
29 | MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
30 |
31 | if [ ! -x "$MILL_EXEC_PATH" ] ; then
32 | mkdir -p $MILL_DOWNLOAD_PATH
33 | if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then
34 | ASSEMBLY="-assembly"
35 | fi
36 | DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download
37 | MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION${ASSEMBLY}"
38 | curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL"
39 | chmod +x "$DOWNLOAD_FILE"
40 | mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH"
41 | unset DOWNLOAD_FILE
42 | unset MILL_DOWNLOAD_URL
43 | fi
44 |
45 | unset MILL_DOWNLOAD_PATH
46 | unset MILL_VERSION
47 |
48 | exec $MILL_EXEC_PATH "$@"
49 |
--------------------------------------------------------------------------------