> ## Documentation Index
> Fetch the complete documentation index at: https://suvera.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Redis Sample Application

> Build an end-to-end Redis app with Winter Boot and the Winter Redis module that stores strings, counters, hashes, lists, and sets.

Build a REST app that performs Redis operations through a `RedisDemoController`. You use Winter Boot for the application runtime and the Winter Redis module from Winter Modules for single-node access via `PhpRedisTemplate`.

## Prerequisites

You need PHP 8.5 or later with the `swoole`, `redis`, and `pcntl` extensions. You also need a Redis server. The sample config points at `localhost:30637` with password `redis123` — replace the host, port, and password with your own server.

Start Redis before you run the app:

```bash theme={null}
docker run -d -p 30637:6379 --name redis \
  redis:7 redis-server --requirepass redis123
```

## Project structure

The sample uses this layout:

```
redis/
├── bin/
│   └── application.php          # Application entry point
├── config/
│   ├── application.yml          # Winter Boot application config
│   └── redis-config.yml         # Redis single-node connections config
├── src/
│   ├── RedisSampleApplication.php  # Main application class
│   └── controller/
│       └── RedisDemoController.php # Redis operations controller
└── composer.json                # Dependencies
```

## Install dependencies

Require the framework and the modules package:

```bash theme={null}
composer require suvera/winter-boot suvera/winter-modules
```

The `redis` PHP extension must be installed separately:

```bash theme={null}
pecl install redis
```

## Source files

Switch between the source files. Each tab shows the exact file from the sample.

