GraphQL database canister - Sudograph

# GraphQL database canister

Sudograph provides one main entrypoint for creating your GraphQL database, and that is the `graphql_database` Rust procedural macro. Using the power of Rust procedural macros, `graphql_database` will take your GraphQL schema file and generate all code required to turn the types defined in your schema into a functioning CRUD database.

To use `graphql_database`, first create a new Rust canister. If you're new to developing for the Internet Computer, you might want to check the documentation to get familiar with canister development.

Add a new canister to your `dfx.json`. You can name the canister whatever you'd like, but to keep things simple the canister defined below is named `graphql`. The contents of your `dfx.json` should look like the following. If you have other canisters already defined, just add the `graphql` canister:

    {
        "canisters": {
            "graphql": {
                "type": "custom",
                "build": "cargo build --target wasm32-unknown-unknown --package graphql --release",
                "candid": "canisters/graphql/src/graphql.did",
                "wasm": "target/wasm32-unknown-unknown/release/graphql.wasm"
            }
        }
    }

The canister defined above assumes a directory structure where `dfx.json` is in the root of your project, and there is a directory called `canisters` to contain each canister. You can change up the directory structure if you'd like, just change all of the paths appropriately. Create a new directory within canisters called `graphql`, and add a `Cargo.toml` file. It should look something like the following:

    [package]
    name = "graphql"
    version = "0.0.0"
    edition = "2018"

    [lib]
    path = "src/graphql.rs"
    crate-type = ["cdylib"]

    [dependencies]
    sudograph = "0.2.0"

Within the `canisters/graphql` directory, now create a `src` directory. The `canisters/graphql/src` directory will contain the entrypoint to your `graphql` canister, `graphql.rs`, along with your `schema.graphql` file and your `graphql.did` file.

The `graphql.rs` file should look like this:

    #![allow(unused)]
    fn main() {
    use sudograph::graphql_database;

    graphql_database!("canisters/graphql/src/schema.graphql");
    }

This simply imports the `graphql_database` procedural macro from `sudograph` and then invokes it with the path to your `schema.graphql` file.

You must also create a custom candid file `graphql.did`:

    service : {
        "graphql_query": (text, text) -> (text) query;
        "graphql_mutation": (text, text) -> (text);
    }

The generated canister code will have created the two functions defined in `graphql.did`, but for now you'll need to create the candid file manually. Hopefully in the future it can be generated for you or abstracted away somehow.

`graphql_query` and `graphql_mutation` both take two parameters. The first parameter is the query or mutation string. The second parameter is a JSON string containing any variables for the query or mutation. Currently the second parameter is required, so just send an empty JSON object strin `"{}"` if no variables are required for the query or mutation.

`graphql_query` and `graphql_mutation` both return the result of the query or mutation as a JSON string. Whatever client is consuming the query or mutation will then need to parse the JSON string to turn it into a language-level object. The Sudograph Client will do this for you in a JavaScript frontend.

Finally create your `schema.graphql` file:

    type User {
        id: ID!
        username: String!
        blogPosts: [BlogPost!]! @relation(name: "User:blogPosts and BlogPost:author")
    }

    type BlogPost {
        id: ID!
        publishedAt: Date
        title: String!
        author: User! @relation(name: "User:blogPosts and BlogPost:author")
    }

You now have everything you need to deploy a simple `graphql canister`. Boot up a node with `dfx start` and then deploy with `dfx deploy`. It's important to note that Sudograph currently only works within a single canister. You can deploy as many Sudograph canisters as you'd like, with as many schemas as you'd like, but the generated querying and mutations will only know about data that has been created within the same canister. Querying between canisters would require you to write your own custom code. Sudograph will hopefully address scaling in the future so that you only ever have to deal with thinking about one schema per application.

## Rust Wasm binary optimization

At some point your compiled Rust Wasm binary will grow too large and will be rejected by the canister on deploy. This could happen because the Rust source code that you've written has grown too large, or because your schema has grown too large. A large schema will lead to a large amount of generated Rust code.

To temporarily overcome this issue (only so much can be done during optimization, eventually the binary will be too big and the Internet Computer will need to address that), you can optimize your Rust Wasm binary.

### Manual optimization

To do this manually, in the root of your directory run the following command once to install the optimizer:

    cargo install ic-cdk-optimizer --root target

You should also change your `dfx.json` file from:

    {
        "canisters": {
            "graphql": {
                "type": "custom",
                "build": "cargo build --target wasm32-unknown-unknown --package graphql --release",
                "candid": "canisters/graphql/src/graphql.did",
                "wasm": "target/wasm32-unknown-unknown/release/graphql.wasm"
            }
        }
    }

to:

    {
        "canisters": {
            "graphql": {
                "type": "custom",
                "build": "cargo build --target wasm32-unknown-unknown --package graphql --release",
                "candid": "canisters/graphql/src/graphql.did",
                "wasm": "target/wasm32-unknown-unknown/release/graphql-optimized.wasm"
            }
        }
    }

The only thing that changed was the `wasm` property of the `graphql` canister object, and it changed from `"wasm": "target/wasm32-unknown-unknown/release/graphql.wasm"` to `"wasm": "target/wasm32-unknown-unknown/release/graphql-optimized.wasm"`.

Each time you run `dfx deploy` or `dfx deploy graphql`, you will need to run the following command after:

    ./target/bin/ic-cdk-optimizer ./target/wasm32-unknown-unknown/release/graphql.wasm -o ./target/wasm32-unknown-unknown/release/graphql-optimized.wasm

### Automatic optimization

It can be tedious to have to run the above command manually after each `dfx deploy`. If you wish to figure out how to use `cargo` scripts of some kind you can do that. You could also use `make` or `bash` or some other build process or scripting system.

Another way is to adopt npm scripts. Your `package.json` could look something like this:

    {
        "scripts": {
            "build": "cd canisters/playground && npm install && npm run build && cd ../frontend && npm install && npm run build",
            "dfx-deploy": "npm run dfx-build-graphql && npm run dfx-optimize-graphql && dfx deploy",
            "dfx-deploy-graphql": "npm run dfx-build-graphql && npm run dfx-optimize-graphql && dfx deploy graphql",
            "dfx-build-graphql": "cargo build --target wasm32-unknown-unknown --package graphql --release",
            "dfx-optimize-graphql": "./target/bin/ic-cdk-optimizer ./target/wasm32-unknown-unknown/release/graphql.wasm -o ./target/wasm32-unknown-unknown/release/graphql-optimised.wasm"
        }
    }

Then instead of running `dfx deploy` or `dfx deploy graphql` you would run `npm run dfx-deploy` or `npm run dfx-deploy-graphql`.

In the future it would be nice for the `dfx.json` to allow for some sort of build scripts, which would make this process less messy. There is an open forum post about this here: https://forum.dfinity.org/t/dfx-json-build-scripts/4922