Actix Web Integration

A full example of integrating Warpgrapher with Actix Web is contained in the warpgrapher-actixweb respository on Github. A slightly simplified version of that project is reproduced with additional description below.

To integrate Warpgrapher with an Actix Web engine, include the following dependencies in the Cargo.toml file.

Cargo.toml

[dependencies]
actix-http = "3.0.0-beta.5"
actix-web = "4.0.0-beta.6"
actix-cors = "0.6.0-beta.2"
serde = "1.0.135"
serde_json = "1.0.78"
warpgrapher = { version="0.10.4", features=["cypher"]}

The rest of the code necessary to accomplish the integration is contained within the single source code file below. First, a number of structs and functions are imported from the Actix and Warpgrapher crates.

src/main.rs

use actix_cors::Cors;
use actix_http::error::Error;
use actix_web::middleware::Logger;
use actix_web::web::{Data, Json};
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fs::File;

use warpgrapher::engine::config::Configuration;
use warpgrapher::engine::context::RequestContext;
use warpgrapher::engine::database::cypher::CypherEndpoint;
use warpgrapher::engine::database::DatabaseEndpoint;
use warpgrapher::juniper::http::playground::playground_source;
use warpgrapher::Engine;

The AppData struct, defined below, is used to pass application data created during setup into the web server. In the case of this integration, the application data that is passed into the web server is the Warpgrapher Engine.

#[derive(Clone)]
struct AppData {
    engine: Engine<Rctx>,
}

impl AppData {
    fn new(engine: Engine<Rctx>) -> AppData {
        AppData { engine }
    }
}

Just like the Quickstart tutorial, this integration creates a RequestContext that could be used to pass data into the Warpgrapher engine for custom resolvers or endpoints, but is left empty in this example. The Rctx struct does contain on associated type, which selects Cypher as the database type to be used for this Warpgrapher engine.

#[derive(Clone, Debug)]
struct Rctx {}

impl RequestContext for Rctx {
    type DBEndpointType = CypherEndpoint;

    fn new() -> Self {
        Rctx {}
    }
}

Next, the integration includes a GraphqlRequest that is used to deserialize queries coming from Actix Web and pass the query content to the Warpgrapher engine.

#[derive(Clone, Debug, Deserialize)]
struct GraphqlRequest {
    pub query: String,
    pub variables: Option<Value>,
}

The following function is the handler that takes requests from the Actix Web framework and passes it into the Warpgrapher engine. In short, it pulls the query and query variables from the Actix Web query and passes those as arguments to the Warpgrapher engine's execute function. A successful response is passed back as an Ok result. Errors are returned within an InternalServerError.

async fn graphql(data: Data<AppData>, req: Json<GraphqlRequest>) -> Result<HttpResponse, Error> {
    let engine = &data.engine;
    let metadata: HashMap<String, String> = HashMap::new();
    let resp = engine
        .execute(req.query.to_string(), req.variables.clone(), metadata)
        .await;
    match resp {
        Ok(body) => Ok(HttpResponse::Ok()
            .content_type("application/json")
            .body(body.to_string())),
        Err(e) => Ok(HttpResponse::InternalServerError()
            .content_type("application/json")
            .body(e.to_string())),
    }
}

To make it easier to explore the schema generated by Warpgrapher, the integration example also includes a handler function that returns a GraphQL playground, as the /playground path. The handler function is shown below.

async fn playground(_data: Data<AppData>) -> impl Responder {
    let html = playground_source("/graphql", None);
    HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(html)
}

The create_engine function pulls data from environment variables to determine how to connect to a Cypher-based database. These are the same environment variables described in the Quickstart and the Neo4J section of the Databases book.

async fn create_engine(config: Configuration) -> Engine<Rctx> {
    let db = CypherEndpoint::from_env()
        .expect("Failed to parse endpoint from environment")
        .pool()
        .await
        .expect("Failed to create db endpoint");
    let engine: Engine<Rctx> = Engine::<Rctx>::new(config, db)
        .build()
        .expect("Failed to build engine");
    engine
}

Lastly, the main function itself pulls all of the above elements together. It reads a configuration from a ./config.yaml file and passes that to the function defined above to create an Warpgrapher Engine. It packages the Warpgrapher engine into an AppData struct to pass off to Actix Web and creates an HttpServer to begin fielding requests. The GraphQL API is bound to the /graphql path, and the playground is bound to the /playground path.

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let config_file = File::open("./config.yaml".to_string()).expect("Could not read file");
    let config = Configuration::try_from(config_file).expect("Failed to parse config file");

    let engine = create_engine(config.clone()).await;

    let graphql_endpoint = "/graphql";
    let playground_endpoint = "/playground";
    let bind_addr = "0.0.0.0".to_string();
    let bind_port = "5000".to_string();
    let addr = format!("{}:{}", bind_addr, bind_port);

    let app_data = AppData::new(engine);

    println!("Starting server on {}", addr);
    HttpServer::new(move || {
        App::new()
            .app_data(actix_web::web::Data::new(app_data.clone()))
            .wrap(Logger::default())
            .wrap(Cors::permissive())
            .route(graphql_endpoint, web::post().to(graphql))
            .route(playground_endpoint, web::get().to(playground))
    })
    .bind(&addr)
    .expect("Failed to start server")
    .run()
    .await
}

To view or clone a full repository project with an Actix Web integration, visit the warpgrapher-actixweb repository on GitHub.