GraphQL server in Rust with Juniper and Hyper

GraphQL server in Rust with Juniper and Hyper

Below you can find the source code that allows to run following read and write graphql queries:

read data

query {users{id, name}}

write data

mutation {addUserIdName(id: "id1", name:"name1") {
 id
}}
mutation {
  addUser(userInput: {id: "id2", name: "name2"}) {
    id
  }
}

the server will generate following schema:

type Mutation {
  addUserIdName(id: String!, name: String!): User!
  addUser(userInput: UserInput!): User!
}

input UserInput {
  id: String!
  name: String!
}

type Query {
  users: [User!]!
}

type User {
  id: String!
  name: String!
}

schema {
  query: Query
  mutation: Mutation
}

Complete source code, the data is stored in hash map:

use std::{collections::HashMap, convert::Infallible, sync::Arc};
use tokio::sync::RwLock;
#[macro_use]
extern crate juniper;

#[macro_use]
extern crate log;

use juniper::{EmptySubscription, RootNode};

struct Query;

struct Mutation;

#[derive(GraphQLInputObject)]
struct UserInput {
    id: String,
    name: String,
}

impl From<UserInput> for User {
    fn from(user_input: UserInput) -> Self {
        Self {
            id: user_input.id,
            name: user_input.name,
        }
    }
}

#[derive(Clone, GraphQLObject)]
struct User {
    id: String,
    name: String,
}

use hyper::{
    server::Server,
    service::{make_service_fn, service_fn},
    Method, Response, StatusCode,
};

struct Context {
    users: RwLock<HashMap<String, User>>,
}

impl juniper::Context for Context {}

#[graphql_object(context = Context)]
impl Query {
    async fn users(context: &Context) -> Vec<User> {
        let map = context.users.read().await;
        map.values().cloned().collect()
    }
}

#[graphql_object(context = Context)]
impl Mutation {
    #[graphql(name = "addUserIdName")]
    async fn add_user(context: &Context, id: String, name: String) -> User {
        info!("create user by id and name");
        let mut map = context.users.write().await;
        let user: User = User { id: id, name: name };
        map.insert(user.id.clone(), user.clone());
        user
    }

    async fn add_user(context: &Context, user_input: UserInput) -> User {
        info!("create user");
        let mut map = context.users.write().await;
        let user: User = user_input.into();
        map.insert(user.id.clone(), user.clone());
        user
    }
}

use hyper::Body;



#[tokio::main]
async fn main()  {
    pretty_env_logger::init();
    let addr = ([127, 0, 0, 1], 3000).into();

    let root_node = Arc::new(RootNode::new(
        Query,
        Mutation,
        EmptySubscription::<Context>::new(),
    ));

    println!("{}", root_node.as_schema_language());

    let new_service = make_service_fn(move |_| {
        let root_node = root_node.clone();

        let context = Arc::new(Context {
            users: RwLock::new(HashMap::new()),
        });

        let ctx = context.clone();

        async {
            Ok::<_, hyper::Error>(service_fn(move |req| {
                let root_node = root_node.clone();
                let ctx = ctx.clone();
                async {
                    Ok::<_, Infallible>(match (req.method(), req.uri().path()) {
                        (&Method::GET, "/") => juniper_hyper::graphiql("/graphql", None).await,
                        (&Method::GET, "/graphql") | (&Method::POST, "/graphql") => {
                            juniper_hyper::graphql(root_node, ctx, req).await
                        }
                        _ => {
                            let mut response = Response::new(Body::empty());
                            *response.status_mut() = StatusCode::NOT_FOUND;
                            response
                        }
                    })
                }
            }))
        }
    });

    let server = Server::bind(&addr).serve(new_service);
    println!("Listening on http://{addr}");

    if let Err(e) = server.await {
        eprintln!("server error: {e}")
    }
}

Run the sever:

RUST_LOG=info cargo r

Dependencies

[dependencies]
juniper = "0.15"
juniper_hyper = "0.8.0"
hyper = { version = "0.14", features = ["server", "runtime"] }
log = "0.4"
pretty_env_logger = "0.4"

Related Posts