First, create a new project using cargo new
and add the following dependencies:
surrealdb
tokio
, in order to to use the database inside fn main()
. You will most likely want to enable the macros
and rt-multi-thread
features so that the #[tokio::main]
attribute can be used on top of fn main()
.serde
with the derive
feature enabled in order to use the Serialize
and Deserialize
attribute macros on top of your Rust data types to match those sent to and returned from the database.All together, that leads to the following commands to get started:
cargo new my_project cd my_project cargo add surrealdb cargo add tokio --features macros,rt-multi-thread cargo add serde --features derive
The examples inside this SDK manual assume that all of these crates and features are present.
To maximize performance when compiling in release mode, it is recommended to use the following profile inside Cargo.toml
, the same as the profile used by SurrealDB when building each version for release.
[profile.release] lto = true strip = true opt-level = 3 panic = 'abort' codegen-units = 1
Before using cargo run
to try out your code, make sure that the SurrealDB server is running by using the surreal start
command. The following command will start an in-memory server with a single root user at the default address 127.0.0.1:8000
.
surreal start --user root --pass root
If you prefer to do everything through Surrealist, you can also use the Start serving
button to do the same as long as you have Surrealist installed locally on your computer.
Open src/main.rs
and replace everything with the following code to try out some basic operations using the SurrealDB SDK.
use surrealdb::engine::remote::ws::Ws; use surrealdb::opt::auth::Root; use surrealdb::Surreal; async fn main() -> surrealdb::Result<()> { // Connect to the server let db = Surreal::new::<Ws>("127.0.0.1:8000").await?; // Signin as a namespace, database, or root user db.signin(Root { username: "root", password: "root", }) .await?; // Select a specific namespace / database db.use_ns("test").use_db("test").await?; let some_queries = db.query(" RETURN 9; RETURN 10; SELECT * FROM { is: 'Nice database' }; ").await?; dbg!(some_queries); Ok(()) }
Note that the .query()
method is able to hold more than one statement, in this case three statements; i.e. two RETURN
statements and one SELECT
statement. The Response
struct returned contains a field called results
which holds the output of each statement. Note that each result has its own index. This will become useful when using the .take()
method in the example to follow, which can access a result by its index number.
Example outputresults: { 0: ( Stats { execution_time: Some( 64.125µs, ), }, Ok( Number( Int( 9, ), ), ), ), 1: ( Stats { execution_time: Some( 19.791µs, ), }, Ok( Number( Int( 10, ), ), ), ), 2: ( Stats { execution_time: Some( 97.75µs, ), }, Ok( Array( Array( [ Object( Object( { "is": Strand( Strand( "Nice database", ), ), }, ), ), ], ), ), ), ), }
Now that we have the basics down, it is time to try out some other methods like CREATE
and UPDATE
. The most ergonomic way to do this is to use a struct that implements Serialize
for anything we want to pass in, and Deserialize
for anything we have received from the database and want to turn back into a Rust type.
use serde::{Deserialize, Serialize}; use surrealdb::engine::remote::ws::Ws; use surrealdb::opt::auth::Root; use surrealdb::opt::Resource; use surrealdb::RecordId; use surrealdb::Surreal; use surrealdb::Value; struct Name<'a> { first: &'a str, last: &'a str, } struct Person<'a> { title: &'a str, name: Name<'a>, marketing: bool, } struct Responsibility { marketing: bool, } struct Record { id: RecordId, } async fn main() -> surrealdb::Result<()> { let db = Surreal::new::<Ws>("127.0.0.1:8000").await?; db.signin(Root { username: "root", password: "root", }) .await?; db.use_ns("test").use_db("test").await?; // Create a new person with a random id let created: Option<Record> = db .create("person") .content(Person { title: "Founder & CEO", name: Name { first: "Tobie", last: "Morgan Hitchcock", }, marketing: true, }) .await?; dbg!(created); // Update a person record with a specific id // We don't care about the response in this case // so we are just going to use `Resource::from` // to let the compiler return `surrealdb::Value` db.update(Resource::from(("person", "jaime"))) .merge(Responsibility { marketing: true }) .await?; // Select all people records let people: Vec<Record> = db.select("person").await?; dbg!(people); // Perform a custom advanced query let mut groups = db .query("SELECT marketing, count() FROM type::table($table) GROUP BY marketing") .bind(("table", "person")) .await?; // Use .take() to transform the first query result into // anything that can be deserialized, in this case // a Value dbg!(groups.take::<Value>(0).unwrap()); Ok(()) }
A static singleton can be used to ensure that a single database instance is available across very large or complicated applications. With the singleton, only one connection to the database is instantiated, and the database connection does not have to be shared across components or controllers.
The LazyLock
struct below has been available in stable Rust since version 1.80, making it usable without a single external crate.
use serde::{Deserialize, Serialize}; use std::sync::LazyLock; use surrealdb::engine::remote::ws::{Client, Ws}; use surrealdb::opt::auth::Root; use surrealdb::RecordId; use surrealdb::Surreal; struct Record { id: RecordId, } struct Person<'a> { name: &'a str, marketing: bool, } static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init); async fn upsert_tobie() -> surrealdb::Result<()> { // Create or update a specific record let tobie: Option<Record> = DB .upsert(("person", "tobie")) .content(Person { name: "Tobie", marketing: true, }) .await?; dbg!(tobie); Ok(()) } async fn main() -> surrealdb::Result<()> { // Connect to the database DB.connect::<Ws>("localhost:8000").await?; // Sign in to the server DB.signin(Root { username: "root", password: "root", }) .await?; // Select a namespace + database DB.use_ns("test").use_db("test").await?; upsert_tobie().await?; Ok(()) }
Besides printing out the results inside the Rust code above, you can sign in to the database using the CLI or Surrealist to view them.
surreal sql --user root --pass root --namespace test --database test --pretty
Inside Surrealist, do the following:
Here is the last query in the example above to get started:
SELECT marketing, count() FROM person GROUP BY marketing;