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().
The two main ways to connect to SurrealDB when getting started are by connecting to a running instance wia the protocol-ws feature, or by running an embedded instance in memory using the kv-mem feature. Each of these can be added via a feature flag in the SDK.
All together, that leads to the following commands to get started:
cargo new my_project cd my_project cargo add surrealdb --features kv-mem,protocol-ws cargo add tokio --features macros,rt-multi-thread
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.
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 secret
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.
Connect to SurrealDB
Open src/main.rs and replace everything with the following code to try out some basic operations using the SurrealDB SDK.
// Connect to the server letdb=Surreal::new::<Ws>("127.0.0.1:8000").await?;
// Signin as a namespace, database, or root user db.signin(Root{ username: "root".to_string(), password: "secret".to_string(), }) .await?;
// Select a specific namespace / database db.use_ns("test").use_db("test").await?;
letsome_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 IndexedResults 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.
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 SurrealValue which allows for both serialization and deserialization between the Rust code and the database.
// Create a new person with a random id letcreated: Option<Record> =db .create("person") .content(Person{ title: "Founder & CEO".to_string(), name: Name{ first: "Tobie".to_string(), last: "Morgan Hitchcock".to_string(), }, 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 letpeople: Vec<Record> =db.select("person").await?; dbg!(people);
// Perform a custom advanced query letmutgroups=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(()) }
Using a static singleton
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.
asyncfnupsert_tobie()->surrealdb::Result<()> { // Create or update a specific record lettobie: Option<Record> =DB .upsert(("person","tobie")) .content(Person{ name: "Tobie".to_string(), marketing: true, }) .await?; dbg!(tobie); Ok(()) }
#[tokio::main] asyncfnmain()->surrealdb::Result<()> { // Connect to the database DB.connect::<Ws>("localhost:8000").await?; // Sign in to the server DB.signin(Root{ username: "root".to_string(), password: "secret".to_string(), }) .await?; // Select a namespace + database DB.use_ns("main").use_db("main").await?; upsert_tobie().await?; Ok(()) }
Other ways to see the results
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 secret --pretty
Inside Surrealist, do the following:
Hover over the current connection and click on "Change connection"
Hover over "New connection" and click the pencil icon
Change the "Method" to "root". Enter "root" for the username and "secret" for the password.
You will now be connected as the root user, and can define and then select the namespace and databases called "main".
Here is the last query in the example above to get started:
The Rust SDK has a single Capabilities struct that is used to allow or limit what users are allowed to do using queries. Each method on this struct is used to configure the capabilities for the database in the same way that capabilities flags are passed in to the surreal start command.
SurrealDB also has a number of experimental capabilities which need to be specifically opted into and are not included inside an --allow-all flag or struct created by the Capabilities::all() function. These can be passed in individually using a slice of the ExperimentalFeature enum inside the .with_experimental_features_allowed method, or all at once with .with_all_experimental_features_allowed().
This configuration on the SDK side is not needed if connecting to a remote instance that has the --allow-experimental flag passed in, but is if using an embedded instance like in the example below.
// DEFINE BUCKET needs the 'files' experimental capability to be enabled println!( "{:?}", db.query("DEFINE BUCKET my_bucket BACKEND 'memory'").await? );
Ok(()) }
Experimenting with an embedded instance
An embedded in-memory instance of SurrealDB does not expose any endpoints. To experiment with such an instance without having to recompile the Rust code, a server can be spun up with a single endpoint that accepts a query. The following example uses Axum to create a localhost:8080/query endpoint that feeds any input into the .query() method, deserializes it into a Value and returns this as a String.
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.
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 secret
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.
Connect to SurrealDB
Open src/main.rs and replace everything with the following code to try out some basic operations using the SurrealDB SDK.
// Connect to the server letdb=Surreal::new::<Ws>("127.0.0.1:8000").await?;
// Signin as a namespace, database, or root user db.signin(Root{ username: "root", password: "secret", }) .await?;
// Select a specific namespace / database db.use_ns("test").use_db("test").await?;
letsome_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.
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.
// Create a new person with a random id letcreated: 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 letpeople: Vec<Record> =db.select("person").await?; dbg!(people);
// Perform a custom advanced query letmutgroups=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(()) }
Using a static singleton
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.
asyncfnupsert_tobie()->surrealdb::Result<()> { // Create or update a specific record lettobie: Option<Record> =DB .upsert(("person","tobie")) .content(Person{ name: "Tobie", marketing: true, }) .await?; dbg!(tobie); Ok(()) }
#[tokio::main] asyncfnmain()->surrealdb::Result<()> { // Connect to the database DB.connect::<Ws>("localhost:8000").await?; // Sign in to the server DB.signin(Root{ username: "root", password: "secret", }) .await?; // Select a namespace + database DB.use_ns("test").use_db("test").await?; upsert_tobie().await?; Ok(()) }
Other ways to see the results
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 secret --namespace test --database test --pretty
Inside Surrealist, do the following:
Hover over the current connection and click on "Change connection"
Hover over "New connection" and click the pencil icon
Change the "Method" to "root". Enter "root" for the username and "secret" for the password.
You will now be connected as the root user, and can define and then select the namespace and databases called "test".
Here is the last query in the example above to get started:
The Rust SDK has a single Capabilities struct that is used to allow or limit what users are allowed to do using queries. Each method on this struct is used to configure the capabilities for the database in the same way that capabilities flags are passed in to the surreal start command.
SurrealDB also has a number of experimental capabilities which need to be specifically opted into and are not included inside an --allow-all flag or struct created by the Capabilities::all() function. These can be passed in individually using a slice of the ExperimentalFeature enum inside the .with_experimental_features_allowed method, or all at once with .with_all_experimental_features_allowed().
println!("{:?}",db.query("DEFINE FIELD comics ON person TYPE option<array<record<comic_book>>> REFERENCE"));
Ok(()) }
Experimenting with an embedded instance
An embedded in-memory instance of SurrealDB does not expose any endpoints. To experiment with such an instance without having to recompile the Rust code, a server can be spun up with a single endpoint that accepts a query. The following example uses Axum to create a localhost:8080/query endpoint that feeds any input into the .query() method, deserializes it into a Value and returns this as a String.