Nexus.SDK 2.5.0

dotnet add package Nexus.SDK --version 2.5.0
                    
NuGet\Install-Package Nexus.SDK -Version 2.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nexus.SDK" Version="2.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nexus.SDK" Version="2.5.0" />
                    
Directory.Packages.props
<PackageReference Include="Nexus.SDK" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nexus.SDK --version 2.5.0
                    
#r "nuget: Nexus.SDK, 2.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nexus.SDK@2.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nexus.SDK&version=2.5.0
                    
Install as a Cake Addin
#tool nuget:?package=Nexus.SDK&version=2.5.0
                    
Install as a Cake Tool

Nexus .NET SDK

Official .NET client library for Nexus, a high-performance Neo4j-compatible graph database.

Compatibility: SDK 2.5.0 ↔ nexus-server 2.5.0. SDK and server move in lockstep on the same X.Y.Z train. See docs/COMPATIBILITY_MATRIX.md.

Features

  • Binary RPC by default — connects over nexus://127.0.0.1:15475 using length-prefixed MessagePack; 3–10× lower latency and 40–60% smaller payloads than the legacy HTTP path.
  • HTTP fallback in one line — pass an http://… URL or set Transport = TransportMode.Http to use the REST transport.
  • Complete Cypher support — execute any Cypher query with parameters over both transports.
  • CRUD helpers — simplified methods for nodes and relationships (REST under the hood; call ExecuteCypherAsync for full RPC coverage).
  • Transaction support — full ACID transaction management with async/await.
  • Schema management — indexes, labels, and relationship types.
  • Authentication — API keys or basic auth.
  • Async/await — fully asynchronous API with CancellationToken support.
  • Type-safe — strongly typed models with nullable reference types.
  • .NET 8+ — built for modern .NET with latest language features.

Installation

NuGet Package Manager

dotnet add package Nexus.SDK --version 2.5.0

.csproj Reference

<PackageReference Include="Nexus.SDK" Version="2.5.0" />

Quick Start (RPC — default)

using Nexus.SDK;

// Defaults to nexus://127.0.0.1:15475 (binary RPC).
await using var client = new NexusClient(new NexusClientConfig());

var result = await client.ExecuteCypherAsync("RETURN 1 AS one");
Console.WriteLine($"{result.Rows[0][0]} via {client.EndpointDescription()}");

Transports

URL form Transport Default port Use case
nexus://host[:port] Binary RPC (MessagePack) 15475 Default. Lowest latency.
http://host[:port] HTTP/JSON 15474 Proxies, firewalls, tooling.
https://host[:port] HTTPS/JSON 443 Public-internet HTTP with TLS.
resp3://host[:port] RESP3 (reserved) 15476 Not yet shipped — throws.

Precedence: URL scheme > NEXUS_SDK_TRANSPORT env var > Config.Transport > default (NexusRpc).

// HTTP fallback
var config = new NexusClientConfig
{
    BaseUrl = "http://localhost:15474",
    ApiKey = Environment.GetEnvironmentVariable("NEXUS_API_KEY"),
};
await using var client = new NexusClient(config);

// Transport hint on a bare URL
var config2 = new NexusClientConfig
{
    BaseUrl = "host:15474",
    Transport = Nexus.SDK.Transports.TransportMode.Http,
};

Full cross-SDK spec: docs/specs/sdk-transport.md.

Advanced Usage

using Nexus.SDK;

var config = new NexusClientConfig
{
    BaseUrl = "nexus://127.0.0.1:15475",
    ApiKey = "your-api-key",   // Optional
    Timeout = TimeSpan.FromSeconds(30),
};

await using var client = new NexusClient(config);

// Check connection
await client.PingAsync();

// Execute Cypher query
var result = await client.ExecuteCypherAsync(@"
    MATCH (n:Person)
    WHERE n.age > $minAge
    RETURN n.name, n.age
    ORDER BY n.age DESC
", new Dictionary<string, object?>
{
    ["minAge"] = 25
});

// Process results
foreach (var row in result.Rows)
{
    Console.WriteLine($"Name: {row["n.name"]}, Age: {row["n.age"]}");
}

