Stargate GraphQL CQL-first API QuickStart
Time to complete: 5 minutes
Stargate is a data gateway deployed between client applications and a database. In this QuickStart, you’ll be up and running on your local machine with the GraphQL API plugin that exposes CRUD access to data stored in Cassandra tables.
For more information about the GraphQL API, see the blog post on the GraphQL API. |
Prerequisites
If you haven’t already, create a database using Astra DB. |
You can create an Astra DB database using Astra Portal or the DevOps API. Currently, there are no drivers or other APIs that support creating a database. |
-
Install cURL, a utility for running REST, Document, or GraphQL queries on the command line.
-
[Optional] If you prefer, you can use Postman as a client interface for exploring the APIs
-
You will also find links to downloadable collections and environments in Using Postman
-
-
[Optional] If you going to use the GraphQL API, you will want to use the GraphQL Playground to deploy schema and execute mutations and queries.
-
[Optional] For the REST and Document APIs, you can use the Swagger UI.
Before you get started, set your environment variables to save time developing on your database. There are four environment variables, three of which you will get from the Astra dashboard (database id, region, and keyspace), and one that you must create (token).
-
In Astra DB, select the database to which you want to connect.
-
In your Database Dashboard, select Connect.
-
Select your API.
If you have multiple regions, select the region you want to connect to from the dropdown menu for instructions.
-
Follow the steps to get your application token and set up your environment variables. Or if you have an older Astra Classic database, follow the steps in Authentication for classic databases.
You will need to add this token to the GraphQL Playground in order to authorize
your GraphQL requests. Copy the value after "authToken"
to use later.
Using the GraphQL Playground
The easiest way to get started with GraphQL is to use the built-in GraphQL playground.
In Astra DB, go to the Connect tab for your database, choose GraphQL under the
Connect using an API
and you’ll see instructions for accessing the playground.
The GraphQL playground launches the url:
https://$ASTRA_CLUSTER_ID-$ASTRA_REGION.apps.astra.datastax.com:8080/api/playground
in your browser.
Add your application token to the HTTP HEADERS section at the lower lefthand corner of the GraphQL Playground window:
{"x-cassandra-token":"$AUTH_TOKEN"}
Once in the playground, you can create new schema and interact with the GraphQL APIs. The server paths are structured to provide access to creating and querying schema, as well as querying and modifying Cassandra data:
-
/api/graphql-schema
-
An API for exploring and creating schema, or Data Definition Language (DDL). For example, Astra DB has queries to create, modify, or drop tables, such as
createTable
, ordropTable
.
-
-
/api/graphql/<keyspace>
-
An API for querying and modifying your tables using GraphQL fields. Generally, you will start the playground with
/api/graphql-schema
to create some schema.
-
Creating schema
In order to use the GraphQL API, you must create schema that defines the keyspace and
tables that will store the data. A keyspace is a container for which a
replication factor
defines the number of data replicas the database will store.
Tables consist of columns that have a defined data type. Multiple tables are contained
in a keyspace, but a table cannot be contained in multiple keyspaces.
Check keyspace existence
To check if a keyspace exists, execute a GraphQL query:
# Works in localhost:8080/graphql-schema
# for either CQL-first or schema-first
query GetKeyspace {
keyspace(name: "library") {
name
dcs {
name
replicas
}
tables {
name
columns {
name
kind
type {
basic
info {
name
}
}
}
}
}
}
{
"data": {
"keyspace": {
"name": "library",
"dcs": [],
"tables": [
{
"name": "book",
"columns": [
{
"name": "title",
"kind": "PARTITION",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "author",
"kind": "CLUSTERING",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "format",
"kind": "REGULAR",
"type": {
"basic": "SET",
"info": {
"name": null
}
}
},
{
"name": "genre",
"kind": "REGULAR",
"type": {
"basic": "SET",
"info": {
"name": null
}
}
},
{
"name": "isbn",
"kind": "REGULAR",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "language",
"kind": "REGULAR",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "pub_year",
"kind": "REGULAR",
"type": {
"basic": "INT",
"info": null
}
}
]
},
{
"name": "reader",
"columns": [
{
"name": "name",
"kind": "PARTITION",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "user_id",
"kind": "CLUSTERING",
"type": {
"basic": "UUID",
"info": null
}
},
{
"name": "addresses",
"kind": "REGULAR",
"type": {
"basic": "LIST",
"info": {
"name": null
}
}
},
{
"name": "birthdate",
"kind": "REGULAR",
"type": {
"basic": "DATE",
"info": null
}
},
{
"name": "email",
"kind": "REGULAR",
"type": {
"basic": "SET",
"info": {
"name": null
}
}
},
{
"name": "reviews",
"kind": "REGULAR",
"type": {
"basic": "TUPLE",
"info": {
"name": null
}
}
}
]
}
}
}
}
Create a table
After the keyspace exists, you can create a table by executing mutations. For this example, two tables are created:
# create two tables (book, reader) in library with a single mutation
# DATA TYPES: TEXT, UUID, SET(TEXT), TUPLE(TEXT, INT, DATE), LIST(UDT)
mutation createTables {
book: createTable(
keyspaceName:"library",
tableName:"book",
partitionKeys: [ # The keys required to access your data
{ name: "title", type: {basic: TEXT} }
]
clusteringKeys: [
{ name: "author", type: {basic: TEXT} }
]
)
reader: createTable(
keyspaceName:"library",
tableName:"reader",
partitionKeys: [
{ name: "name", type: {basic: TEXT} }
]
clusteringKeys: [ # Secondary key used to access values within the partition
{ name: "user_id", type: {basic: UUID}, order: "ASC" }
]
values: [
{ name: "birthdate", type: {basic: DATE} }
{ name: "email", type: {basic: SET, info:{ subTypes: [ { basic: TEXT } ] } } }
{ name: "reviews", type: {basic: TUPLE, info: { subTypes: [ { basic: TEXT }, { basic: INT }, { basic: DATE } ] } } }
{ name: "addresses", type: { basic: LIST, info: { subTypes: [ { basic: UDT, info: { name: "address_type", frozen: true } } ] } } }
]
)
}
"data": {
"book": true,
"reader": true
}
}
It is worth noting that one mutation is used to create two tables. Information about partition keys and clustering keys can be found in the CQL reference.
The second table, reader
, also defines a column using a
user-defined type (UDT).
One of these tables includes creating a column with the data type LIST
, an
ordered collection of text values.
Collection (set, list, map) columns
Including a collection in a table has a couple of extra parts:
# create a table with a MAP
# DATA TYPE: TEXT, INT, MAP(TEXT, DATE)
# Sample: btype=Editor, badge_id=1, earned = [Gold:010120, Silver:020221]
mutation createMapTable {
badge: createTable (
keyspaceName:"library",
tableName: "badge",
partitionKeys: [
{name: "btype", type: {basic:TEXT}}
]
clusteringKeys: [
{ name: "badge_id", type: { basic: INT} }
],
ifNotExists:true,
values: [
{name: "earned", type:{basic:LIST { basic:MAP, info:{ subTypes: [ { basic: TEXT }, {basic: DATE}]}}}}
]
)
}
{
"data": {
"badge": true
}
}
This example shows a map. A previous example shows a list. In the next example, a set will be defined.
Add columns to table schema
If you need to add more attributes to something you are storing in a table, you can add one or more columns:
# alter a table and add columns
# DATA TYPES: TEXT, INT, SET(TEXT)
mutation alterTableAddCols {
alterTableAdd(
keyspaceName:"library",
tableName:"book",
toAdd:[
{ name: "isbn", type: { basic: TEXT } }
{ name: "language", type: {basic: TEXT} }
{ name: "pub_year", type: {basic: INT} }
{ name: "genre", type: {basic:SET, info:{ subTypes: [ { basic: TEXT } ] } } }
{ name: "format", type: {basic:SET, info:{ subTypes: [ { basic: TEXT } ] } } }
]
)
}
{
"data": {
"alterTableAdd": true
}
}
Check table and column existence
To check if a table or particular table columns exist, execute a GraphQL query:
query GetTables {
keyspace(name: "library") {
name
tables {
name
columns {
name
kind
type {
basic
info {
name
}
}
}
}
}
}
{
"data": {
"keyspace": {
"name": "library",
"tables": [
{
"name": "reader",
"columns": [
{
"name": "name",
"kind": "PARTITION",
"type": {
"basic": "VARCHAR",
"info": null
}
},
]
},
{
"name": "book",
"columns": [
{
"name": "title",
"kind": "PARTITION",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "author",
"kind": "REGULAR",
"type": {
"basic": "VARCHAR",
"info": null
}
},
{
"name": "isbn",
"kind": "REGULAR",
"type": {
"basic": "VARCHAR",
"info": null
}
}
]
}
]
}
}
}
Because these queries are named, the GraphQL playground will allow you to select
which query to run. The first query will return information about the keyspace
library
and the tables within it. The second query will return just information
about the tables in that keyspace.
Interacting with tables
API generation
Once schema is created, the GraphQL API generates mutations and queries can be used. In the GraphQL playground, expand the tabs on the righthand side labelled "DOCS" or "SCHEMA", to discover the items available and the syntax to use.
For each table in the Cassandra schema that we just created, several GraphQL
fields are created for
handling queries and mutations. For example, the GraphQL API generated for the
books
table is:
schema {
query: Query
mutation: Mutation
}
type Query {
book(value: bookInput, filter: bookFilterInput, orderBy: [bookOrder], options: QueryOptions): bookResult
bookFilter(filter: bookFilterInput!, orderBy: [bookOrder], options: QueryOptions): bookResult
}
type Mutation {
insertbook(value: bookInput!, ifNotExists: Boolean, options: UpdateOptions): bookMutationResult
updatebook(value: bookInput!, ifExists: Boolean, ifCondition: bookFilterInput, options: UpdateOptions): bookMutationResult
deletebook(value: bookInput!, ifExists: Boolean, ifCondition: bookFilterInput, options: UpdateOptions): bookMutationResult
}
The query books()
can query book values by equality. If no value argument is
provided, then the first hundred (default pagesize) values are returned.
Several mutations are created that you can use to insert, update, or delete books. Some important facts about these mutations are:
-
insertBooks()
is an upsert operation if a book with the same information exist, unless theifNotExists
is set to true. -
updateBooks()
is also an upsert operation, and will create a new book if it doesn’t exist, unlessifNotExists
is set to true. -
Using the
ifNotExists
orifCondition
options affects the performance of operations because of the compare-and-set execution path in Cassandra. Under the hood these operations are using a feature in Cassandra called lightweight transactions (LWTs).
As more tables are added to a keyspace, additional GraphQL fields will add query and mutation types that can be used to interact with the table data.
Insert data
Any of the created APIs can be used to interact with the GraphQL data, to write or read data.
First, let’s navigate to your new keyspace library
inside the playground.
Change the location to
http://localhost:8080/graphql/library
and add a couple of books to the book
table:
# insert 2 books in one mutation
mutation insert2Books {
moby: insertbook(value: {title:"Moby Dick", author:"Herman Melville"}) {
value {
title
}
}
catch22: insertbook(value: {title:"Catch-22", author:"Joseph Heller"}) {
value {
title
}
}
}
{
"data": {
"moby": {
"value": {
"title": "Moby Dick"
}
},
"catch22": {
"value": {
"title": "Catch-22"
}
}
}
}
Note that the keyword value
is used twice in the mutation.
The first use defines the value that the record is set to, for instance, the title
to Moby Dick and the author to Herman Melville.
The second use defines the values that will be displayed after the success
of the mutation, so that proper insertion can be verified.
This same method is valid for updates and read queries.
Retrieve data
Let’s check that the data was inserted.
Now let’s search for a particular record using a WHERE
clause. The primary
key of the table can be used in the WHERE
clause, but non-primary key columns
cannot be used unless indexed.
The following query, looking at the location
http://localhost:8080/graphql/library
will get both the title
and the author
for the specified book WHERE
title:"Moby Dick"
:
Update data
Using the column that we added earlier, the data for a book is updated with the
ISBN
value:
mutation updateOneBook {
moby: updatebook(value: {title:"Moby Dick", author:"Herman Melville", isbn: "9780140861723"}, ifExists: true ) {
value {
title
author
isbn
}
}
}
{
"data": {
"moby": {
"value": {
"title": "Moby Dick",
"author": "Herman Melville",
"isbn": "9780140861723"
}
}
}
}
Updates are upserts. If the row doesn’t exist, it will be created. If it does exist, it will be updated with the new row data. |
Delete data
After adding the book "Pride and Prejudice" with an insertBooks()
, you can delete
the book using deleteBooks()
to illustrate deleting data:
mutation deleteOneBook {
PaP: deletebook(value: {title:"Pride and Prejudice", author: "Jane Austen"}, ifExists: true ) {
value {
title
}
}
}
{
"data": {
"PaP": {
"value": {
"title": "Pride and Prejudice"
}
}
}
}
Note the use of ifExists
to validate that the book exists before deleting it.
For more information on the GraphQL API (CQL-first), see Using the GraphQL API (CQL-first) in the Developing with Stargate APIs section.