Akka-http rest routes

Hi
This could be a newbie question or I’m missing some dumb thing and I didn’t try in the lightbend forum (yet), but lets go… I’d like to implement these two routes:

/users/:userid
/users/:userid/books

This routing in expressjs is pretty much straightforward:

app.get('/user/:userid', (req, res) => {
  const userid = req.params.userid;
  res.send({ user: userid });
});

app.get('/user/:userid/books', (req, res) => {
  const userid = req.params.userid;
  res.send({ books: 36, user: userid });
})

But after searching in the docs and struggling I couldn’t do the same with the akka routing DSL. I guess my best effort is as follows:

import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.MethodDirectives.get
import akka.http.scaladsl.server.PathMatchers.IntNumber
import akka.util.ByteString

trait BookRoutes {

  /*
    /users/:userId
    /users/:userId/books/
  */
  lazy val bookRoutes = pathPrefix("user") {
    path(IntNumber) { userId =>
      get {
        complete(ByteString(s"user: $userId"))
      } ~
      pathSuffix("books") {
        get {
          complete(ByteString(s"books for user $userId"))
        }
      }
    }
  }

}

This works for /user/21 but not for /user/21/books ( -> 404).

May anyone provide a hint about this can be achieved based on :point_up: (well, or not :relaxed:)

Thanks in advance (and merry new year)

There’s two issues here:

  • path(IntNumber) will only match a complete (sub-)path - it won’t match if there’s a trailing “/books”. Use pathPrefix here, too.
  • Route chaining will only try the next route if the current is rejected. You’ll have to put more specific/more constrained routes first, just as in Scala pattern matching.
pathPrefix("user") {
  pathPrefix(IntNumber) { userId =>
    pathSuffix("books") {
      get {
        complete(ByteString(s"books for user $userId"))
      }
    } ~
    get {
      complete(ByteString(s"user: $userId"))
    }
  }
}

You could factor out the shared get directive.

pathPrefix("user") {
  pathPrefix(IntNumber) { userId =>
    get {
      pathSuffix("books") {
        complete(ByteString(s"books for user $userId"))
      } ~
      complete(ByteString(s"user: $userId"))
    }
  }
}

@sangamon++ -> many thanks, nice explanation :relaxed:

I also found out I could replace pathSuffix("books") with path("books") in this particular case reaching the same result. Actually, I had missed the complete word when checking the docs for path directive.