Your first bucket
Time to make the INVENTORY bucket real. The inventory service keeps a
stock count for each SKU, and a bucket is where those counts live. This
page creates the bucket with one command, puts a count, gets it back, and
reads the bucket's status, and does nothing more than that.
The previous chapter gave you JetStream. A bucket rides on top of it: a
bucket is a JetStream stream the key-value API creates and configures
for you, so you never write the stream by hand. You ask for a bucket, and the
server stores one stream named KV_INVENTORY behind the friendly name
INVENTORY. The stream is the topic of the under the
hood page; here you only need to know it
exists.
Create the bucket
A running nats-server with JetStream enabled is the one prerequisite.
The key-value API is part of JetStream, so without -js the next command
has nothing to talk to:
nats-server -js
In another terminal, create the bucket:
- CLI
#!/bin/bash
# Create the INVENTORY bucket. A bucket is created as a JetStream stream,
# so this one command sets up the backing stream KV_INVENTORY on the
# subjects $KV.INVENTORY.>.
#
# --history 1 keeps only the current value of each key. We raise this
# later on the history-and-revisions page; 1 is the default and is all
# the inventory service needs to start.
nats kv add INVENTORY --history 1
# Expected output ends with a line confirming the bucket was created:
#
# INVENTORY Key-Value Store
#
# Configuration:
# Bucket Name: INVENTORY
# History: 1
# ...
Two parts of this command matter. The first is the bucket name: INVENTORY. Bucket names are
case-sensitive identifiers, and they show up in every command and every
error message in this chapter. The name maps straight onto the backing
stream: INVENTORY becomes KV_INVENTORY.
The second is --history 1. History is how many prior values the
bucket keeps for each key. One means the bucket holds only the current
value of a key and forgets the rest. That's the default and all the
inventory service needs to start. The depth can go as high as 64, but no
higher; History and revisions raises it so a key
remembers where it's been, and for now, one is enough.
You didn't set any other configuration. A bucket has the same long list
of stream knobs underneath, all filled with sensible defaults. The full
set of bucket configuration options lives in Reference → Create
Stream, since a bucket is created
as a stream. We use only History here.
Put a value, get an entry
The bucket is empty. Put the first stock count into it. The key is the SKU, and the value is the count stored as bytes:
- CLI
#!/bin/bash
# Put the stock count for widget-blue into the bucket. The key is the SKU,
# the value is the count as bytes. A put is unconditional: it writes the
# value whether or not the key already exists.
#
# This is the inventory service recording that there are 42 widget-blue
# units in stock.
nats kv put INVENTORY widget-blue 42
# The put returns the new revision of the key. The first write to a fresh
# key lands at revision 1:
#
# INVENTORY > widget-blue revision: 1 created @ ...
That's a put: an unconditional write. It stores the value whether or not the key already exists, and it hands back the key's new revision, a number the bucket bumps on every write. The first write to a fresh key lands at revision 1. Revisions are how the bucket tracks change over time; History and revisions builds on them, and for now the number only confirms the write happened.
Now read it back:
- CLI
#!/bin/bash
# Get widget-blue back. By default a get returns the whole entry: the
# value plus its revision and timestamp. --raw asks for only the value
# bytes, which is what a program usually wants.
nats kv get INVENTORY widget-blue --raw
# Prints just the value:
#
# 42
# Without --raw you see the full entry the server returns:
nats kv get INVENTORY widget-blue
# INVENTORY > widget-blue created @ ...
#
# 42
#
# A get for a key that was never put fails with a key-not-found error,
# which is distinct from a key whose value is empty. Check for that error
# before using the value:
nats kv get INVENTORY widget-green --raw || echo "widget-green not in stock yet"
A get returns an entry rather than a bare
value: the value together with its revision and
the time it was written. The CLI's --raw flag strips the entry down to
just the value bytes (42), which is usually what a program wants, but the
full object is what the server actually sends.
That shape is intentional, because the inventory service rarely wants only the count; it wants the count and the revision, because history and revisions uses that revision to decrement the value safely. The entry carries both in one read, so you never have to make a second call to learn which revision you just saw.
The timestamp on the entry matters too. It records when the value was written, not when you read it, so the inventory service can tell a count taken seconds ago from one that has sat untouched for a week. You get all three facts — value, revision, and write time — from the single get you already made.
Read the bucket's status
One command summarizes the bucket as a whole:
- CLI
#!/bin/bash
# Ask the server about the bucket as a whole. Status reports the bucket's
# configuration and how many values it currently holds.
nats kv status INVENTORY
# Expected output reports the bucket name, history depth, value count, and
# the backing store:
#
# INVENTORY Key-Value Store Status
#
# Bucket Name: INVENTORY
# History: 1
# TTL: 0s
# Backing Store: JetStream
# Backing Store Name: KV_INVENTORY
# Values: 1
#
# Backing Store Name names the stream under the bucket: KV_INVENTORY.
# That is the proof the bucket is a stream; the under-the-hood page opens
# it up.
The status reports the bucket name, the history depth you set, and how many
values it holds. It also reports the backing stream: the stream the
bucket is built on, named KV_INVENTORY. That line is your first concrete
proof that a bucket is a stream with a friendlier name. The under the
hood page opens that stream and reads
it directly.
Pitfalls
Two mistakes are common on your very first bucket, and each is easy to avoid once you've seen it.
A get returns an entry rather than a value, and a missing key is an error rather than an empty value. Reaching straight for the value bytes works only when the key exists. A key that was never put doesn't return an empty entry; it returns a key-not-found error. Those are two different situations: an empty value is a value, and a missing key is the absence of one. Don't treat a failed get as "the count is zero." The get example above ends with exactly this case: its last line gets a SKU that was never stocked, the get fails, and the program decides what a missing SKU means instead of reading a stale or zero count by accident. Check the error first, then read the value.
Bucket and key names are validated. A bucket name may contain only
letters, digits, dash, and underscore, and it can't be empty. A key is
more permissive (letters, digits, and the characters -, /, _, =,
and .), but nothing beyond that set, no leading or trailing dot, and no
two dots in a row (a..b is rejected even though a.b is fine). An order
id like ord:8w2k has a colon, so it can't be a key; the server rejects
the write rather than storing a broken key. Pick names from the allowed
set, and reach for an underscore or dash where you'd have used a colon:
- CLI
#!/bin/bash
# Key and bucket names are validated. A bucket name may use only letters,
# digits, dash, and underscore. A key may use letters, digits, and the
# characters - / _ = . — and nothing else.
#
# An order id like "ord:8w2k" has a colon, which is not allowed as a key.
# The server rejects the put rather than storing a broken key:
nats kv put INVENTORY "ord:8w2k" 42 || echo "rejected: ':' is not a legal key character"
# A legal key with the same intent uses an allowed separator:
nats kv put INVENTORY ord_8w2k 42
Where you are
You now have:
- An
INVENTORYbucket, backed by the streamKV_INVENTORY. - The key
widget-blueholding the value42at revision 1. - The ability to put a value, get back its entry, and read the bucket's status.
What's next
The next page puts the warehouse dashboard on the bucket: a watch that streams every stock change live, starting with a snapshot of what's already there.
Continue to Watching.
See also
- Reference → Create Stream — every configuration option a bucket inherits from its backing stream.
- Core Concepts → JetStream — the storage layer a bucket is built on.
- Watching — stream live changes off the bucket.