Trying out SurrealDB with Rust

SurrealDB is a multi-model database, which essentially means it allows storage of relation, document, graph, time-series, vector and search as well as geospatial models (as taken from the SurrealDB Overview).

SurrealDB allows queries through an SQL like query language as well as GraphQL, HTTP and RPC.

There are SDKs for Rust (which I’m going to use here) along with JavaScript, Java, Go, Python, .NET and PHP.

Whilst you can install on Windows, Linux and Mac I prefer using Docker, so let’s run up an instance of SurrealDB

docker run --rm -p 8000:8000 surrealdb/surrealdb:latest start --log trace --user root --pass root memory

With a volume, either create yourself a folder (i.e. mkdir mydata) or use an existing path

docker run --rm -p 8000:8000 surrealdb/surrealdb:latest start --log trace --user root --pass root mydb:/mydata/mydatabase.db

If you’d like to run a web based UI for SurrealDB, you can run Surrealist

docker run -d -p 8080:8080 surrealdb/surrealist:latest

Then use this to connect to your running instance, default user is admin, default password is admin (obviously change this in a real world usage).

Once connected via Surrealist we can create a namespace and database, here’s a simple example of such a query run via Surrealist

USE NS myns DB mydb;

Yes, we literally just use the namespace and database for the first time to create both. Now let’s a some data, creating a “table” using

CREATE person CONTENT {
  first_name: "Scooby",
  last_name: "Doo",
  age: 42,
  email: "scooby.doo@example.com"
};

We can query for the list of “person” rows using

SELECT * FROM person;

As you can see, it’s very SQL like syntax with some differences.

We didn’t created an id or such like field, but if you select the rows from the person table you’ve notice something like this

[
  {
    age: 42,
    email: 'scooby.doo@example.com',
    first_name: 'Scooby',
    id: person:77xrs2c05oe9bmtgjbhq,
    last_ame: 'Doo'
  }
]

We could have supplied an id ourselves like this

CREATE person CONTENT {
  first_name: "Fred",
  last_name: "Jones",
  age: 19,
  id: person:fredjones,
  email: "fred.jones@example.com"
};

We can update a row using

UPDATE person:77xrs2c05oe9bmtgjbhq SET name="Scrappy", age = 23;

There are obviously more commands/queries we could use, but let’s move on to using the DB from Rust.

We’ll start by adding a few dependencies to Cargo.toml

[dependencies]
tokio = { version = "1.47.1", features = ["full"] }
surrealdb = "2.3.8"
serde = { version = "1.0.219", features = ["derive"] }

Next update main.rs to look like this

use surrealdb::{Surreal};
use surrealdb::engine::remote::ws::Ws;
use std::error::Error;
use surrealdb::opt::auth::Root;

use surrealdb::sql::Thing;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Person {
    id: Thing,
    first_name: String,
    last_name: String,
    email: String,
    age: u32,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
    db.signin(Root { username: "root", password: "root" }).await?;
    db.use_ns("myns").use_db("mydb").await?;

    let result: Vec<Person> = db.query("SELECT * FROM person").await?.take(0)?;

    println!("{:?}", result);
    Ok(())
}

We’re using the default username and password. Ofcourse you should change the password for this user and create your own user, but for now, let’s just get things up and running.

Notice that we connect to SurrealDB via the web socket.

You may have also noticed that in our Person struct we have an id Thing. This is essentially a record pointer, which has the table name and record id.