-   [](https://platform.exaloop.io/)
-   Databases
-   [Redis](https://platform.exaloop.io/db/redis/)
-   Server commands

# Redis Server Commands

Extend Redis with commands written in Codon. A handler receives an argument view and a Redis context, then returns a native value or protocol response. The SDK compiles handlers into a shared library that Redis loads as a module.

Server commands suit short operations close to stored data, such as transforming a value or combining several Redis calls. The typed API manages the native boundary while command declarations specify arguments, keys, and access flags.

**Before you begin:** install the [all-SDK distribution](https://platform.exaloop.io/start/distribution/) and Redis 7.2+ tools. Use a disposable local server; the example changes a key named `greeting`. You will build and load `codon_demo.so`.

## Write and Load Commands

Save the following as `commands.codon`. It defines three commands: add two numbers, uppercase a stored string, and echo a binary value.

```python title="commands.codon"
from exaloop.databases.redis import Module

module = Module("codon_demo")

@module.command("codon.add", arity=2, flags="readonly fast")
def add(context, args):
    return args.int(0) + args.int(1)

@module.command("codon.upper", arity=1, flags="write deny-oom", keys=(1, 1, 1))
def upper(context, args):
    key = args[0]
    value = context.get(key)
    if value is None:
        return False
    context[key] = value.decode("utf-8").upper()
    return True

@module.command("codon.echo", arity=1, flags="readonly fast")
def echo(context, args):
    return args[0]
```

The decorator declares the command name, argument count, and access behavior. Inside the handler, `args.int(0)` parses an integer while `args[0]` returns raw bytes. The `upper` handler additionally identifies its first argument as a key that it writes; [key declarations](https://platform.exaloop.io/db/redis/commands/#declare-the-keys-a-command-uses) are required for permission checks and introspection.

Compile the handlers, then start a development server on an unused local port (6379 in this example):

```sh title="Terminal"
exaloop --kind redis-module build commands.codon -o "$PWD/build/codon_demo.so"
redis-server --bind 127.0.0.1 --loadmodule "$PWD/build/codon_demo.so"
```

Use the SDK builder directly

With Codon 0.20.3 and Python 3.9+ available:

```sh title="Terminal"
python3 /path/to/sdk/redis/build.py commands.codon \
  --codon /absolute/path/to/codon -o "$PWD/build/codon_demo.so"
```

In another terminal, try the commands:

```sh title="Terminal"
redis-cli --raw codon.add 19 23
redis-cli --raw SET greeting hello
redis-cli --raw codon.upper greeting
redis-cli --raw GET greeting
```

```text title="Output"
42
OK
1
HELLO
```

You can also load a module into a running server with `MODULE LOAD`, where administrative module loading is enabled. That permission should be restricted to trusted administrators: loading a module executes native code in the server process.

## Registration

Create one `Module` per library and give it a name unique to the server. The `arity=2` declaration on `codon.add` asks Redis to reject a call without exactly two arguments, while `readonly fast` describes a short operation that does not change stored data.

Choose flags that describe what the command actually does. In particular, a command that writes data needs the `write` flag. The [registration reference](https://platform.exaloop.io/db/reference/#redis-module-commands) covers the complete decorator signature.

### Declare the Keys a Command Uses

Redis uses key declarations for permission checks and command introspection. For `codon.upper`, `keys=(1, 1, 1)` identifies the first argument as the only key. These positions count from one, even though `args[0]` reads that argument inside the handler.

Declare every key the command will use, including keys accessed through nested Redis calls. A read/modify/write operation needs read/write access, not just permission to overwrite a value.

## Arguments, Keys, and Calls

Arguments are binary by default, preserving Redis values exactly. Use `args.text(index)` for UTF-8 or a numeric accessor for numbers. The context's mapping syntax is convenient for string keys; `context.get(key)` lets you handle a missing key without catching `KeyError`.

| API | Description |
| --- | --- |
| `len(args)`, `args[index]`, `for value in args` | Argument count and owned binary values; negative indexes work |
| `args.text(index)` | Decode UTF-8; invalid encoding raises |
| `args.int(index)` | Native Redis signed 64-bit integer parser; invalid/overflowing input raises |
| `args.float(index)` | Native Redis double parser; invalid input raises |
| `context.get(key)` | Owned `bytes`, or `None` for a missing string key |
| `context[key]` | Owned `bytes`; missing keys raise `KeyError` |
| `context.set(key, value)`, `context[key] = value` | Set a string value and remove its TTL, like `SET` |
| `context.call(command, *args)` | Invoke a Redis command and return a scoped `Reply`; Redis errors raise |
| `context.callv(command, args)` | Invoke a Redis command with a runtime-sized iterable of arguments |
| `reply.is_null`, `reply.int()`, `reply.bytes()`, `reply.text()` | Explicit typed access to a command reply |
| `len(reply)`, `reply[index]`, iteration | Array reply access, including negative indexes |

Use direct key access for strings and `context.call()` for other Redis operations. For example, `context.call("LRANGE", key, 0, -1)` reads a list, while `context.call("INCR", key).int()` increments a counter and returns its new value. Nested calls still follow the client's ACL permissions and Redis's script-mode restrictions; they do not grant extra privileges.

## Replies and Errors

Return a number, string, bytes, or a list/tuple to send a response to the client. You can also return a command reply directly, or use `Status("OK")` and `Error("ERR message")` for explicit protocol responses.

An exception becomes a Redis error, but it does not undo earlier writes. Validate the inputs before changing data when a command performs several steps.

## Persistence and State

Writes to Redis keys participate in Redis replication and persistence. Codon module globals do not, so keep durable or shared state in keys. A handler's context and replies are only valid during that call; retain copied values rather than those handles.

## Next Steps

-   [Deploy the module](https://platform.exaloop.io/db/redis/deployment/) with its runtime libraries and a restart plan.
-   Use the [command reference](https://platform.exaloop.io/db/reference/#redis-module-commands) to add flags and key declarations.
-   [Call Redis from an application](https://platform.exaloop.io/db/redis/client/) for work that should not block the server's command thread.

Source: https://platform.exaloop.io/db/redis/commands/