Console.WriteLine($"Query took {result.Stats?.ExecutionTimeMs:F2}ms");

External IDs

Nodes can carry a caller-supplied external id in prefixed string form. Supported variants: sha256:<64-hex>, blake3:<64-hex>, sha512:<128-hex>, uuid:<canonical>, str:<utf8 ≤256 B>, bytes:<hex ≤128 chars>.

await using var client = new NexusClient(new NexusClientConfig
{
    BaseUrl = "http://localhost:15474",
});

// Create a node with a stable external id
var create = await client.CreateNodeWithExternalIdAsync(
    labels: new List<string> { "Document" },
    properties: new Dictionary<string, object?> { ["title"] = "Phase 10 spec" },
    externalId: $"str:doc-phase10",
    conflictPolicy: "replace");   // "error" | "match" | "replace"

// Retrieve by external id
var resp = await client.GetNodeByExternalIdAsync("str:doc-phase10");
Console.WriteLine($"Found node: {resp.Node?.Id}");

// Cypher _id round-trip
var result = await client.ExecuteCypherAsync(
    "CREATE (n:Doc {_id: 'str:doc-phase10-cypher'}) RETURN n._id");
Console.WriteLine(result.Rows[0][0]?.ToString()); // str:doc-phase10-cypher

Run the live test suite (requires a running server):

NEXUS_LIVE_HOST=http://localhost:15474 dotnet test --filter "category=live"

Usage Examples

Creating Nodes

// Create a single node
var node = await client.CreateNodeAsync(
    new List<string> { "Person" },
    new Dictionary<string, object?>
    {
        ["name"] = "John Doe",
        ["age"] = 30,
        ["email"] = "john@example.com"
    });

Console.WriteLine($"Created node with ID: {node.Id}");

// Batch create multiple nodes
var nodes = await client.BatchCreateNodesAsync(new List<NodeInput>
{
    new()
    {
        Labels = new List<string> { "Person" },
        Properties = new Dictionary<string, object?> { ["name"] = "Alice", ["age"] = 28 }
    },
    new()
    {
        Labels = new List<string> { "Person" },
        Properties = new Dictionary<string, object?> { ["name"] = "Bob", ["age"] = 32 }
    }
});

Console.WriteLine($"Created {nodes.Count} nodes");

Creating Relationships

// Create a relationship
var rel = await client.CreateRelationshipAsync(
    node1.Id,
    node2.Id,
    "KNOWS",
    new Dictionary<string, object?>
    {
        ["since"] = "2020",
        ["strength"] = 0.8
    });

Console.WriteLine($"Created relationship: {rel.Type}");

// Batch create relationships
var rels = await client.BatchCreateRelationshipsAsync(new List<RelationshipInput>
{
    new()
    {
        StartNode = "1",
        EndNode = "2",
        Type = "KNOWS",
        Properties = new Dictionary<string, object?> { ["since"] = "2020" }
    },
    new()
    {
        StartNode = "2",
        EndNode = "3",
        Type = "WORKS_WITH",
        Properties = new Dictionary<string, object?> { ["project"] = "GraphDB" }
    }
});

Reading and Updating Data

// Get node by ID
var node = await client.GetNodeAsync("1");
Console.WriteLine($"Node: {node.Properties["name"]}");

