Quick Start
Get started with SlateDB in minutes
SlateDB is implemented in Rust and ships official bindings for Go, Java, Node.js, and Python. Pick your language below to install the library and run a minimal example backed by an in-memory object store.
Installation
Section titled “Installation”cargo add slatedb tokio --features tokio/macros,tokio/rt-multi-threadgo get slatedb.io/slatedb-goThe Go binding uses cgo and links against the slatedb_uniffi shared library. You need Go 1.25+, CGO_ENABLED=1, a C toolchain, and the slatedb_uniffi library on your loader path.
Gradle:
dependencies { implementation("io.slatedb:slatedb-uniffi:<version>")}Maven:
<dependency> <groupId>io.slatedb</groupId> <artifactId>slatedb-uniffi</artifactId> <version><version></version></dependency>Requires Java 22 or newer.
npm install @slatedb/uniffiRequires Node.js 20 or newer.
pip install slatedbRequires Python 3.10 or newer.
SlateDB reads and writes through an object store. Every binding exposes ObjectStore.resolve(...) (or InMemory::new() in Rust) which accepts any URL supported by Rust’s object_store crate, including memory:///, file:///..., s3://, gs://, and az://. The examples below use memory:/// so they run without any external setup.
SlateDB uses tokio as its async runtime and object_store to interact with object storage.
use slatedb::{object_store::memory::InMemory, Db, Error};use std::sync::Arc;
#[tokio::main]async fn main() -> Result<(), Error> { // Setup let object_store = Arc::new(InMemory::new()); let kv_store = Db::open("/tmp/slatedb_full_example", object_store).await?;
// Put let key = b"test_key"; let value = b"test_value"; kv_store.put(key, value).await?;
// Get assert_eq!(kv_store.get(key).await?, Some("test_value".into()));
// Delete kv_store.delete(key).await?; assert!(kv_store.get(key).await?.is_none());
kv_store.put(b"test_key1", b"test_value1").await?; kv_store.put(b"test_key2", b"test_value2").await?; kv_store.put(b"test_key3", b"test_value3").await?; kv_store.put(b"test_key4", b"test_value4").await?;
// Scan over unbound range let mut iter = kv_store.scan::<Vec<u8>, _>(..).await?; let mut count = 1; while let Ok(Some(item)) = iter.next().await { assert_eq!(item.key, format!("test_key{count}").into_bytes()); assert_eq!(item.value, format!("test_value{count}").into_bytes()); count += 1; }
// Scan over bound range let mut iter = kv_store.scan("test_key1"..="test_key2").await?; let kv1 = iter.next().await?.unwrap(); assert_eq!(kv1.key, b"test_key1".as_slice()); assert_eq!(kv1.value, b"test_value1".as_slice());
let kv2 = iter.next().await?.unwrap(); assert_eq!(kv2.key, b"test_key2".as_slice()); assert_eq!(kv2.value, b"test_value2".as_slice());
// Seek ahead to next key let mut iter = kv_store.scan::<Vec<u8>, _>(..).await?; let next_key = b"test_key4"; iter.seek(next_key).await?; let kv4 = iter.next().await?.unwrap(); assert_eq!(kv4.key, b"test_key4".as_slice()); assert_eq!(kv4.value, b"test_value4".as_slice()); assert_eq!(iter.next().await?, None);
// Close kv_store.close().await?;
Ok(())}package main
import ( "bytes" "fmt"
slatedb "slatedb.io/slatedb-go/uniffi")
func main() { store, err := slatedb.ObjectStoreResolve("memory:///") if err != nil { panic(err) } defer store.Destroy()
builder := slatedb.NewDbBuilder("example-db", store) defer builder.Destroy()
db, err := builder.Build() if err != nil { panic(err) } defer db.Destroy()
if _, err := db.Put([]byte("hello"), []byte("world")); err != nil { panic(err) }
value, err := db.Get([]byte("hello")) if err != nil { panic(err) } if value == nil || !bytes.Equal(*value, []byte("world")) { panic("unexpected value") }
fmt.Println(string(*value))
if err := db.Shutdown(); err != nil { panic(err) }}Handle types own Rust-side resources: call Shutdown() on databases and readers, and Destroy() on handles when you are done with them.
Most database operations return CompletableFuture. Native-backed handles implement AutoCloseable and should be closed.
import io.slatedb.uniffi.Db;import io.slatedb.uniffi.DbBuilder;import io.slatedb.uniffi.ObjectStore;
import java.nio.charset.StandardCharsets;import java.util.Arrays;import java.util.concurrent.CompletableFuture;import java.util.concurrent.TimeUnit;
public final class Main { private static <T> T await(CompletableFuture<T> future) throws Exception { return future.get(30, TimeUnit.SECONDS); }
public static void main(String[] args) throws Exception { byte[] key = "hello".getBytes(StandardCharsets.UTF_8); byte[] value = "world".getBytes(StandardCharsets.UTF_8);
try (ObjectStore store = ObjectStore.resolve("memory:///"); DbBuilder builder = new DbBuilder("demo-db", store)) { Db db = await(builder.build()); try (db) { await(db.put(key, value));
byte[] read = await(db.get(key)); if (read == null || !Arrays.equals(read, value)) { throw new IllegalStateException("unexpected value"); }
System.out.println(new String(read, StandardCharsets.UTF_8)); await(db.shutdown()); } } }}Keys and values are binary; pass Buffer or Uint8Array. Most operations are async and should be awaited. Native-backed handles also expose dispose() for deterministic cleanup after shutdown() or when abandoning a builder.
import assert from "node:assert/strict";import { DbBuilder, ObjectStore } from "@slatedb/uniffi";
async function main() { const store = ObjectStore.resolve("memory:///"); let db;
try { const builder = new DbBuilder("demo-db", store); try { db = await builder.build(); } finally { builder.dispose(); }
const key = Buffer.from("hello"); const value = Buffer.from("world");
await db.put(key, value);
const read = await db.get(key); assert.deepEqual(read, value);
console.log(Buffer.from(read).toString("utf8")); } finally { if (db != null) { await db.shutdown(); db.dispose(); } store.dispose(); }}
main().catch((error) => { console.error(error); process.exitCode = 1;});Most database operations are async and should be used with asyncio. WriteBatch is mutated synchronously and then consumed by db.write(...). Call db.shutdown() to close native resources.
import asyncio
from slatedb.uniffi import ( DbBuilder, IsolationLevel, ObjectStore, WriteBatch,)
async def main() -> None: store = ObjectStore.resolve("memory:///") builder = DbBuilder("demo-db", store) db = await builder.build()
try: await db.put(b"user:1", b"Alice") value = await db.get(b"user:1") assert value == b"Alice"
batch = WriteBatch() batch.put(b"user:2", b"Bob") batch.put(b"user:3", b"Carol") await db.write(batch)
scan = await db.scan_prefix(b"user:") while (row := await scan.next()) is not None: print(row.key, row.value)
tx = await db.begin(IsolationLevel.SERIALIZABLE_SNAPSHOT) await tx.put(b"user:4", b"Dora") await tx.commit() finally: await db.shutdown()
asyncio.run(main())