Skip to main content

Integrations

On existing schema and server#

OverLayer was built to be very easy to integrate to existing akka-http server.

Existing server#

Let's say, you have a server similar to this.

object Main {  implicit val system = ActorSystem(Behaviors.empty, "main-actor-system")
  val route: Route =    (path("graphql") & post & entity(as[JsValue])) { json =>      // ...    }
  def main(args: Array[String]): Unit = {    Http()      .newServerAt("localhost", 4000)      .bind(route)  }}

Integrating OverLayer#

Simply plug in the OverTransportLayer, the new system behavior, and the appropriate route.

Integrating on existing GraphQL Server
object Main {  implicit val system = OverTransportLayer.makeSystem("main-actor-system")
  val transport = OverTransportLayer(...)
  val route: Route =    (path("graphql") & post & entity(as[JsValue])) { json =>      // ...    } ~ (path("graphql" / "websocket")) {      transport.applyMiddleware(...)    }
  def main(args: Array[String]): Unit = {    Http()      .newServerAt("localhost", 4000)      .bind(route)  }}

Integrating request specific context#

OverLayer was built with request based context and avoid a central context object. With this approach, you are forced to specify a context on the route handler which allow for request specific context. This is useful to gather more information from the request to identity and perform different behavior on subscriptions.

Explicit upgrade to get the on connected headers
val requestHandler: HttpRequest => HttpResponse = {  case req @ HttpRequest(GET, Uri.Path("/<path>"), _, _, _) =>
    val token = req      .getHeader("Authorization")      .map(_.value())      .flatMap {        case s"Bearer $tokenValue" => Some(tokenValue)        case _ => None      }
    req.getAttribute(AttributeKeys.webSocketUpgrade) match {      case Some(upgrade) =>        upgrade.handleMessages(transport.applyMiddleware(Ctx(token)))      case None =>        HttpResponse(400, entity = "Not a valid websocket request!")    }  case r: HttpRequest =>    r.discardEntityBytes() // important to drain incoming HTTP Entity stream    HttpResponse(404, entity = "Unknown resource!")}

Queries, Mutations, Introspections over Websocket#

From v0.2.0, OverLayer will no longer reject Query and Mutation and now have capabilities to resolve them. This also include introspection queries, thus you have your entire operations be handled through websocket on a single open connection.

Only allow operation over websocket
object Main {  implicit val system = OverTransportLayer.makeSystem("main-actor-system")
  val transport = OverTransportLayer(...)
  val route: Route = (path("graphql" / "websocket")) {    transport.applyMiddleware(...)  }
  def main(args: Array[String]): Unit = {    Http()      .newServerAt("localhost", 4000)      .bind(route)  }}

Integrating with Ahql#

Ahql is a minimal library to perform GraphQL over HTTP Request and Response (Server and Client) on akka-http and spray-json. While Ahql and OverLayer has no specific integration features, Ahql can be great tool used combined with this package.

Handle both HTTP and Websocket request
val route: Route = path("graphql") {    Ahql.applyMiddleware(schema, context, ())  } ~ path("graphql" / "websocket") {    transport.applyMiddleware(context)  }

Combining pathPrefix and other directives, you can easily make Ahql and OverLayer work nicely together.

More complex example
val gqlServer = Ahql.createServer(schema, (), deferredResolver = deferredResolver)val gqlTransport = OverTransportLayer(schema, (), deferredResolver = deferredResolver)
// CORS for Apollo Sandboxval corsConfig = CorsSettings  .defaultSettings  .withAllowedOrigins(HttpOriginMatcher(HttpOrigin("https://studio.apollographql.com")))  .withAllowCredentials(true)
val route: Route = cors(corsConfig) {  // Route for "__endpoint__/graphql/**"  pathPrefix("graphql") {    // Get header for a specific value: "Authorization"    optionalHeaderValueByName("Authorization") { auth =>      val context = Ctx(..., auth)
      pathEndOrSingleSlash { // Route for "__endpoint__/graphql"        gqlServer.applyMiddleware(context)      } ~ path("websocket") { // Route for "__endpoint__/graphql/websocket"        gqlTransport.applyMiddleware(context)      }    }  } ~ path(Remaining) { _ => // Any other route goes through Apollo Sandbox    redirect(      uri = s"http://sandbox.apollo.dev/?endpoint=${__endpoint__}",      StatusCodes.PermanentRedirect    )  }}

Additional redirect and cors for allowing Apollo Sandbox.