<Tabs>
  <Tab title="RedisSampleApplication.php">
    The single entry point. It points Winter Boot at the `config` directory and at the sample namespace.

    ```php theme={null}
    <?php

    namespace dev\example;

    use dev\winterframework\stereotype\WinterBootApplication;

    #[WinterBootApplication(
        configDirectory: [__DIR__ . "/../config"],
        scanNamespaces: [
            ['dev\\example', __DIR__ . '']
        ]
    )]
    class RedisSampleApplication {

        public static function main(): void {
            $winterApp = new \dev\winterframework\core\app\WinterWebSwooleApplication();
            $winterApp->run(self::class);
        }
    }
    ```
  </Tab>

  <Tab title="RedisDemoController.php">
    The controller. It autowires `PhpRedisTemplate` and exposes one endpoint per Redis data type — strings, counters, hashes, lists, sets, expiry — plus a `demo` endpoint that runs them all in sequence.

    ```php theme={null}
    <?php

    namespace dev\example\controller;

    use dev\winterframework\data\redis\phpredis\PhpRedisTemplate;
    use dev\winterframework\stereotype\Autowired;
    use dev\winterframework\stereotype\RestController;
    use dev\winterframework\stereotype\web\GetMapping;
    use dev\winterframework\stereotype\web\PostMapping;
    use dev\winterframework\stereotype\web\PathVariable;
    use dev\winterframework\stereotype\web\RequestParam;
    use Exception;

    #[RestController]
    class RedisDemoController {

        #[Autowired]
        private PhpRedisTemplate $redis;

        /**
         * Check Redis connectivity and server info
         */
        #[GetMapping(path: "/redisdemo/ping")]
        public function ping(): array {
            try {
                $pong = $this->redis->ping();
                $info = $this->redis->info('server');

                return [
                    'success' => true,
                    'ping' => $pong,
                    'redisVersion' => $info['redis_version'] ?? 'unknown',
                    'mode' => $info['redis_mode'] ?? 'unknown'
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Set a string value, with optional expiry in seconds
         */
        #[PostMapping(path: "/redisdemo/string")]
        public function setString(
            #[RequestParam] string $key,
            #[RequestParam] string $value,
            #[RequestParam(required: false)] ?int $ttl = null
        ): array {
            try {
                if ($ttl !== null && $ttl > 0) {
                    $this->redis->setex($key, $ttl, $value);
                } else {
                    $this->redis->set($key, $value);
                }

                return [
                    'success' => true,
                    'message' => "Key {$key} set successfully",
                    'ttl' => $ttl
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Get a string value along with its TTL
         */
        #[GetMapping(path: "/redisdemo/string")]
        public function getString(#[RequestParam] string $key): array {
            try {
                $value = $this->redis->get($key);

                return [
                    'success' => true,
                    'key' => $key,
                    'value' => $value === false ? null : $value,
                    'exists' => $value !== false,
                    'ttl' => $this->redis->ttl($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Atomically increment a counter
         */
        #[PostMapping(path: "/redisdemo/incr")]
        public function incr(
            #[RequestParam] string $key,
            #[RequestParam(required: false, defaultValue: '1')] ?int $by = 1
        ): array {
            try {
                $value = $this->redis->incrBy($key, $by ?? 1);

                return [
                    'success' => true,
                    'key' => $key,
                    'value' => $value
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Set a hash field
         */
        #[PostMapping(path: "/redisdemo/hash")]
        public function setHashField(
            #[RequestParam] string $key,
            #[RequestParam] string $field,
            #[RequestParam] string $value
        ): array {
            try {
                $this->redis->hSet($key, $field, $value);

                return [
                    'success' => true,
                    'message' => "Field {$field} set on hash {$key}"
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Get all fields of a hash
         */
        #[GetMapping(path: "/redisdemo/hash")]
        public function getHash(#[RequestParam] string $key): array {
            try {
                $fields = $this->redis->hGetAll($key);

                return [
                    'success' => true,
                    'key' => $key,
                    'fields' => $fields === false ? [] : $fields,
                    'length' => $this->redis->hLen($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Push a value to the tail of a list
         */
        #[PostMapping(path: "/redisdemo/list")]
        public function pushList(
            #[RequestParam] string $key,
            #[RequestParam] string $value
        ): array {
            try {
                $length = $this->redis->rPush($key, $value);

                return [
                    'success' => true,
                    'key' => $key,
                    'length' => $length
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Read all elements of a list
         */
        #[GetMapping(path: "/redisdemo/list")]
        public function getList(#[RequestParam] string $key): array {
            try {
                return [
                    'success' => true,
                    'key' => $key,
                    'values' => $this->redis->lRange($key, 0, -1),
                    'length' => $this->redis->lLen($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Add a member to a set
         */
        #[PostMapping(path: "/redisdemo/set")]
        public function addSetMember(
            #[RequestParam] string $key,
            #[RequestParam] string $value
        ): array {
            try {
                $added = $this->redis->sAdd($key, $value);

                return [
                    'success' => true,
                    'key' => $key,
                    'added' => $added,
                    'size' => $this->redis->sCard($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Get all members of a set
         */
        #[GetMapping(path: "/redisdemo/set")]
        public function getSet(#[RequestParam] string $key): array {
            try {
                return [
                    'success' => true,
                    'key' => $key,
                    'members' => $this->redis->sMembers($key),
                    'size' => $this->redis->sCard($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Set expiry on a key in seconds
         */
        #[PostMapping(path: "/redisdemo/expire")]
        public function expire(
            #[RequestParam] string $key,
            #[RequestParam] int $seconds
        ): array {
            try {
                return [
                    'success' => true,
                    'key' => $key,
                    'expireSet' => $this->redis->expire($key, $seconds),
                    'ttl' => $this->redis->ttl($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Delete one or more keys
         */
        #[PostMapping(path: "/redisdemo/delete")]
        public function delete(#[RequestParam] string $key): array {
            try {
                return [
                    'success' => true,
                    'key' => $key,
                    'deleted' => $this->redis->del($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Get a key by path variable (route-style access)
         */
        #[GetMapping(path: "/redisdemo/keys/{key}")]
        public function getByPath(#[PathVariable] string $key): array {
            try {
                $type = $this->redis->type($key);

                return [
                    'success' => true,
                    'key' => $key,
                    'type' => $type,
                    'ttl' => $this->redis->ttl($key)
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }

        /**
         * Demo all operations together
         */
        #[GetMapping(path: "/redisdemo/demo")]
        public function demoAllOperations(): array {
            try {
                $prefix = 'demo:' . time() . ':';
                $strKey = $prefix . 'greeting';
                $counterKey = $prefix . 'visits';
                $hashKey = $prefix . 'user';
                $listKey = $prefix . 'events';
                $setKey = $prefix . 'tags';

                // 1. String set/get
                $this->redis->set($strKey, 'Hello Redis');
                $greeting = $this->redis->get($strKey);

                // 2. Counter
                $this->redis->incrBy($counterKey, 5);

                // 3. Hash
                $this->redis->hSet($hashKey, 'name', 'Winter Boot');
                $this->redis->hSet($hashKey, 'lang', 'PHP');
                $user = $this->redis->hGetAll($hashKey);

                // 4. List
                $this->redis->rPush($listKey, 'login');
                $this->redis->rPush($listKey, 'purchase');
                $events = $this->redis->lRange($listKey, 0, -1);

                // 5. Set
                $this->redis->sAdd($setKey, 'php');
                $this->redis->sAdd($setKey, 'redis');
                $tags = $this->redis->sMembers($setKey);

                // 6. Expiry
                $this->redis->expire($strKey, 60);
                $ttl = $this->redis->ttl($strKey);

                // 7. Cleanup
                $deleted = $this->redis->del($strKey, $counterKey, $hashKey, $listKey, $setKey);

                return [
                    'success' => true,
                    'operations' => [
                        'string' => "Set and read back: {$greeting}",
                        'counter' => "Incremented {$counterKey} by 5",
                        'hash' => "Stored and read " . count($user) . " user fields",
                        'list' => "Pushed and read " . count($events) . " events",
                        'set' => "Added and read " . count($tags) . " tags",
                        'expire' => "TTL on {$strKey}: {$ttl}s",
                        'cleanup' => "Deleted {$deleted} demo keys"
                    ],
                    'prefix' => $prefix,
                    'contentMatch' => $greeting === 'Hello Redis'
                ];
            } catch (Exception $e) {
                return [
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }
    }
    ```
  </Tab>

  <Tab title="bin/application.php">
    The launch script. It loads the Composer autoloader and starts the application.

    ```php theme={null}
    #!/usr/bin/env php
    <?php

    use dev\example\RedisSampleApplication;

    require_once(dirname(__DIR__) . '/vendor/autoload.php');

    RedisSampleApplication::main();
    ```
  </Tab>

  <Tab title="composer.json">
    The sample declares framework dependencies with PSR-4 autoloading for its own namespace. The `redis` extension comes from PECL, not Composer.

    ```json theme={null}
    {
        "name": "suvera/winter-boot-redis-sample",
        "require": {
            "ext-pcntl": "*",
            "ext-swoole": "*",
            "ext-redis": "*",
            "suvera/winter-boot": "@dev",
            "suvera/winter-modules": "@dev"
        },
        "autoload": {
            "psr-4": {
                "dev\\example\\": "src/"
            }
        }
    }
    ```

    The runnable sample in `winter-boot-samples` adds local `path` repositories for `winter-boot` and `winter-modules` so it resolves them from a sibling checkout. You do not need those entries when you install released packages from Packagist.
  </Tab>
