Part 5 of 8
Batteries included: an embedded FalkorDB you can cargo run
Every "getting started" guide has the same speed bump on line one: first, run a server. It's a
small ask, but it's friction at the exact moment someone is deciding whether your library is worth
their afternoon. So we removed it. With the embedded feature, the client can start a real FalkorDB
for you, in-process, and shut it down when you're done.
What "embedded" actually does
The embedded server spins up redis-server as a child process with the FalkorDB module loaded, waits
until it answers PING, hands you a connected client, and tears the whole thing down on drop. The
interesting part is where the module comes from.
sequenceDiagram
autonumber
participant App
participant E as EmbeddedServer
participant Cache as module cache / build-time bundle
participant R as redis-server (child process)
participant DB as FalkorDB module
App->>E: build() with FalkorConnectionInfo::Embedded
E->>R: check `redis-server --version` (must be >= 8.0)
E->>Cache: resolve falkordb.so (cached / downloaded / embedded bytes)
Cache-->>E: module path
E->>R: spawn redis-server --loadmodule falkordb.so
E->>R: poll PING until ready
R-->>E: PONG
E-->>App: connected client
Note over App,DB: on drop, the child server is shut down for you
There are three ways the falkordb.so module gets resolved, in increasing order of "I have no network
and I like it that way":
- Auto-download (default). If the module isn't already present, it's fetched from the official
releases, verified against a pinned SHA-256 checksum, and cached locally. First run pays the
download; every run after is instant. The
redis-serverbinary itself is not downloaded — it's found on yourPATH— so this leans on a tool you already have rather than smuggling in a binary. - Already-installed / offline. Point at an existing module, or disable downloading and let it search common system locations. No network at all.
- Build-time bundle. Enable the
embedded-bundlefeature instead, and abuild.rsfetches the module for your build target at compile time and bakes it into your binary withinclude_bytes!. The running process then needs zero network — it extracts the embedded bytes and goes. This is the one for air-gapped and network-isolated deployments, and it's why the feature exists.
Here's the real examples/embedded_usage.rs — note that the usage is identical regardless of how
the module is sourced; only the EmbeddedConfig differs:
/*
* Copyright FalkorDB Ltd. 2023 - present
* Licensed under the MIT License.
*/
//! Example demonstrating the embedded FalkorDB server feature.
//!
//! This example shows how to use FalkorDB with an embedded Redis server
//! that has the FalkorDB module loaded. This is useful for:
//! - Testing without external dependencies
//! - Embedded applications
//! - Quick prototyping
//!
//! # Self-Contained Mode
//!
//! The embedded server can auto-provision the FalkorDB **module** via the
//! `auto_download` option (enabled by default). When enabled, a missing
//! `falkordb.so` is downloaded from the official FalkorDB releases, verified
//! against a pinned SHA-256 checksum and cached locally, so the embedded server
//! "just works" without a pre-installed module.
//!
//! The `redis-server` binary is **not** downloaded — it is located on the host
//! via `PATH` or `redis_server_path`. Install it from your package manager
//! (e.g. `brew install redis`, `apt-get install redis-server`).
//!
//! # Using an already-installed FalkorDB
//!
//! To run fully offline against binaries already on the machine, either set
//! `falkordb_module_path` explicitly or disable `auto_download` (common system
//! locations are still searched). See `already_installed_config()` below.
//!
//! # Build-time bundle (offline at runtime)
//!
//! For network-isolated deployments, enable the `embedded-bundle` feature instead
//! of `embedded`. A `build.rs` fetches the module for the build target at compile
//! time and embeds it in the binary, so the running process needs **no network**.
//! The same `EmbeddedConfig` / API shown here works unchanged; only the module
//! source differs. Run it with:
//! ```bash
//! cargo run --example embedded_usage --features embedded-bundle
//! ```
//!
//! # Platform-Specific Requirements
//!
//! ## macOS
//! The FalkorDB module requires OpenMP (libomp) on macOS. If not already installed via
//! Homebrew, install it with: `brew install libomp`
//!
//! # License Note
//!
//! The FalkorDB module binary (downloaded at runtime, or embedded at build time
//! with `embedded-bundle`) is licensed under the Server Side Public License
//! (SSPL) v1. See https://www.falkordb.com/ for licensing details.
//!
//! # Running this example
//! ```bash
//! cargo run --example embedded_usage --features embedded
//! ```
use falkordb::{EmbeddedConfig, FalkorClientBuilder, FalkorConnectionInfo, FalkorResult};
/// Build a config that uses binaries already installed on the machine, without
/// any network access: redis-server is taken from `PATH` and the FalkorDB
/// module from the given path (or common system locations when left as `None`).
#[allow(dead_code)]
fn already_installed_config() -> EmbeddedConfig {
EmbeddedConfig {
// Disable downloading: resolve only explicit/system binaries.
auto_download: false,
// Optionally pin an installed module; `None` searches system locations.
falkordb_module_path: None,
..Default::default()
}
}
fn main() -> FalkorResult<()> {
println!("Starting embedded FalkorDB example...");
// Default config: download + cache the FalkorDB module if missing, and find
// redis-server on PATH. Swap in `already_installed_config()` to run offline
// against an existing install instead.
let embedded_config = EmbeddedConfig::default();
// You can also customize the configuration:
// let embedded_config = EmbeddedConfig {
// redis_server_path: Some(PathBuf::from("/path/to/redis-server")),
// falkordb_module_path: Some(PathBuf::from("/path/to/falkordb.so")),
// db_dir: Some(PathBuf::from("/tmp/my_falkordb")),
// db_filename: "mydb.rdb".to_string(),
// socket_path: Some(PathBuf::from("/tmp/falkordb.sock")),
// start_timeout: Duration::from_secs(10),
// auto_download: true,
// falkordb_version: None, // or Some("v4.18.10".to_string())
// cache_dir: None, // or Some(PathBuf::from("/tmp/fk-cache"))
// };
println!("Creating client with embedded FalkorDB server...");
// Build a client with embedded FalkorDB
let client = FalkorClientBuilder::new()
.with_connection_info(FalkorConnectionInfo::Embedded(embedded_config))
.build()?;
println!("Connected to embedded FalkorDB server!");
// Now use the client normally
let mut graph = client.select_graph("social");
// Create some nodes
println!("Creating nodes...");
graph
.query("CREATE (:Person {name: 'Alice', age: 30})")
.execute()?;
graph
.query("CREATE (:Person {name: 'Bob', age: 25})")
.execute()?;
graph
.query("CREATE (:Person {name: 'Charlie', age: 35})")
.execute()?;
// Create relationships
println!("Creating relationships...");
graph
.query(
"MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) CREATE (a)-[:KNOWS]->(b)",
)
.execute()?;
graph
.query(
"MATCH (b:Person {name: 'Bob'}), (c:Person {name: 'Charlie'}) CREATE (b)-[:KNOWS]->(c)",
)
.execute()?;
// Query the graph
println!("\nQuerying the graph:");
let result = graph
.query("MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age")
.execute()?;
println!("Found {} results:", result.data.len());
for row in result.data {
println!(" {:?}", row);
}
// Query with relationships
println!("\nQuerying relationships:");
let result = graph
.query("MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name")
.execute()?;
println!("Found {} relationships:", result.data.len());
for row in result.data {
println!(" {:?}", row);
}
// Clean up
println!("\nDeleting graph...");
graph.delete()?;
println!("Example completed successfully!");
println!("The embedded server will be automatically shut down when the client is dropped.");
Ok(())
}Source: examples/embedded_usage.rs — compiled in CI.
The detail I'm quietly proud of
Two, actually.
First, the embedded server now pre-flights redis-server --version and fails early, with a clear
message, if it's older than the 8.0 the module requires. The failure mode it replaces — a server that
starts and then mysteriously can't load the module — is exactly the kind of thing that eats an hour of
someone's day. Fail fast, fail legible.
Second, the build-time bundle is genuinely hermetic. The checksum verification on the download path isn't decoration: a graph module is native code you're loading into your process, and "download some bytes and run them" without verifying them is how you end up in an incident review. Pinned version, pinned checksum, or embedded-at-build-time bytes. Pick your paranoia level.
Why this matters more for a graph database
Graph code is fiddly to test. You want a real engine — Cypher semantics, real planning, real
results — not a mock that lies to you. Embedded mode means your integration tests boot an actual
FalkorDB per run with no external setup, and your CI doesn't need a service container just to check
that a MATCH returns what you think. It collapses the distance between "I have an idea" and "I ran
it against the real thing" to a single cargo run. That distance is where adoption lives or dies.
Next: the values inside those nodes. Part 6 is about mapping
rows straight into your structs with serde, and decoding temporal values into types with actual
arithmetic.