SurrealDB Docs Logo

Enter a search query

Quick start

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

Start SurrealDB

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.

Connect to SurrealDB

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; #[tokio::main] 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 output
results: { 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; #[derive(Debug, Serialize)] struct Name<'a> { first: &'a str, last: &'a str, } #[derive(Debug, Serialize)] struct Person<'a> { title: &'a str, name: Name<'a>, marketing: bool, } #[derive(Debug, Serialize)] struct Responsibility { marketing: bool, } #[derive(Debug, Deserialize)] struct Record { id: RecordId, } #[tokio::main] 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(()) }

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.

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; #[derive(Debug, Deserialize)] struct Record { id: RecordId, } #[derive(Debug, Serialize)] 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(()) } #[tokio::main] 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(()) }

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 root --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 both username and 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:

SELECT marketing, count() FROM person GROUP BY marketing;

On this page

© SurrealDB GitHub Discord Community Cloud Features Releases Install