rusty-gql
rusty-gql is a Schema First GraphQL library for Rust.
It is designed to make it easier to create a GraphQL server.
Features
- Schema First approach
- Code Generate from GraphQL schema
- Convention Over Configuration
Status
rusty-gql is still an experimental project. APIs and the architecture are subject to change.
It is not yet recommended for use in production.
Getting Started
Install rusty-gql-cli
cargo install rusty-gql-cli
Run new command
rusty-gql new gql-example
cd gql-example
Start the GraphQL Server
cargo run
Creating a GraphQL Schema
rusty-gql is designed for schema first development.
It reads any graphql files under schema/**
.
schema/schema.graphql
type Query {
todos(first: Int): [Todo!]!
}
type Todo {
title: String!
content: String
done: Boolean!
}
Implement Resolvers
Let's edit src/graphql/query/todos.rs
.
Generate Rust code
Edit schema.graphql.
type Query {
todos(first: Int): [Todo!]!
# added
todo(id: ID!): Todo
}
type Todo {
title: String!
description: String
done: Boolean!
}
rusty-gql generates rust code from graphql schema files.
rusty-gql generate // or rusty-gql g
Directory Structure
src
┣ graphql
┃ ┣ directive
┃ ┃ ┗ mod.rs
┃ ┣ input
┃ ┃ ┗ mod.rs
┃ ┣ mutation
┃ ┃ ┗ mod.rs
┃ ┣ query
┃ ┃ ┣ mod.rs
┃ ┃ ┣ todo.rs
┃ ┃ ┗ todos.rs
┃ ┣ resolver
┃ ┃ ┣ mod.rs
┃ ┃ ┗ todo.rs
┃ ┣ scalar
┃ ┃ ┗ mod.rs
┃ ┗ mod.rs
┗ main.rs
GraphQL Playground
rusty-gql supports GraphiQL playground. Open a browser to http://localhost:3000/graphiql.
Directory Structure
A rusty-gql project has the following directory structure.
rusty-gql-project
┣ schema
┃ ┗ schema.graphql
┣ src
┃ ┣ graphql
┃ ┃ ┣ directive
┃ ┃ ┃ ┗ mod.rs
┃ ┃ ┣ input
┃ ┃ ┃ ┗ mod.rs
┃ ┃ ┣ mutation
┃ ┃ ┃ ┗ mod.rs
┃ ┃ ┣ query
┃ ┃ ┃ ┣ mod.rs
┃ ┃ ┣ resolver
┃ ┃ ┃ ┣ mod.rs
┃ ┃ ┣ scalar
┃ ┃ ┃ ┗ mod.rs
┃ ┃ ┗ mod.rs
┃ ┗ main.rs
┗ Cargo.toml
schema
GraphQL schema files are located under schema/**
.
We can also place multiple GraphQL files.
For example, like this.
schema
┣ post
┃ ┗ post.graphql
┣ user
┃ ┗ user.graphql
┗ index.graphql
src/graphql/query
Query resolvers.
src/graphql/mutation
Mutation resolvers.
src/graphql/resolver
GraphQL Object
, Enum
, Union
, Interface
types.
src/graphql/input
GraphQL InputObject.
src/graphql/scalar
Custom scalars.
src/graphql/directive
Custom directives.
Types
rusty-gql generates GraphQL types as Rust codes from schemas.
Object
rusty-gql defines GraphQL Object as Rust struct and #[GqlType]
like the following.
src/graphql/resolver/todo.rs
schema.graphql
type Todo {
title: String!
content: String
done: Boolean!
}
We'll implement async fn
for each fields with #[GqlType]
.
If we want to execute only when the field is included in a operation, implement async fn
without the struct field.
src/graphql/resolver/todo.rs
type Todo {
title: String!
content: String
done: Boolean!
user: User!
}
type User {
name
}
Interface
GraphQL Interface is represented as Rust enum with different types and #[derive(GqlInterface)
, #[GqlType(interface)]
.
Each variants is possible types of interface.
src/graphql/resolver/pet.rs
schema.graphql
interface Pet {
name: String
}
type Cat implements Pet {
name: String
meows: Boolean
}
type Dog implements Pet {
name: String
woofs: Boolean
}
Union
rusty-gql defines GraphQL Union as Rust enum with different types and #[derive(GqlUnion)]
.
src/graphql/resolver/search_result.rs
schema.graphql
type Query {
search(text: String): [SearchResult!]!
}
union SearchResult = Human | Droid
type Human {
id: ID!
name: String!
homePlanet: String
}
type Droid {
id: ID!
name: String!
primaryFunction: String
}
Enum
rusty-gql defines GraphQL Enum as Rust enum with #[derive(GqlEnum)]
.
src/graphql/resolver/episode.rs
schema.graphql
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
InputObject
rusty-gql defines GraphQL InputObject as Rust struct with #[derive(GqlInputObject)]
.
src/graphql/input/review_input.rs
schema.graphql
type Mutation {
createReview(episode: Episode, review: ReviewInput!): Review
}
input ReviewInput {
stars: Int!
commentary: String
}
type Review {
episode: Episode
stars: Int!
commentary: String
}
Scalar
We can define custom scalars.
rusty-gql represents custom scalar by using #[derive(GqlScalar)]
and GqlInputType
trait.
src/graphql/scalar/base64.rs
schema.graphql
scalar Base64
Directive
We can use directives as middleware.
It is useful in the following use cases.
- Authorization
- Validation
- Caching
- Logging, metrics
- etc.
If we don't want to expose a specific field, we can define the following directive.
src/graphql/directive/hidden.rs
schema.graphql
type User {
name: String!
password_hash: String @hidden
}
directive @hidden on FIELD_DEFINITION | OBJECT
Need to pass a HashMap of directives when Container::new in main.rs.
A Key is the directive name, a value is the directive struct.
main.rs
async fn main() {
...
let mut custom_directive_maps = HashMap::new();
custom_directive_maps.insert("hidden", Hidden::new());
let container = Container::new(
schema_docs.as_slice(),
Query,
Mutation,
EmptySubscription,
custom_directive_maps, // path here
)
.unwrap();
...
}
Schema
rusty-gql supports Query and Mutation. (Subscription is work in progress.)
These will be generated automatically when we create a rusty-gql project.
Query
rusty-gql has Query files under src/graphql/query/**
.
For example,
src
┣ graphql
┃ ┣ query
┃ ┃ ┣ mod.rs
┃ ┃ ┗ todos.rs
src/graphql/query/todos.rs
src/graphql/query/mod.rs
Files except for mod.rs
implements resolvers for each Query fields.
mod.rs
only bundles these files and defines Query
struct.
Mutation
Mutation has a similar directory structure to Query.
rusty-gql has Mutation files under src/graphql/mutation/**
.
src
┣ graphql
┃ ┣ mutation
┃ ┃ ┣ mod.rs
┃ ┃ ┗ create_todo.rs
src/graphql/mutation/create_todo.rs
src/graphql/mutation/mod.rs
Mutation is optional, so if we don't need Mutation, use EmptyMutation
struct in main.rs
main.rs
mod graphql;
...
#[tokio::main]
async fn main() {
...
let container = Container::new(
schema_docs.as_slice(),
Query,
EmptyMutation, // or graphql::Mutation
EmptySubscription,
Default::default(),
)
.unwrap();
}
Error Handling
If errors occur while GraphQL operation, errors
field will be included in the response.
Add errors by using add_error
of Context
.
A error is defined by GqlError
struct.
When we want to add a meta info, use extensions
.
The GraphQL definition of rusty-gql error is as follows. Also see GraphQL spec.
type GqlError {
message: String!
locations: [Location!]!
path: [String!]!
extensions: GqlTypedError
}
type GqlTypedError {
errorType: GqlErrorType!
errorDetail: String
origin: String
debugInfo: DebugInfo
debugUri: String
}
enum GqlErrorType {
BadRequest
FailedPreCondition
Internal
NotFound
PermissionDenied
Unauthenticated
Unavailable
Unknown
}
Roadmap
The following features will be implemented.
- Subscription
- Dataloader
- Calculate Query complexity
- Apollo tracing
- Apollo Federation
- Automatic Persisted Query
- etc.