4

I want to figure out how the akka-http should be deployed with appropriate frontend app.

Let's assume that we have akka-http application which provides some API. It's located in repo A. For this server side app we have frontend app (Angular or REACT or whatever). It's located in repo B.

So how it should be deployed correctly together?

I'm overviewing following scenario:

  1. Checkout the A repo
  2. Navigate to /src/main/public and checkout the B repo
  3. Build the akka-http repo with help of SBT in jar
  4. Deploy the jar on the dedicated server

Is it bad scenario?

Alex Fruzenshtein
  • 2,846
  • 6
  • 32
  • 53
  • What do you mean "deployed together"? Why would you deploy a client side & server side application "together"? The whole point of "microservices" is that the server logic and client logic aren't mixed together, therefore they can be deployed independently of each other... – Ramón J Romero y Vigil Apr 17 '17 at 13:38
  • @RamonJRomeroyVigil correct. That's why I want to know what is the best practice in akka http to deploy API app + client side Angular e.g. – Alex Fruzenshtein Apr 17 '17 at 14:43

2 Answers2

4

The preferable way would probably be to deploy your frontend application separately. But it's also a common practice to deploy backend and frontend apps as a single bundle (e.g. JHipster practice). Nevertheless, I can answer how to do it as bundled.

For convenience, you can create two modules in a single SBT project - service and ui (root directory for the frontend app). One for the backend and another one for the frontend.

SBT settings

Depending on the frontend framework you use, we will modify SBT settings a bit. Let's say we use React. By default, if we run npm build or yarn build, frontend bundled files will end up in ui/build directory by default. We will mark build directory as "resources" in our ui module:

lazy val `ui` =
  project
    .in(file("./ui"))
    .settings(
      resourceGenerators in Compile += buildUi.init
    )

lazy val buildUi = taskKey[Seq[File]]("Generate UI resources") := {
  val webapp = baseDirectory.value / "build"
  val managed = resourceManaged.value
  for {
    (from, to) <- webapp ** "*" pair Path.rebase(webapp, managed / "main" / "ui")
  } yield {
    Sync.copy(from, to)
    to
  }
}

service module will depend on ui module:

lazy val `service` =
  project
    .in(file("./service"))
    .dependsOn(`ui`)

Now, service can pick up resource files from ui after you build your React application.

How to serve the frontend app alongside backend API

Let's presume that you created API routes that will be consumed by the frontend. Create a pathPrefix route beginning with "api", "v1", "api/v1" or whatever, and you'll see later why do we need this prefix:

pathPrefix("api") { // api routes }

And create another route that will serve frontend assets:

def assets: Route =
      getFromResourceDirectory("ui") ~ 
      pathPrefix("") {
        get {
          getFromResource("ui/index.html", ContentType(`text/html`, `UTF-8`))
        }
      }

And then join two routes this way:

pathPrefix("api") seal { // api routes } ~ assets

Voila!

Let's explain these routes.

First, we want to match our API routes, since they are at the specific URL. assets are matching all other URL's. This means, visiting any URL that doesn't start with /api will return React's static resources.

Next, let's disect assets routes:

First one is getFromResourceDirectory("ui"). Remember when we marked ui/build directory as resources directory? This means that our React resources are located in target/scala/classes/ui directory and we can simply serve them this way.

The second one and the trickiest one:

pathPrefix("") {
  get {
    getFromResource("ui/index.html", ContentType(`text/html`, `UTF-8`))
  }
}

This means that any URL that doesn't start with /api will match this route and return React's index.html file. But you might ask: "Why didn't we simply use pathSingleSlash?" This is exactly the tricky part - frontend framework routing.

Let's say that we use pathSingleSlash (we expose React's resources only on the root "/"). And let's say that we use e.g. react-router-dom for routing in React application. And we have a nice "/users" route that displays users in a table. We go to "/" -> Akka HTTP server serves static files and all works as expected -> we click some button that goes to "/users" and all works as expected again since React router is doing routing now -> then we refresh our page, and we get a 404 error. Why? Because it's a known route in React app, but an unknown route in Akka HTTP server. Therefore, at any route except on API route ("/api") we want to expose our React resources. With pathPrefix(""), we are doing exactly that. When we go to "/users" page and hit refresh, Akka HTTP still returns React's resources and the rest of the routing magic is done by React - "/users" page renders successfully.

You can also create CORS route so you can independently run frontend from backend during the development stage.

Happy hakking.

Branislav Lazic
  • 14,388
  • 8
  • 60
  • 85
1

This may not be quite what you are looking but I have a seed project which contains Akka Http and Angular 5 all in one repo (you asked for 2 repos) configured to be deployed to Heroku in a single dyno.

https://github.com/jdschmitt/akka-angular-heroku

Hope it helps you.

ShatyUT
  • 1,375
  • 2
  • 14
  • 28