Rust GraphQL server using simple type-only schema

Overview

SimpleGQL

This library allows a simplified GraphQL schema to be given and will run a server with a backend store (currently only SQLite) and a set of automatically generated resolvers for queries and mutations.

As an example, you can specify:

type Taxon {
    name: String!
    description: String
    rank: Rank
    observations: [Observation!]!
}

enum Rank {
    Order
    Family
    Species
}

type Observation {
    filename: String!
    parent: Taxon @incoming(src_field: "observations")
}

and by running cargo run -- --sqlite mydblocation.sqlite --schema schema_above.graphql a HTTP server will be started handling GraphQL requests at /gql with a set of automatically generated resolvers including:

Query {
    getTaxon(id: ID!): Taxon
    queryTaxon(filter: TaxonFilter, first: Int, offset: Int): [Taxon!]!

    getObservation(id: ID!): Observation
    queryObservation(filter: ObservationFilter, first: Int, offset: Int): [Observation!]!
}

Mutation {
    addTaxon(input: [TaxonInput!]!): TaxonMutationResponse
    updateTaxon(input: {filter: TaxonFilter, set: {...}, remove: {...}}): TaxonMutationResponse
    deleteTaxon(...): TaxonMutationResponse

    addObservation(input: [ObservationInput!]!): ObservationMutationResponse
    updateObservation(input: {filter: ObservationFilter, set: {...}, remove: {...}}): ObservationMutationResponse
    deleteObservation(...): ObservationMutationResponse
}

This allows for the following kinds of queries:

query {
    getTaxon(id: "bbb") {
        id
        name
        rank
        description
        observations(first: 5) {
            filename
            parent {
                id
            }
        }
    }
}

query {
    queryTaxon(first: 10) {
        id
    }
}

mutation {
    addTaxon(input: [{
        name: "newly added",
        rank: Species,
    }]) {
        count
        taxon {
            id
            name
            rank
            description
        }
    }
}

mutation {
    updateTaxon(input: {
        filter: {id: ["aaa"]},
        set: {name: "changed",
              observations: [{filename: "new obs"}]},
        remove: {description: null},
    }) {
        count
        taxon {
            id
            name
            description
            observations {
                filename
                parent {
                    id
                }
            }
        }
    }
}

The API for the queries and mutation is heavily influenced by that of Dgraph.

Backends

This crate is designed to support modular backend "drivers" for a given API. An implementation of a driver must include handlers for:

  • accessing of an entity of a particular type,
  • accessing a filtered list of entities of a particular type,
  • accessing a field of an entity,
  • adding a new entity of a given type,
  • updating entities of a given type with a filter,
  • starting a transaction,
  • completing (with or without failure) a transaction

Currently, only my first attempt at a crude SQLite backend is implemented.

Command line arguments

Usage: simplegql [OPTIONS] --schema <SCHEMA>

Options:
      --schema <SCHEMA>
      --sqlite <SQLITE>
      --port <PORT>      [default: 5001]
      --bind <BIND>      [default: localhost]
      --jwks <JWKS>
      --migrate
      --playground
  -h, --help             Print help

Some options in more detail:

  • --jwks provides a URL to require authentication for mutations. This will also be for per-entity queries in the futrue. The authentication must be given in the X-Auth-Header HTTP header as Bearer <JWK> in the mutation request.
  • --migrate allows the database to be overwritten with an updated schema. By default, the backend will only create new unconflicting entity types/edge types. This flag is required if you change the schema as it can cause data loss if you are not careful.
  • --playground starts a GraphIQL playground at /playground.

Directives

@incoming

@incoming(src_field: String)

Indicates the given field is the counterpart of another outgoing (regular) field.

@unique

@unique

@dynamic - Dynamic resolvers

@dynamic(hook: String!, args: Object!)

Adding hooks is possible for custom resolvers of given fields. For example:

type Taxon {
    name: String!
    children: [Taxon!]!
    parent: Taxon @incoming
    allParents: [Taxon!]! @dynamic(hook: "allParents": args: {})
}

These Taxon entites could be connected in a tree-like style. The allParents hook can then provide the complete ancestory of a given entity. This must be implemented in custom code as it is arbitrarily recursive (GraphQL does not allow for recursive queries).

Currently, to include a hook like this, you must build your own crate using simplegql as a dependency. The hooks are then made available with a HashMap to the server configuration. The hooks must be written for a given backend.

In the future, these hooks should be allowed to defined in a different high-level language such as Node.js. The hooks will also be able to be defined in a backend-agnostic form or specific to a particular backend.

In addition there is support for separate HTTP endpoints.

TODO list

Short-term TODOs:

  • Auth audience and JWK url read from input file
  • Switching between ID and UUID for sqlite backend from configuration.
  • Backend-agnostic custom resolvers
  • Proper auth handling per entity instead of blanket lock on mutations
  • Introduce proper error and result types
  • Update JWKS before expiry.

Long-term TODOs:

  • Resolvers in other languages through bindings
  • Avoid (or at least wrap an interface around) the Box::leak for the initalised configuration. The purpose is to allow a program to start/stop a server without leaking memory. Must avoid unnecessary Arc+Mutex on every resolver call.
  • Implement the filter feature fully using backend-specific option and a fallback to post-query filtering.
  • Examples for the different directives and hooks.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

