TinyDB is a TinyGo-compatible, minimal key–value store for string keys and values. It intentionally provides a very small API and a pluggable storage backend so it can be embedded into small or resource-constrained projects.
- Purpose: store and retrieve string key/value pairs persistently with a tiny, dependency-free runtime.
- Target: TinyGo and small Go programs where avoiding heavy runtime features (JSON, reflection, fmt) is desirable.
- Persistence: delegated to a user-provided
Storeimplementation.
- Minimal public API: only
GetandSetoperations. - Keys and values are plain
stringtypes. - Pluggable storage via the
Storeinterface (file, memory, remote object store, etc.). - Simple on-disk/text format: one
key=valueentry per line. - TinyGo friendly: no use of reflection or encoding/json in the core.
- Implement the
Storeinterface for your persistence layer (file, in-memory, S3, etc.). - Create a database instance with
tinydb.New(name, logger, store). - Use
Get(key)andSet(key, value)to read and write values.
Example minimal flow:
- Implement
Store - New DB:
db, err := tinydb.New("mydb.tdb", logger, store) - Set:
db.Set("foo", "bar") - Get:
val, err := db.Get("foo")
- Inputs:
keyandvaluearestring. - Outputs:
Getreturns(string, error).Setreturnserror. - Persistence: handled entirely by the
Storeimplementation. - Failure modes: I/O errors or backend errors are returned as
errorfrom the public methods.
Edge cases to consider:
- Missing key:
Getreturns""and a non-nilerror. - Backend I/O failure: propagated as an
error. - Values containing newlines or
=characters: the canonical on-disk format iskey=valueper line; avoid storing raw newlines in values.
The public interfaces look like this:
type KVStore interface {
Get(key string) (string, error)
Set(key, value string) error
}
// Store abstracts the persistence backend used by tinydb.
type Store interface {
// GetFile returns the full contents of a named file or an error.
GetFile(filePath string) ([]byte, error)
// SetFile replaces the file contents with the provided data.
SetFile(filePath string, data []byte) error
// AddToFile appends the provided data to the named file.
// Used by tinydb when adding a new key/value pair to avoid
// rewriting the entire backing store for each insert.
AddToFile(filePath string, data []byte) error
}Constructor example:
db, err := tinydb.New("mydb.tdb", logger, store)name(string): logical database name; commonly a file path used by theStoreimplementation.logger(io.Writer): optional logging target; may beos.Stdoutornil.store(Store): required backend implementation.name(string): logical database name; commonly a file path used by theStoreimplementation.logger(tinydb.LoggerFunc, signaturefunc(...any)): optional logger function; may be a wrapper that writes toos.Stdoutornil.store(Store): required backend implementation.
package main
import (
"os"
"github.com/tinywasm/kvdb"
)
type FileStore struct{}
func (fs FileStore) GetFile(path string) ([]byte, error) {
return os.ReadFile(path)
}
func (fs FileStore) SetFile(path string, data []byte) error {
return os.WriteFile(path, data, 0644)
}
// AddToFile appends bytes to the end of the named file. This is used by
// tinydb when inserting new key/value pairs to avoid rewriting the whole
// store on every insert.
func (fs FileStore) AddToFile(path string, data []byte) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(data)
return err
}
func main() {
// Simple LoggerFunc using only the built-in print/println helpers
logger := func(args ...any) {
for i, a := range args {
if i > 0 {
print(" ")
}
print(a)
}
println()
}
db, err := tinydb.New("settings.tdb", logger, FileStore{})
if err != nil {
panic(err)
}
if err := db.Set("username", "cesar"); err != nil {
panic(err)
}
val, err := db.Get("theme")
if err != nil {
// key missing or other error
println("error getting key:", err.Error())
return
}
println("theme:", val)
}By default tinydb uses a simple text representation: one key=value per line.
Example file contents:
username=cesar
theme=dark
window=1024x768
Notes:
- Avoid embedding newlines in values; the implementation expects single-line entries.
- If your values may contain
=or newline characters, use an encoding strategy in yourStoreor pre-encode values before callingSet.
- Unit test the
Storeimplementations (file, memory, mocks) independently. - Add tests for basic Set/Get behaviour and for recovery after failures (simulate I/O errors).
Run on: Linux (11th Gen Intel i7-11800H @ 2.30GHz)
The following micro-benchmark measures allocations and time for repeated Set operations.
| Test | ns/op | B/op | allocs/op |
|---|---|---|---|
| BenchmarkSetAlloc-16 | 96,493 | 364 | 5 |
Quick notes:
- 🟢 ns/op — lower is better (time per operation).
- 🟢 B/op and allocs/op — lower is better (memory allocations per operation).
- 🟡 Result: reasonable for a small in-memory store; low allocations but non-negligible per-op cost.
- 💡 Suggestion: consider reusing buffers (buffer pools) to reduce allocations if you need maximum optimization.
⚠️ This benchmark uses an in-memorymemStore(no disk I/O); file-backed implementations will be slower.
- Not a full database: tinydb is intended for small key/value needs with simple persistence.
- No indexing, transactions, concurrency locking, or binary values support out of the box.
MIT