// Update node properties
var updated = await client.UpdateNodeAsync("1", new Dictionary<string, object?>
{
    ["age"] = 31,
    ["updated_at"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});

// Get relationship
var rel = await client.GetRelationshipAsync("r1");

// Delete node
await client.DeleteNodeAsync("1");

// Delete relationship
await client.DeleteRelationshipAsync("r1");

Transactions

// Begin transaction
using var tx = await client.BeginTransactionAsync();

try
{
    // Execute queries in transaction
    await tx.ExecuteCypherAsync(
        "CREATE (n:Person {name: $name})",
        new Dictionary<string, object?> { ["name"] = "Transaction User" });

    await tx.ExecuteCypherAsync(
        "CREATE (n:Person {name: $name})",
        new Dictionary<string, object?> { ["name"] = "Another User" });

    // Commit transaction
    await tx.CommitAsync();
    Console.WriteLine("Transaction committed successfully");
}
catch (Exception ex)
{
    // Rollback on error
    await tx.RollbackAsync();
    Console.WriteLine($"Transaction rolled back: {ex.Message}");
}

Schema Management

// List all labels. Each entry is a `LabelInfo { Name, Id }` —
// the `Id` is the catalog id allocated by the engine, not a count.
// (Renamed from a JSON tuple ["Person", 0] in nexus-server 1.15+,
// see https://github.com/hivellm/nexus/issues/2.)
var labels = await client.ListLabelsAsync();
foreach (var label in labels)
    Console.WriteLine($"  {label.Name} (id={label.Id})");

// List all relationship types. Each entry is a `RelTypeInfo`.
var types = await client.ListRelationshipTypesAsync();
foreach (var relType in types)
    Console.WriteLine($"  {relType.Name} (id={relType.Id})");

// Create index
await client.CreateIndexAsync("person_name_idx", "Person", new List<string> { "name" });

// List indexes
var indexes = await client.ListIndexesAsync();
foreach (var idx in indexes)
{
    Console.WriteLine($"Index: {idx.Name} on {idx.Label}({string.Join(", ", idx.Properties)})");
}

// Delete index
await client.DeleteIndexAsync("person_name_idx");

Cancellation and Timeouts

// Use CancellationToken
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

try
{
    var result = await client.ExecuteCypherAsync(
        "MATCH (n) RETURN count(n)",
        null,
        cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Query was cancelled or timed out");
}

// Use timeout per request
var config = new NexusClientConfig
{
    BaseUrl = "http://localhost:15474",
    Timeout = TimeSpan.FromSeconds(10) // Global timeout
};

Error Handling

try
{
    var result = await client.ExecuteCypherAsync("INVALID QUERY");
}
catch (NexusApiException ex)
{
    Console.WriteLine($"HTTP {ex.StatusCode}: {ex.ResponseBody}");

    switch (ex.StatusCode)
    {
        case 400:
            Console.WriteLine("Bad request - check your query syntax");
            break;
        case 401:
            Console.WriteLine("Unauthorized - check your API key");
            break;
        case 404:
            Console.WriteLine("Not found");
            break;
        case 500:
            Console.WriteLine("Server error");
            break;
    }
}
catch (NexusException ex)
{
    Console.WriteLine($"Nexus SDK error: {ex.Message}");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Network error: {ex.Message}");
}

Authentication

API Key

var client = new NexusClient(new NexusClientConfig
{
    BaseUrl = "http://localhost:15474",
    ApiKey = "your-api-key"
});

Username/Password

var client = new NexusClient(new NexusClientConfig
{
    BaseUrl = "http://localhost:15474",
    Username = "admin",
    Password = "password"
});

Bearer Token

var client = new NexusClient(new NexusClientConfig
{
    BaseUrl = "http://localhost:15474"
});

// Set token manually after authentication
client.SetToken("your-jwt-token");

High Availability with Replication

Nexus supports master-replica replication for high availability and read scaling. Use the master for all write operations and replicas for read operations.

NexusCluster Implementation

using Nexus.SDK;

namespace Nexus.SDK;

/// <summary>
/// Client for Nexus cluster with master-replica topology.
/// Routes writes to master, reads to replicas (round-robin).
/// </summary>
public class NexusCluster : IAsyncDisposable
{
    private readonly NexusClient _master;
    private readonly List<NexusClient> _replicas;
    private int _replicaIndex;

    /// <summary>
    /// Creates a new cluster client.
    /// </summary>
    /// <param name="masterUrl">URL of the master node (for writes)</param>
    /// <param name="replicaUrls">List of replica URLs (for reads)</param>
    public NexusCluster(string masterUrl, IEnumerable<string> replicaUrls)
    {
        _master = new NexusClient(new NexusClientConfig { BaseUrl = masterUrl });
        _replicas = replicaUrls
            .Select(url => new NexusClient(new NexusClientConfig { BaseUrl = url }))
            .ToList();
        _replicaIndex = 0;
    }

    /// <summary>
    /// Gets the next replica using thread-safe round-robin selection.
    /// </summary>
    private NexusClient GetNextReplica()
    {
        if (_replicas.Count == 0)
            return _master;

        var index = Interlocked.Increment(ref _replicaIndex) % _replicas.Count;
        return _replicas[index];
    }

    /// <summary>
    /// Executes a write query on the master node.
    /// </summary>
    public Task<QueryResult> WriteAsync(
        string query,
        Dictionary<string, object?>? parameters = null,
        CancellationToken cancellationToken = default)
    {
        return _master.ExecuteCypherAsync(query, parameters, cancellationToken);
    }

    /// <summary>
    /// Executes a read query on a replica node (round-robin selection).
    /// </summary>
    public Task<QueryResult> ReadAsync(
        string query,
        Dictionary<string, object?>? parameters = null,
        CancellationToken cancellationToken = default)
    {
        return GetNextReplica().ExecuteCypherAsync(query, parameters, cancellationToken);
    }

    /// <summary>
    /// Gets the master client for direct access.
    /// </summary>
    public NexusClient Master => _master;

    /// <summary>
    /// Gets all replica clients.
    /// </summary>
    public IReadOnlyList<NexusClient> Replicas => _replicas.AsReadOnly();

    /// <summary>
    /// Disposes all client connections.
    /// </summary>
    public async ValueTask DisposeAsync()
    {
        _master.Dispose();
        foreach (var replica in _replicas)
        {
            replica.Dispose();
        }
        await Task.CompletedTask;
    }
}

Usage Example

using Nexus.SDK;

// Connect to cluster
// Master handles all writes, replicas handle reads
await using var cluster = new NexusCluster(
    "http://master:15474",
    new[] { "http://replica1:15474", "http://replica2:15474" }
);

// Write operations go to master
await cluster.WriteAsync(
    "CREATE (n:Person {name: $name, age: $age}) RETURN n",
    new Dictionary<string, object?>
    {
        ["name"] = "Alice",
        ["age"] = 30
    }
);

// Read operations are distributed across replicas
var result = await cluster.ReadAsync(
    "MATCH (n:Person) WHERE n.age > $minAge RETURN n",
    new Dictionary<string, object?> { ["minAge"] = 25 }
);
Console.WriteLine($"Found {result.Rows.Count} persons");

// High-volume reads are load-balanced across replicas
for (int i = 0; i < 100; i++)
{
    await cluster.ReadAsync("MATCH (n) RETURN count(n) as total");
}

Replication Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Application                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │              NexusCluster Client                     │   │
│   │   WriteAsync() ────────┐   ReadAsync() ──────────┐   │  │
│   └────────────────────────┼─────────────────────────┼───┘  │
└────────────────────────────┼─────────────────────────┼──────┘
                             │                         │
                             ▼                         ▼
              ┌───────────────────┐     ┌─────────────────────┐
              │      MASTER       │     │      REPLICAS       │
              │   (writes only)   │────▶│   (reads only)      │
              │                   │ WAL │  ┌───────────────┐  │
              │ • CREATE          │────▶│  │   Replica 1   │  │
              │ • UPDATE          │     │  └───────────────┘  │
              │ • DELETE          │     │  ┌───────────────┐  │
              │ • MERGE           │────▶│  │   Replica 2   │  │
              └───────────────────┘     │  └───────────────┘  │
                                        └─────────────────────┘

Starting a Nexus Cluster

# Start master node
NEXUS_REPLICATION_ROLE=master \
NEXUS_REPLICATION_BIND_ADDR=0.0.0.0:15475 \
./nexus-server

# Start replica 1
NEXUS_REPLICATION_ROLE=replica \
NEXUS_REPLICATION_MASTER_ADDR=master:15475 \
./nexus-server

# Start replica 2
NEXUS_REPLICATION_ROLE=replica \
NEXUS_REPLICATION_MASTER_ADDR=master:15475 \
./nexus-server

Monitoring Replication Status

using System.Net.Http.Json;

public record ReplicationStatus(
    string Role,
    bool Running,
    string Mode,
    int? ReplicaCount
);

public record MasterStats(
    ulong EntriesReplicated,
    uint ConnectedReplicas
);

public record ReplicaInfo(
    string Id,
    string Addr,
    ulong Lag,
    bool Healthy
);

public record ReplicasResponse(List<ReplicaInfo> Replicas);

public static async Task CheckReplicationStatus(string masterUrl)
{
    using var httpClient = new HttpClient();

    // Check master status
    var status = await httpClient.GetFromJsonAsync<ReplicationStatus>(
        $"{masterUrl}/replication/status"
    );
    Console.WriteLine($"Role: {status?.Role}");
    Console.WriteLine($"Running: {status?.Running}");
    Console.WriteLine($"Connected replicas: {status?.ReplicaCount ?? 0}");

    // Get master stats
    var stats = await httpClient.GetFromJsonAsync<MasterStats>(
        $"{masterUrl}/replication/master/stats"
    );
    Console.WriteLine($"Entries replicated: {stats?.EntriesReplicated}");
    Console.WriteLine($"Connected replicas: {stats?.ConnectedReplicas}");

    // List replicas
    var replicas = await httpClient.GetFromJsonAsync<ReplicasResponse>(
        $"{masterUrl}/replication/replicas"
    );
    if (replicas?.Replicas != null)
    {
        foreach (var replica in replicas.Replicas)
        {
            Console.WriteLine($"  - {replica.Id}: lag={replica.Lag}, healthy={replica.Healthy}");
        }
    }
}

Configuration

public class NexusClientConfig
{
    /// <summary>
    /// Base URL of the Nexus server (required).
    /// </summary>
    public string BaseUrl { get; set; } = "http://localhost:15474";

    /// <summary>
    /// API key for authentication (optional).
    /// </summary>
    public string? ApiKey { get; set; }

    /// <summary>
    /// Username for authentication (optional).
    /// </summary>
    public string? Username { get; set; }

    /// <summary>
    /// Password for authentication (optional).
    /// </summary>
    public string? Password { get; set; }

    /// <summary>
    /// HTTP request timeout (default: 30 seconds).
    /// </summary>
    public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
}

Models

Node

public class Node
{
    public string Id { get; set; }                          // Unique identifier
    public List<string> Labels { get; set; }                // Node labels
    public Dictionary<string, object?> Properties { get; set; } // Properties
}

Relationship

public class Relationship
{
    public string Id { get; set; }                          // Unique identifier
    public string Type { get; set; }                        // Relationship type
    public string StartNode { get; set; }                   // Start node ID
    public string EndNode { get; set; }                     // End node ID
    public Dictionary<string, object?> Properties { get; set; } // Properties
}

QueryResult

public class QueryResult
{
    public List<string> Columns { get; set; }               // Column names
    public List<Dictionary<string, object?>> Rows { get; set; } // Result rows
    public QueryStats? Stats { get; set; }                  // Execution stats
}

public class QueryStats
{
    public int NodesCreated { get; set; }
    public int NodesDeleted { get; set; }
    public int RelationshipsCreated { get; set; }
    public int RelationshipsDeleted { get; set; }
    public int PropertiesSet { get; set; }
    public double ExecutionTimeMs { get; set; }
}

Testing

Run tests with:

dotnet test

Run with coverage:

dotnet test --collect:"XPlat Code Coverage"

Examples

See the Examples directory for complete working examples:

  • BasicUsage.cs - Basic CRUD operations
  • Transactions.cs - Transaction management
  • BatchOperations.cs - Batch node/relationship creation
  • SchemaManagement.cs - Working with indexes and schema

Performance Tips

  1. Use Batch Operations - For creating multiple nodes/relationships
  2. Dispose Clients - Use using statements to properly dispose HttpClient
  3. Connection Pooling - HttpClient reuses connections automatically
  4. CancellationTokens - Pass tokens to prevent hanging operations
  5. Parameterized Queries - Always use parameters instead of string concatenation
  6. Transactions - Group related operations for consistency and performance

Requirements

  • .NET 8.0 or higher
  • Nexus server 1.0.0 or higher

License

Apache License 2.0 - see LICENSE file for details

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Support

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.5.0 93 7/14/2026
2.1.0 349 5/2/2026
2.0.0 334 4/30/2026
1.15.0 345 4/26/2026
1.14.0 342 4/22/2026