You might also like...
A simple authentication flow using Rust and Actix-web, with a PostgreSQL database and a sveltekit frontend.

Rust-auth-example This repository aims to represent a simple authentication flow using Rust and Actix-web, with a PostgreSQL database and a sveltekit

Rust/Axum server implementation with PCR(Prisma Client Rust)
Rust/Axum server implementation with PCR(Prisma Client Rust)

Realworld Rust Axum Prisma This project utilizes Rust with the Axum v0.7 framework along with the Prisma Client Rust to build a realworld application.

 An extensible music server written in Rust πŸš€πŸŽ΅βœ¨
An extensible music server written in Rust πŸš€πŸŽ΅βœ¨

Music Player (written in Rust) Note: This is a work in progress. This is a simple music player that I made for my own use. It is written in Rust and u

Markdown LSP server for easy note-taking with cross-references and diagnostics.
Markdown LSP server for easy note-taking with cross-references and diagnostics.

Zeta Note is a language server that helps you write and manage notes. The primary focus is to support Zettelkasten-like1, 2 note taking by providing an easy way to cross-reference notes (see more about features below).

A super-easy, composable, web server framework for warp speeds.

warp A super-easy, composable, web server framework for warp speeds. The fundamental building block of warp is the Filter: they can be combined and co

REST API server that abstracts the need to write CRUD methods by exposing a standardized API to interact with a Postgres database
REST API server that abstracts the need to write CRUD methods by exposing a standardized API to interact with a Postgres database

Basiliq Exposing a Postgres database via a REST API that follows the JSON:API specs. All in all, a tasty API. What is Basiliq Quickstart Ready to use

Hot reload static web server for deploying mutiple static web site with version control.

SPA-SERVER It is to provide a static web http server with cache and hot reload. δΈ­ζ–‡ README Feature Built with Hyper and Warp, fast and small! SSL with

Proxies all incoming connections to a minecraft server of your choosing, while also logging all ping and login requests to a json file and discord webhook.

minecraft-honeypot Proxies all incoming connections to a minecraft server of your choosing, while also logging all ping and login requests to a json f

An adapter to easily allow an Axum server to be run within a Cloudflare worker.

axum-cloudflare-adapter An adapter to easily allow an Axum server to be run within a Cloudflare worker. Usage use worker::*; use axum::{ response

Owner
Daniel Cocks
Daniel Cocks
Rust server with Axum, GraphQL and SurrealDb

??️ Article on my web Axum server, Async-GraphQl, SurrealDB template Run without any prior setup, DB is in memory: cargo run To use routes other than

null 15 Jun 26, 2023
Scratch-Containerised Rust GraphQL-API using Dataloaders

Dockerize Graphql Rust More current version at https://github.com/jayy-lmao/rust-cult-graphql-server This project is currently for demonstrating the u

James H. 89 Dec 3, 2022
Proof of concept writing a monolith BBS using Rust, GraphQL, WASM, and SQL. WILL BE ARCHIVED ONCE PROVEN

GraphQL Forum Important DO NOT even think about using this in production, lest your sanity be destroyed and credentials lost! Loosely following the aw

Rongcui Dong 25 Apr 25, 2023
Diana is a GraphQL system for Rust that's designed to work as simply as possible out of the box

Diana is a GraphQL system for Rust that's designed to work as simply as possible out of the box, without sacrificing configuration ability. Unlike other GraphQL systems, Diana fully supports serverless functions and automatically integrates them with a serverful subscriptions system as needed, and over an authenticated channel. GraphQL subscriptions are stateful, and so have to be run in a serverful way. Diana makes this process as simple as possible.

null 0 Aug 3, 2021
A Rust GraphQL system with full support for subscriptions and authentication that works out of the box.

Diana is a GraphQL system for Rust that's designed to work as simply as possible out of the box, without sacrificing configuration ability.

arctic_hen7 36 Dec 19, 2022
openapi schema serialization for rust

open api Rust crate for serializing and deserializing open api documents Documentation install add the following to your Cargo.toml file [dependencies

Doug Tangren 107 Dec 6, 2022
A fast GraphQL engine.

bluejay-rb Warning This project is still very early in its development and should be considered highly unstable and experimental. It is incomplete and

Adam Petro 4 Feb 20, 2023
A URL shortener that uses emojis, only emojis.

emojied Shorten your URLs with emojis! Features Well, shorten your URLs! Customize what emoji to use. e.g Want to use an eggplant emoji? Sure, as long

SEKUN 99 Dec 31, 2022
Merge multiple Juniper object definitions into a single object type.

juniper-compose Merge multiple Juniper object definitions into a single object type. crates.io | docs | github Motivation You are building a GraphQL s

Kit Isaev 3 Aug 5, 2022
Type safe multipart/form-data handling for axum.

axum_typed_multipart Designed to seamlessly integrate with Axum, this crate simplifies the process of handling multipart/form-data requests in your we

Lorenzo Murarotto 10 Mar 28, 2023