# Strings

> Store text, numbers, and binary values under a key, with counters, conditional writes, and expiry.

URL: https://docs.hotkv.com/data-types/strings/


A string is the simplest HotKV value: one key, one value. Values are
binary safe, so a string can hold text, a serialized JSON document, a
protobuf message, an image thumbnail, or a number that HotKV can increment
for you. Strings are the right choice for cached objects, session tokens,
feature flags, counters, and simple locks.

Strings also back two other types: [bitmaps](https://docs.hotkv.com/data-types/bitmaps/index.md) and
[HyperLogLogs](https://docs.hotkv.com/data-types/hyperloglog/index.md) are strings that dedicated
commands read and write in a special way.

## Create, read, update, delete

[SET](https://docs.hotkv.com/commands/set/index.md) writes a value and replaces whatever the key held
before, of any type. [GET](https://docs.hotkv.com/commands/get/index.md) reads it back, and returns nil
when the key does not exist.

```
127.0.0.1:6379> SET user:42:name "Ada"
OK
127.0.0.1:6379> GET user:42:name
"Ada"
127.0.0.1:6379> GET user:43:name
(nil)
127.0.0.1:6379> APPEND user:42:name " Lovelace"
(integer) 12
127.0.0.1:6379> STRLEN user:42:name
(integer) 12
127.0.0.1:6379> GETRANGE user:42:name 0 2
"Ada"
127.0.0.1:6379> DEL user:42:name
(integer) 1
```

Read or write several keys in one round trip with [MSET](https://docs.hotkv.com/commands/mset/index.md)
and [MGET](https://docs.hotkv.com/commands/mget/index.md). `MSET` is atomic: other clients see all of the
new values or none of them.

```
127.0.0.1:6379> MSET config:theme "dark" config:lang "en"
OK
127.0.0.1:6379> MGET config:theme config:lang config:missing
1) "dark"
2) "en"
3) (nil)
```

### Conditional writes

`SET` takes `NX` (only if the key is missing) and `XX` (only if it
exists). With `GET` it returns the value it replaced.

```
127.0.0.1:6379> SET flag:beta "on" NX
OK
127.0.0.1:6379> SET flag:beta "off" NX
(nil)
127.0.0.1:6379> SET flag:beta "off" XX GET
"on"
```

[MSETNX](https://docs.hotkv.com/commands/msetnx/index.md) sets several keys only if none of them exist.
[GETDEL](https://docs.hotkv.com/commands/getdel/index.md) reads a value and deletes the key in one step,
and [GETSET](https://docs.hotkv.com/commands/getset/index.md) is the older form of `SET ... GET`.

## Counters

A string that holds a base-10 integer can be changed in place with
[INCR](https://docs.hotkv.com/commands/incr/index.md), [INCRBY](https://docs.hotkv.com/commands/incrby/index.md),
[DECR](https://docs.hotkv.com/commands/decr/index.md) and [DECRBY](https://docs.hotkv.com/commands/decrby/index.md). A missing key
counts as 0. Every call is atomic, so many clients can bump the same
counter without losing updates.

```
127.0.0.1:6379> INCR page:home:views
(integer) 1
127.0.0.1:6379> INCRBY page:home:views 10
(integer) 11
127.0.0.1:6379> DECR page:home:views
(integer) 10
127.0.0.1:6379> SET price "10.5"
OK
127.0.0.1:6379> INCRBYFLOAT price 0.1
"10.6"
127.0.0.1:6379> SET name "Ada"
OK
127.0.0.1:6379> INCR name
(error) ERR value is not an integer or out of range
```

Integer counters are signed 64-bit. A change that would leave that range is
refused with `ERR increment or decrement would overflow` and the value is
left as it was. [INCRBYFLOAT](https://docs.hotkv.com/commands/incrbyfloat/index.md) works on any number
and refuses a result that would be NaN or infinite.

## Expiry

Any key can be given a time to live. For strings, the most convenient way
is to set the value and the expiry together:

```
127.0.0.1:6379> SET session:9f2c "user:42" EX 1800
OK
127.0.0.1:6379> TTL session:9f2c
(integer) 1800
127.0.0.1:6379> GETEX session:9f2c EX 3600
"user:42"
127.0.0.1:6379> TTL session:9f2c
(integer) 3600
127.0.0.1:6379> SET session:9f2c "user:42"
OK
127.0.0.1:6379> TTL session:9f2c
(integer) -1
```

Things to know:

- A plain `SET` on an existing key clears its time to live. Pass `KEEPTTL`
  to keep it.
- `SET` accepts `EX` (seconds), `PX` (milliseconds), `EXAT` and `PXAT`
  (Unix time). [SETEX](https://docs.hotkv.com/commands/setex/index.md) and [PSETEX](https://docs.hotkv.com/commands/psetex/index.md)
  are the older forms.
- [GETEX](https://docs.hotkv.com/commands/getex/index.md) reads a value and sets, changes, or removes
  (`PERSIST`) its expiry in one step: a sliding session window in one
  command.
- [MSETEX](https://docs.hotkv.com/commands/msetex/index.md) sets several keys with one shared expiry, and
  replies `1`, or `0` when an `NX` or `XX` condition stops it.
- `APPEND`, `INCR` and the other in-place commands keep the key's time to
  live.
- HotKV can also expire keys with nanosecond precision:
  [NEXPIRE](https://docs.hotkv.com/commands/nexpire/index.md), [NEXPIREAT](https://docs.hotkv.com/commands/nexpireat/index.md) and
  [NTTL](https://docs.hotkv.com/commands/nttl/index.md).

The general expiry commands ([EXPIRE](https://docs.hotkv.com/commands/expire/index.md),
[TTL](https://docs.hotkv.com/commands/ttl/index.md), [PERSIST](https://docs.hotkv.com/commands/persist/index.md) and the rest) are
covered in [Keys and expiration](https://docs.hotkv.com/develop/keys-and-expiration/index.md).

## Common patterns

**Cache entries.** Store the serialized object with `SET key value EX
seconds` so stale entries disappear on their own, and fall back to your
database on a nil `GET`.

**A simple lock.** `SET lock:report <random-token> NX PX 30000` takes the
lock only if nobody holds it, and the expiry releases it if the holder
crashes. Release it only if the stored token is still yours, for example
with a short [Lua script](https://docs.hotkv.com/develop/lua-scripting/index.md) that compares and
deletes in one step.

**Rate counters.** `INCR` a key named after the current window (for
example `hits:user:42:202610021405`) and give it an expiry on first use.
For a ready-made limiter, see [Rate limiting](https://docs.hotkv.com/develop/rate-limiting/index.md).

**Partial reads and writes.** [GETRANGE](https://docs.hotkv.com/commands/getrange/index.md) and
[SETRANGE](https://docs.hotkv.com/commands/setrange/index.md) read or overwrite a byte range without
moving the whole value. [LCS](https://docs.hotkv.com/commands/lcs/index.md) finds the longest common
subsequence of two string values.

## Limits

A single string can be as large as `proto-max-bulk-len`, 512 MB by
default. The setting accepts values from 1mb up to 4294967295 bytes. It is
also the longest value `APPEND` and `SETRANGE` may produce: past it they
reply `ERR string exceeds maximum allowed size (proto-max-bulk-len)`. See
[Configuration](https://docs.hotkv.com/reference/configuration/index.md).

Large values cost bandwidth and latency on every read. If you often need
only part of an object, a [hash](https://docs.hotkv.com/data-types/hashes/index.md) lets you read and
update single fields.

## Compatibility

String commands take the same arguments and give the same replies as in
Redis and Valkey. HotKV does not have the compare-and-set options of newer
Redis releases (`SET ... IFEQ`, `IFNE`, `IFDEQ`, `IFDNE`) or the `DELEX` and
`DIGEST` commands; use a [transaction](https://docs.hotkv.com/develop/transactions/index.md) with
`WATCH`, or a Lua script, to make a write depend on the current value.

## Commands

| Command | What it does |
| --- | --- |
| [SET](https://docs.hotkv.com/commands/set/index.md) | Store a value, with optional expiry and `NX`/`XX`/`GET` |
| [GET](https://docs.hotkv.com/commands/get/index.md) | Read a value |
| [MSET](https://docs.hotkv.com/commands/mset/index.md) / [MGET](https://docs.hotkv.com/commands/mget/index.md) | Write or read several keys at once |
| [MSETNX](https://docs.hotkv.com/commands/msetnx/index.md) | Set several keys only if none exist |
| [MSETEX](https://docs.hotkv.com/commands/msetex/index.md) | Set several keys with one shared expiry |
| [SETNX](https://docs.hotkv.com/commands/setnx/index.md) | Set only if the key is missing (older form of `SET NX`) |
| [SETEX](https://docs.hotkv.com/commands/setex/index.md) / [PSETEX](https://docs.hotkv.com/commands/psetex/index.md) | Set with an expiry in seconds or milliseconds |
| [GETEX](https://docs.hotkv.com/commands/getex/index.md) | Read and change the expiry |
| [GETDEL](https://docs.hotkv.com/commands/getdel/index.md) | Read and delete |
| [GETSET](https://docs.hotkv.com/commands/getset/index.md) | Set and return the old value |
| [APPEND](https://docs.hotkv.com/commands/append/index.md) | Append to the value |
| [STRLEN](https://docs.hotkv.com/commands/strlen/index.md) | Length of the value in bytes |
| [GETRANGE](https://docs.hotkv.com/commands/getrange/index.md) / [SUBSTR](https://docs.hotkv.com/commands/substr/index.md) | Read a byte range |
| [SETRANGE](https://docs.hotkv.com/commands/setrange/index.md) | Overwrite a byte range |
| [INCR](https://docs.hotkv.com/commands/incr/index.md) / [INCRBY](https://docs.hotkv.com/commands/incrby/index.md) | Increment an integer |
| [DECR](https://docs.hotkv.com/commands/decr/index.md) / [DECRBY](https://docs.hotkv.com/commands/decrby/index.md) | Decrement an integer |
| [INCRBYFLOAT](https://docs.hotkv.com/commands/incrbyfloat/index.md) | Add a floating-point amount |
| [LCS](https://docs.hotkv.com/commands/lcs/index.md) | Longest common subsequence of two values |

## From your application

**Python**

```python
import hotkv

client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)

client.set("session:9f2c", "user:42", ex=1800)
print(client.get("session:9f2c"))      # user:42

client.incr("page:home:views")
client.incrby("page:home:views", 10)
print(client.mget(["page:home:views", "missing"]))  # ['11', None]
client.close()
```

**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

await client.set("session:9f2c", "user:42", { ex: 1800 });
console.log(await client.get("session:9f2c"));

await client.incr("page:home:views");
await client.incrBy("page:home:views", 10);
console.log(await client.mGet("page:home:views", "missing"));
await client.close();
```

**Go**

```go
client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})
if err != nil {
    log.Fatal(err)
}
defer client.Close()
ctx := context.Background()

client.Set(ctx, "session:9f2c", "user:42", 30*time.Minute)
val, err := client.Get(ctx, "session:9f2c").Result()
fmt.Println(val, err)

client.Incr(ctx, "page:home:views")
views, _ := client.IncrBy(ctx, "page:home:views", 10).Result()
fmt.Println(views)
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.setEx("session:9f2c", 1800, "user:42");
    System.out.println(client.get("session:9f2c"));

    client.incr("page:home:views");
    long views = client.incrBy("page:home:views", 10);
    System.out.println(views);
}
```
