Overview
This quickstart guide will walk you through creating a simple application that connects to ScyllaDB, creates a keyspace and table, inserts data, and queries it back.
This guide assumes you have a ScyllaDB or Cassandra cluster running. If you don’t, see the ScyllaDB documentation for setup instructions.
Prerequisites
Install Rust
Ensure you have Rust 1.85.0 or later installed:
Create a new project
cargo new scylla-quickstart
cd scylla-quickstart
Add dependencies
Add the following to your Cargo.toml: [ dependencies ]
scylla = "1.5"
tokio = { version = "1.40" , features = [ "full" ] }
futures = "0.3"
anyhow = "1.0"
Basic Connection
Let’s start by connecting to your ScyllaDB cluster. Replace the code in src/main.rs:
use anyhow :: Result ;
use scylla :: client :: session :: Session ;
use scylla :: client :: session_builder :: SessionBuilder ;
use std :: env;
#[tokio :: main]
async fn main () -> Result <()> {
// Get ScyllaDB URI from environment or use default
let uri = env :: var ( "SCYLLA_URI" )
. unwrap_or_else ( | _ | "127.0.0.1:9042" . to_string ());
println! ( "Connecting to {uri} ..." );
// Create a session
let session : Session = SessionBuilder :: new ()
. known_node ( uri )
. build ()
. await ? ;
println! ( "Connected successfully!" );
Ok (())
}
Run your application:
Set the SCYLLA_URI environment variable to connect to a different node: SCYLLA_URI = "192.168.1.100:9042" cargo run
Creating Schema
Now let’s create a keyspace and table:
use anyhow :: Result ;
use scylla :: client :: session :: Session ;
use scylla :: client :: session_builder :: SessionBuilder ;
#[tokio :: main]
async fn main () -> Result <()> {
let uri = std :: env :: var ( "SCYLLA_URI" )
. unwrap_or_else ( | _ | "127.0.0.1:9042" . to_string ());
println! ( "Connecting to {uri} ..." );
let session : Session = SessionBuilder :: new ()
. known_node ( uri )
. build ()
. await ? ;
// Create a keyspace
session
. query_unpaged (
"CREATE KEYSPACE IF NOT EXISTS my_keyspace WITH REPLICATION = {
'class' : 'NetworkTopologyStrategy',
'replication_factor' : 1
}" ,
& [],
)
. await ? ;
// Create a table
session
. query_unpaged (
"CREATE TABLE IF NOT EXISTS my_keyspace.users (
user_id int,
username text,
email text,
PRIMARY KEY (user_id)
)" ,
& [],
)
. await ? ;
println! ( "Schema created successfully!" );
Ok (())
}
The query_unpaged method executes a query and fetches all results in a single page. For queries that might return many rows, use query_iter instead.
Inserting Data
Insert data using parameterized queries to prevent CQL injection:
Simple Query
Prepared Statement
// Insert with simple query and parameters
session
. query_unpaged (
"INSERT INTO my_keyspace.users (user_id, username, email) VALUES (?, ?, ?)" ,
( 1 , "alice" , "[email protected] " ),
)
. await ? ;
session
. query_unpaged (
"INSERT INTO my_keyspace.users (user_id, username, email) VALUES (?, ?, ?)" ,
( 2 , "bob" , "[email protected] " ),
)
. await ? ;
Use prepared statements for queries that will be executed multiple times. They are parsed once on the server and provide better performance.
Querying Data
Query data and deserialize results into Rust types:
Tuples
Structs
Untyped Rows
use futures :: TryStreamExt ;
// Query and parse rows as tuples
let mut iter = session
. query_iter ( "SELECT user_id, username, email FROM my_keyspace.users" , & [])
. await ?
. rows_stream :: <( i32 , String , String )>() ? ;
while let Some (( user_id , username , email )) = iter . try_next () . await ? {
println! ( "User {}: {} ({})" , user_id , username , email );
}
Complete Example
Here’s a complete working example combining all the concepts:
use anyhow :: Result ;
use futures :: TryStreamExt ;
use scylla :: DeserializeRow ;
use scylla :: client :: session :: Session ;
use scylla :: client :: session_builder :: SessionBuilder ;
use std :: env;
#[derive( Debug , DeserializeRow )]
struct User {
user_id : i32 ,
username : String ,
email : String ,
}
#[tokio :: main]
async fn main () -> Result <()> {
let uri = env :: var ( "SCYLLA_URI" )
. unwrap_or_else ( | _ | "127.0.0.1:9042" . to_string ());
println! ( "Connecting to {uri} ..." );
let session : Session = SessionBuilder :: new ()
. known_node ( uri )
. build ()
. await ? ;
// Create keyspace
session
. query_unpaged (
"CREATE KEYSPACE IF NOT EXISTS my_keyspace WITH REPLICATION = {
'class' : 'NetworkTopologyStrategy',
'replication_factor' : 1
}" ,
& [],
)
. await ? ;
// Create table
session
. query_unpaged (
"CREATE TABLE IF NOT EXISTS my_keyspace.users (
user_id int,
username text,
email text,
PRIMARY KEY (user_id)
)" ,
& [],
)
. await ? ;
// Prepare insert statement
let prepared = session
. prepare ( "INSERT INTO my_keyspace.users (user_id, username, email) VALUES (?, ?, ?)" )
. await ? ;
// Insert data
session
. execute_unpaged ( & prepared , ( 1 , "alice" , "[email protected] " ))
. await ? ;
session
. execute_unpaged ( & prepared , ( 2 , "bob" , "[email protected] " ))
. await ? ;
session
. execute_unpaged ( & prepared , ( 3 , "charlie" , "[email protected] " ))
. await ? ;
println! ( "Inserted 3 users" );
// Query data
let mut iter = session
. query_iter ( "SELECT user_id, username, email FROM my_keyspace.users" , & [])
. await ?
. rows_stream :: < User >() ? ;
println! ( " \n Users in database:" );
while let Some ( user ) = iter . try_next () . await ? {
println! ( " - User {}: {} ({})" , user . user_id, user . username, user . email);
}
Ok (())
}
Run the complete example:
SCYLLA_URI = "127.0.0.1:9042" cargo run
Advanced Features
Now that you’ve built a basic application, explore these advanced features:
User-Defined Types
Define custom types that map to CQL UDTs:
use scylla :: { DeserializeValue , SerializeValue };
#[derive( Debug , DeserializeValue , SerializeValue )]
struct Address {
street : String ,
city : String ,
zip_code : i32 ,
}
// Create the UDT in ScyllaDB
session
. query_unpaged (
"CREATE TYPE IF NOT EXISTS my_keyspace.address (
street text,
city text,
zip_code int
)" ,
& [],
)
. await ? ;
// Use it in a table
session
. query_unpaged (
"CREATE TABLE IF NOT EXISTS my_keyspace.user_addresses (
user_id int,
address frozen<address>,
PRIMARY KEY (user_id)
)" ,
& [],
)
. await ? ;
// Insert data
let address = Address {
street : "123 Main St" . to_string (),
city : "Springfield" . to_string (),
zip_code : 12345 ,
};
session
. query_unpaged (
"INSERT INTO my_keyspace.user_addresses (user_id, address) VALUES (?, ?)" ,
( 1 , address ),
)
. await ? ;
Metrics and Monitoring
Track driver performance with built-in metrics:
let metrics = session . get_metrics ();
println! ( "Queries requested: {}" , metrics . get_queries_num ());
println! ( "Errors occurred: {}" , metrics . get_errors_num ());
println! ( "Average latency: {} ms" , metrics . get_latency_avg_ms () ? );
println! ( "99.9th percentile: {} ms" , metrics . get_latency_percentile_ms ( 99.9 ) ? );
println! ( "Total connections: {}" , metrics . get_total_connections ());
Enable the metrics feature flag in your Cargo.toml to use metrics functionality.
Authentication
Connect to a cluster with authentication:
let session = SessionBuilder :: new ()
. known_node ( "127.0.0.1:9042" )
. user ( "cassandra" , "cassandra" )
. build ()
. await ? ;
Batch Statements
Execute multiple statements atomically:
use scylla :: statement :: batch :: Batch ;
let mut batch = Batch :: default ();
batch . append_statement ( "INSERT INTO my_keyspace.users (user_id, username, email) VALUES (?, ?, ?)" );
batch . append_statement ( "INSERT INTO my_keyspace.users (user_id, username, email) VALUES (?, ?, ?)" );
session
. batch (
& batch ,
(( 4 , "dave" , "[email protected] " ), ( 5 , "eve" , "[email protected] " )),
)
. await ? ;
Next Steps
Connection Learn about connection pooling and cluster discovery
Queries Master different query types and execution patterns
Type System Understand CQL to Rust type mappings
Examples Explore more examples in the repository