A Swift client library for Valkey.
valkey-swift provides two clients depending on your deployment:
ValkeyClient: For single-node or primary-replica deploymentsValkeyClusterClient: For Cluster Mode deployments with automatic sharding
For single-node Valkey instances or primary-replica setups.
Create a client with server connection details:
import Valkey
let valkeyClient = ValkeyClient(
.hostname("127.0.0.1", port: 6379),
logger: logger
)The client uses a connection pool that requires a background process. There are two approaches:
You can run the background process using async let. When you leave the scope of the function your async let variable is declared the client will be shutdown.
async let _ = valkeyClient.run()
// Use client
try await valkeyClient.set(key: "foo", value: "bar")
let value = try await valkeyClient.get(key: "foo")
// Client continues running in backgroundAlternatively you could also use a TaskGroup
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
await valkeyClient.run()
}
// All operations happen in the closure body
try await valkeyClient.set(key: "foo", value: "bar")
let value = try await valkeyClient.get(key: "foo")
// When done, cancel the run() task
group.cancelAll()
}
// Client is shut down when task group exitsOr use with swift-service-lifecycle for long-running services.
let services: [Service] = [myApp, valkeyClient]
let serviceGroup = ServiceGroup(
services: services,
gracefulShutdownSignals: [.sigint, .sigterm],
logger: logger
)
try await serviceGroup.run()Execute commands directly from the client (connection is managed automatically):
try await valkeyClient.set(key: "user:123", value: "alice")
let value = try await valkeyClient.get(key: "user:123")Send multiple commands at once without waiting for each response:
let (setResponse, getResponse) = await valkeyClient.execute(
SET(key: "MyKey", value: "TestString"),
GET(key: "MyKey")
)
let value = try getResponse.get()Execute commands atomically using MULTI/EXEC:
let results = try await valkeyClient.transaction(
SET(key: "order:123:status", value: "pending"),
SET(key: "order:123:timestamp", value: "2025-01-15"),
INCR(key: "order:123:attempts")
)Get a dedicated connection when multiple commands need to run on the same connection:
try await valkeyClient.withConnection { connection in
try await connection.set(key: "foo1", value: "bar")
try await connection.set(key: "foo2", value: "baz")
let value = try await connection.get(key: "foo1")
}For Cluster Mode Valkey deployments with automatic command routing, topology discovery, and live cluster topology changes.
Configure node discovery to provide initial cluster endpoints. The client will automatically discover the full cluster topology:
import Valkey
let discovery = ValkeyStaticNodeDiscovery([
.init(endpoint: "node1.example.com", port: 6379),
.init(endpoint: "node2.example.com", port: 6379),
.init(endpoint: "node3.example.com", port: 6379)
])
let configuration = ValkeyClusterClientConfiguration(
client: ValkeyClientConfiguration(
commandTimeout: .seconds(5)
),
maximumRedirects: 3,
clusterRefreshInterval: .seconds(300)
)
let clusterClient = ValkeyClusterClient(
nodeDiscovery: discovery,
configuration: configuration,
logger: logger
)In a similar way that ValkeyClient requires a background process the cluster client also requires a background process to manage all its connection pools and perform regular cluster topology updates. You can use all the same methods to run the ValkeyClusterClient background process: (Swift Concurrency, ServiceLifecycle).
The cluster client automatically routes commands to the correct node based on key hash slots. Note a command cannot reference two keys in different hash slots.
// Automatically routed to the node owning the "user:123" hash slot
try await clusterClient.set(key: "user:123", value: "alice")
let value = try await clusterClient.get(key: "user:123")Commands affecting different hash slots are automatically split and executed concurrently across nodes:
let results = await clusterClient.execute(
SET(key: "user:1", value: "alice"),
SET(key: "user:2", value: "bob"),
GET(key: "user:1"),
GET(key: "user:2")
)To ensure commands target the same hash slot, use hash tags (substring between {}):
// Both keys guaranteed to be on the same node
try await clusterClient.set(key: "user:{123}:profile", value: "data")
try await clusterClient.set(key: "user:{123}:settings", value: "prefs")Transactions require all keys to be in the same hash slot:
let results = try await clusterClient.transaction(
SET(key: "order:{456}:status", value: "pending"),
SET(key: "order:{456}:timestamp", value: "2025-01-15"),
INCR(key: "order:{456}:attempts")
)Get a connection for specific keys when multiple commands need to run on the same node:
try await clusterClient.withConnection(forKeys: ["user:{123}"], readOnly: false) { connection in
try await connection.set(key: "user:{123}:profile", value: "data")
try await connection.set(key: "user:{123}:email", value: "alice@example.com")
}All Valkey commands are available in the Commands folder, generated from Valkey's command specifications. Commands return typed responses where possible, or RESPToken for manual conversion when needed.
As Valkey is a fork of Redis v7.2.4, valkey-swift is compatible with Redis databases up to v7.2.4. There is a chance that v7.2.4 features will still be compatible in later versions of Redis, but these are now considered two different projects and they will diverge. valkey-swift uses the RESP3 protocol.
User guides and reference documentation for valkey-swift can be found on the Swift Package Index.