ScalarDB GraphQL is an interface layer that allows client applications to communicate with a ScalarDB database with GraphQL.

In this Getting Started guide, you will run a GraphQL server on your local machine.

Prerequisites

We assume you have already installed Docker and have access to a ScalarDB-supported database such as Cassandra. Please configure them first by following Getting Started with ScalarDB if you have not set them up yet.

You need a Personal Access Token (PAT) to access the Docker image of ScalarDB GraphQL in GitHub Container registry since the image is private. Ask a person in charge to get your account ready. Please read the official document for more detail.

Set up a database schema

We use the following simple example schema.

emoney.json

{
  "emoney.account": {
    "transaction": true,
    "partition-key": [
      "id"
    ],
    "clustering-key": [],
    "columns": {
      "id": "TEXT",
      "balance": "INT"
    }
  }
}

To apply the schema to your database, download the Schema Loader that matches the version you use from scalardb releases, and run the following command to load the schema.

$ java -jar scalardb-schema-loader-<version>.jar --config /path/to/database.properties -f emoney.json --coordinator

Docker login

docker login is required to start the ScalarDB GraphQL Docker image as described in the Prerequisites section.

# read:packages scope needs to be selected in a personal access token to login
$ export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN
$ echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin

Configure the GraphQL Server

Add the following properties to your database.properties file. You can change the values as needed. Especially, make sure to set namespaces property. The tables in the specified namespaces (it can be a comma-separated list) will be exposed by the GraphQL server.

scalar.db.graphql.port=8080
scalar.db.graphql.path=/graphql
scalar.db.graphql.namespaces=emoney
scalar.db.graphql.graphiql=true

Start up the GraphQL Server

The following command starts up the ScalarDB GraphQL server. The first time you run the command, it will download the Docker image from GitHub Container Registry.

$ docker run -v /path/to/database.properties:/scalardb-graphql/database.properties.tmpl -p 8080:8080 ghcr.io/scalar-labs/scalardb-graphql:<version>

At this point, the server reads the tables in the specified namespaces and generates a GraphQL schema to perform CRUD operations on the them.

Run operations from GraphiQL

If the server is configured with a property scalar.db.graphql.graphiql=true (true by default), GraphiQL IDE will be available. When the above example properties are used, the endpoint URL of GraphiQL IDE is http://localhost:8080/graphql. Opening that URL with your web browser will take you to the GraphiQL screen.

Let’s insert the first record. In the left pane, paste the following mutation, then push the triangle-shaped Execute Query button at the top of the window.

mutation PutUser1 {
  account_put(put: {key: {id: "user1"}, values: {balance: 1000}})
}

ScalarDB GraphQL always runs queries with transactions. The above query starts a new transaction, executes a ScalarDB Put command, and commits the transaction at the end of the execution.

The following response from the GraphQL server will appear in the right pane.

{
  "data": {
    "account_put": true
  }
}

The "data" field contains the result of the execution. This response shows the account_put field of the mutation was successful. The result type of mutations is Boolean!, which indicates whether the operation succeeded or not.

Next, let’s get the record you just inserted. Paste the following query next to the previous mutation in the left pane, and click the Execute Query button. Since you don’t delete the mutation PutUser1 above, a pull-down will appear below the button, and you can choose which operation should be executed. Choose GetUser1.

query GetUser1 {
  account_get(get: {key: {id: "user1"}}) {
    account {
      id
      balance
    }
  }
}

You should get the following result in the right pane.

{
  "data": {
    "account_get": {
      "account": {
        "id": "user1",
        "balance": 1000
      }
    }
  }
}

Mappings between GraphQL API and ScalarDB Java API

The automatically generated GraphQL schema defines queries, mutations, and object types for input/output to allow you to run CRUD operations for all the tables in the specified namespaces. These operations are designed to match the ScalarDB APIs defined in the DistributedTransaction interface.

Assuming you have an account table in a namespace, the following queries and mutations will be generated.

ScalarDB API GraphQL root type GraphQL field
get(Get get) Query account_get(get: account_GetInput!): account_GetPayload
scan(Scan scan) Query account_scan(scan: account_ScanInput!): account_ScanPayload
put(Put put) Mutation account_put(put: account_PutInput!): Boolean!
put(java.util.List<Put> puts) Mutation account_bulkPut(put: [account_PutInput!]!): Boolean!
delete(Delete delete) Mutation account_delete(delete: account_DeleteInput!): Boolean!
delete(java.util.List<Delete> deletes) Mutation account_bulkDelete(delete: [account_DeleteInput!]!): Boolean!
mutate(java.util.List<? extends Mutation> mutations) Mutation account_mutate(put: [account_PutInput!]delete: [account_DeleteInput!]): Boolean!

Note that the scan field is not generated for a table with no clustering key. This is the reason why account_scan field is not available in our emoney example in this document.

You can see all generated GraphQL types in GraphiQL’s Documentation Explorer (the < Docs link at the top-right corner).

Transaction across multiple requests

This section describes how to run a transaction that spans multiple GraphQL requests.

The generated schema provides the @transaction directive that allows you to identify transactions. This directive can be used with both queries and mutations.

Start a transaction before running an operation

Adding a @transaction directive with no arguments to a query or a mutation directs the execution to start a new transaction.

query GetAccounts @transaction {
  user1: account_get(get: {key: {id: "user1"}}) {
    account { balance }
  }
  user2: account_get(get: {key: {id: "user2"}}) {
    account { balance }
  }
}

You will get a result with a transaction ID in the extensions field. The id value in the extensions is the transaction ID in which the operation in the request was run. In this case, this is the new ID of the transaction just started by the request.

{
  "data": {
    "user1": {
      "account": {
        "balance": 1000
      }
    },
    "user2": {
      "account": {
        "balance": 1000
      }
    }
  },
  "extensions": {
    "transaction": {
      "id": "c88da8a6-a13f-4857-82fe-45f1ab4150f9"
    }
  }
}

Run an operation in a continued transaction

To run the next queries or mutations in the transaction you started, specify the transaction ID as the id argument of the @transaction. The following example will update two accounts you got in the previous example in the same transaction. This represents a transfer of balance from user1’s account to user2’s account.

mutation Transfer @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
  user1: account_put(put: {key: {id: "user1"}, values: {balance: 750}})
  user2: account_put(put: {key: {id: "user2"}, values: {balance: 1250}})
}

Note that a transaction started with GraphQL has a timeout of 1 minute and will be aborted automatically when it exceeds the timeout.

Commit a transaction

To commit the continued transaction, specify both the id and the commit: true flag as arguments of the @transaction directive.

query GetAndCommit @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9", commit: true) {
  user1: account_get(get: {key: {id: "user1"}}) {
    account { balance }
  }
  user2: account_get(get: {key: {id: "user2"}}) {
    account { balance }
  }
}

Note: If you specify a commit: true flag without an id argument like @transaction(commit: true), a new transaction is started and committed just for one operation. This is exactly the same as not specifying the @transaction directive, as seen in the above examples using GraphiQL. In other words, you can omit the directive itself when it is @transaction(commit: true).

Abort/Rollback a transaction

When you need to abort/rollback a transaction explicitly, you can use the abort or rollback mutation fields interchangeably (both have the same effect and usage). Note that you cannot mix it with any other operations, so you must specify it alone.

mutation AbortTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
  abort
}

or

mutation RollbackTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
  rollback
}