Installation

Add Concord to your mix.exs:

def deps do
  [
    {:concord, "~> 0.1.0"}
  ]
end

Quick Start — Embedded Database

Concord starts automatically with your application. No separate infrastructure needed.

# Store data
Concord.put("user:1001", %{name: "Alice", role: "admin"})
#=> :ok

# Retrieve data
Concord.get("user:1001")
#=> {:ok, %{name: "Alice", role: "admin"}}

# Store with TTL (auto-expires after 1 hour)
Concord.put("feature:dark_mode", "enabled", ttl: 3600)

# Get value with remaining TTL
Concord.get_with_ttl("feature:dark_mode")
#=> {:ok, {"enabled", 3595}}

# Delete
Concord.delete("user:1001")
#=> :ok

Quick Start — HTTP API

1. Start the HTTP API server:

# Development mode (auth disabled)
iex -S mix

# With HTTP API enabled (see config/dev.exs)
CONCORD_API_PORT=4000 iex -S mix

2. Use the REST API:

# Health check
curl http://localhost:4000/api/v1/health

# Store data
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"value": "Hello, World!"}' \
  http://localhost:4000/api/v1/kv/greeting

# Retrieve data
curl http://localhost:4000/api/v1/kv/greeting

# Interactive Swagger UI
open http://localhost:4000/api/docs

See HTTP API Guide for full examples including authentication.

Multi-Node Cluster

# Terminal 1
iex --name n1@127.0.0.1 --cookie concord -S mix

# Terminal 2
iex --name n2@127.0.0.1 --cookie concord -S mix

# Terminal 3
iex --name n3@127.0.0.1 --cookie concord -S mix

Nodes discover each other automatically via libcluster gossip.

Authentication

Authentication is disabled in dev and enabled in prod by default.

# config/prod.exs
config :concord,
  auth_enabled: true
# Create a token
mix concord.cluster token create
# => Created token: sk_concord_abc123def456...

# Revoke when needed
mix concord.cluster token revoke sk_concord_abc123def456...
# Use tokens in code
token = System.fetch_env!("CONCORD_TOKEN")
Concord.put("config:api_rate_limit", 1000, token: token)
Concord.get("config:api_rate_limit", token: token)

Common Use Cases

Feature Flags

Concord.put("flags:new_dashboard", "enabled")

if Concord.get("flags:new_dashboard") == {:ok, "enabled"} do
  render_new_dashboard()
end

Session Storage

# Store session with 30-minute TTL
Concord.put("session:#{session_id}", session_data, ttl: 1800)

# Retrieve session
Concord.get_with_ttl("session:#{session_id}")
#=> {:ok, {%{user_id: 123, ...}, 1755}}

# Extend session on activity
Concord.touch("session:#{session_id}", 1800)

Rate Limiting

user_key = "rate_limit:#{user_id}:#{Date.utc_today()}"
case Concord.get(user_key) do
  {:ok, count} when count < 1000 ->
    Concord.put(user_key, count + 1, ttl: 86400)
    :allow
  _ -> :deny
end

Service Discovery

Concord.put("services:web:1", %{
  host: "10.0.1.100",
  port: 8080,
  health: "healthy"
})

{:ok, all} = Concord.get_all()
healthy = all
  |> Enum.filter(fn {k, _} -> String.starts_with?(k, "services:web:") end)
  |> Enum.filter(fn {_, v} -> v.health == "healthy" end)

Distributed Locks

case Concord.put("locks:job:123", "node:worker1", timeout: 5000) do
  :ok ->
    # Do work
    Concord.delete("locks:job:123")
  {:error, :timeout} ->
    # Lock already held
end

Management Commands

# Check cluster health
mix concord.cluster status

# List cluster members
mix concord.cluster members

# Create authentication token
mix concord.cluster token create

# Revoke a token
mix concord.cluster token revoke <token>

HTTP API Endpoints

MethodPathDescription
PUT/api/v1/kv/:keyStore key-value pair
GET/api/v1/kv/:keyRetrieve value
DELETE/api/v1/kv/:keyDelete key
POST/api/v1/kv/:key/touchExtend TTL
GET/api/v1/kv/:key/ttlGet remaining TTL
POST/api/v1/kv/bulkBulk store (up to 500)
POST/api/v1/kv/bulk/getBulk retrieve
POST/api/v1/kv/bulk/deleteBulk delete
POST/api/v1/kv/bulk/touchBulk TTL extend
GET/api/v1/kvList all keys
GET/api/v1/statusCluster status
GET/api/v1/healthHealth check
GET/api/v1/openapi.jsonOpenAPI spec
GET/api/docsSwagger UI

Next Steps