</Tabs>

## Configuration

Switch between the two config files. `application.yml` enables the module, and `redis-config.yml` defines the `primary` single-node connection.

<Tabs>
  <Tab title="application.yml">
    The full sample file registers `RedisModule` and sets the server and app identity:

    ```yaml theme={null}
    server:
        port: 8080
        address: 0.0.0.0
        context-path: /
    winter:
        application:
            name: Redis Sample Application
            id: redis-sample-app
            version: 1.0.0
    modules:
        -   module: dev\winterframework\data\redis\RedisModule
            enabled: true
            configFile: redis-config.yml
    ```

    See [Configuration](/configuration) for every `application.yml` key.
  </Tab>

  <Tab title="redis-config.yml">
    Defines a `primary` single-node connection. Replace the host, port, and password with your own server.

    ```yaml theme={null}
    phpredis:
        singles:
            -   name: __default__
                timeout: 2.5
                readTimeout: 2.5

            -   name: primary
                host: localhost
                port: 30637
                auth: redis123
                timeout: 2.5
                readTimeout: 2.5
    ```

    The `__default__` entry supplies shared timeouts. The `primary` connection is also injectable by name with `#[Autowired("primary")]`. See the [Redis module](/modules/data-redis) for clusters, arrays, sentinels, and Dynomite rings.
  </Tab>
</Tabs>

## Run the app

Start the app, then call the endpoints to store a string, work a hash, and run the full demo.

**1. Start the application:**

```bash theme={null}
composer install
php bin/application.php
```

**2. Check connectivity:**

```bash theme={null}
curl "http://localhost:8080/redisdemo/ping"
```

**3. Set a string with a 60s TTL:**

```bash theme={null}
curl -X POST "http://localhost:8080/redisdemo/string?key=my-key&value=Hello%20Redis&ttl=60"
```

**4. Get the string:**

```bash theme={null}
curl "http://localhost:8080/redisdemo/string?key=my-key"
```

**5. Increment a counter:**

```bash theme={null}
curl -X POST "http://localhost:8080/redisdemo/incr?key=visits&by=5"
```

**6. Work with a hash:**

```bash theme={null}
curl -X POST "http://localhost:8080/redisdemo/hash?key=user:1&field=name&value=Winter%20Boot"
curl "http://localhost:8080/redisdemo/hash?key=user:1"
```

**7. Run the complete demo:**

```bash theme={null}
curl "http://localhost:8080/redisdemo/demo"
```

The demo writes string, counter, hash, list, and set keys under a time-based prefix, verifies each read, sets a TTL, cleans up, and returns one status entry per operation.

## Next steps

* Read the [Redis module](/modules/data-redis) for cluster, array, sentinel, and Dynomite templates.
* Browse all [Libraries](/modules/overview) when you need Kafka, SQS, S3, or Doctrine in the same app.
