oxidite-graphql
GraphQL integration for the Oxidite web framework.

Overview
oxidite-graphql provides seamless GraphQL integration for Oxidite applications. It leverages the Juniper and async-graphql ecosystems to offer type-safe GraphQL schemas, resolvers, and automatic schema generation. The crate integrates smoothly with Oxidite's routing and middleware systems.
Installation
Add this to your Cargo.toml:
[dependencies]
oxidite-graphql = "2.0"
oxidite = "2.0"
Features
- Type-Safe GraphQL Schemas: Define schemas using Rust types with automatic mapping
- Integration with Oxidite Router: Easy endpoint registration for GraphQL APIs
- Automatic Schema Generation: Generate schemas from Rust types and resolvers
- Query Complexity Analysis: Built-in protection against complex queries
- Introspection Support: Full GraphQL introspection capabilities
- Subscriptions: Real-time GraphQL subscriptions with WebSocket support
- Error Handling: Proper GraphQL error formatting and reporting
- Batching: Support for batched GraphQL queries
- Caching: Integrated caching for GraphQL resolvers
Usage
Basic GraphQL Setup
use oxidite::prelude::*;
use oxidite_graphql::{GraphQLHandler, Schema, RootNode};
use juniper::{EmptyMutation, EmptySubscription, FieldResult};
struct Context {
}
impl juniper::Context for Context {}
#[derive(juniper::GraphQLObject)]
struct User {
id: String,
name: String,
email: String,
}
struct QueryRoot;
#[juniper::graphql_object(Context = Context)]
impl QueryRoot {
fn users(context: &Context) -> FieldResult<Vec<User>> {
Ok(vec![
User {
id: "1".to_string(),
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
}
])
}
fn user(context: &Context, id: String) -> FieldResult<Option<User>> {
Ok(Some(User {
id,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
}))
}
}
type Schema = juniper::RootNode<'static, QueryRoot, EmptyMutation, EmptySubscription>;
fn create_schema() -> Schema {
Schema::new(QueryRoot, EmptyMutation::new(), EmptySubscription::new())
}
#[tokio::main]
async fn main() -> Result<()> {
let schema = std::sync::Arc::new(create_schema());
let context_factory = || Context {};
let mut router = Router::new();
router.get("/graphql", GraphQLHandler::new(schema.clone(), context_factory));
router.post("/graphql", GraphQLHandler::new(schema, context_factory));
router.get("/playground", GraphQLHandler::playground("/graphql"));
Server::new(router)
.listen("127.0.0.1:3000".parse().unwrap())
.await
}
Using async-graphql Alternative
use oxidite::prelude::*;
use oxidite_graphql::AsyncGraphQLHandler;
use async_graphql::*;
#[derive(SimpleObject)]
struct User {
id: ID,
name: String,
email: String,
}
#[derive(Default)]
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn users(&self) -> Vec<User> {
vec![
User {
id: "1".into(),
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
}
]
}
async fn user(&self, id: ID) -> Option<User> {
Some(User {
id,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
})
}
}
type AppSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
fn create_async_schema() -> AppSchema {
Schema::build(QueryRoot::default(), EmptyMutation, EmptySubscription).finish()
}
async fn setup_async_graphql() -> Result<()> {
let schema = create_async_schema();
let mut router = Router::new();
router.all("/graphql", AsyncGraphQLHandler::new(schema));
Server::new(router)
.listen("127.0.0.1:3000".parse().unwrap())
.await
}
GraphQL with Database Integration
use oxidite::prelude::*;
use oxidite_db::{Model, Database};
use oxidite_graphql::{GraphQLHandler, Context};
#[derive(Model, juniper::GraphQLObject)]
#[model(table = "users")]
struct User {
id: i32,
name: String,
email: String,
created_at: String,
}
struct AppContext {
db: Database,
}
impl juniper::Context for AppContext {}
struct QueryRoot;
#[juniper::graphql_object(Context = AppContext)]
impl QueryRoot {
fn users(context: &AppContext) -> FieldResult<Vec<User>> {
let users = context.db.find_all::<User>()?;
Ok(users)
}
fn user(context: &AppContext, id: i32) -> FieldResult<Option<User>> {
let user = context.db.find_by_id::<User>(id)?;
Ok(user)
}
}
Advanced Features
Middleware Integration
use oxidite::prelude::*;
use oxidite_graphql::GraphQLHandler;
async fn auth_middleware(req: Request, next: Next) -> Result<Response> {
if !is_authenticated(&req)? {
return Err(Error::Unauthorized("Authentication required".to_string()));
}
next.run(req).await
}
let mut router = Router::new();
router.get("/graphql").middleware(auth_middleware);
router.post("/graphql").middleware(auth_middleware);
Subscriptions
use oxidite_graphql::{Subscription, SubscriptionHandler};
struct SubscriptionRoot;
#[juniper::graphql_subscription]
impl SubscriptionRoot {
async fn user_events(&self) -> impl Stream<Item = UserEvent> {
tokio_stream::iter(vec![
UserEvent::UserCreated(User { }),
UserEvent::UserUpdated(User { }),
])
}
}
Error Handling
use juniper::FieldError;
#[juniper::graphql_object(Context = Context)]
impl QueryRoot {
fn user(context: &Context, id: String) -> FieldResult<User> {
match get_user_by_id(id) {
Ok(Some(user)) => Ok(user),
Ok(None) => Err(FieldError::new("User not found", juniper::Value::null())),
Err(e) => Err(FieldError::new("Database error",
juniper::Value::string(e.to_string()))),
}
}
}
Performance
- Query Optimization: Automatic query optimization and field selection
- Caching: Built-in resolver caching with configurable TTL
- Batching: DataLoader integration for efficient N+1 query resolution
- Compression: Automatic response compression for large payloads
License
MIT