A lightweight and extensible key-value store for Go, supporting:
- Atomic Writes (writes to a temporary file, then renames to final).
- Optional JSON Encryption (AES-GCM, with a scrypt-derived key).
- Optional .bak backups before overwriting existing files.
- Optional In-Memory Caching (via patrickmn/go-cache).
Repository: github.com/guarzo/jankdb
- Simple Go API – Use
jankdb.Store[T]to store any Go type (map,struct,[]Something, etc.). - Atomic File Writes – Prevent data corruption by writing to a
.tmpfile and renaming once complete. - Automatic Backups – Optionally rename the old file to
.bakbefore overwriting. - Encryption at Rest – Enable encryption by specifying an
EncryptionKey; data is transparently encrypted/decrypted. - In-Memory Caching – Speed up reads with a configurable TTL cache.
- File System Abstraction – Built-in
OSFileSystemfor real I/O, or provide your own mockFileSystemfor testing.
go get github.com/guarzo/jankdbThen import it in your code:
import "github.com/guarzo/jankdb"If you just want to store your data as plain JSON (no encryption, no cache):
package main
import (
"fmt"
"github.com/guarzo/jankdb"
)
type SomeStruct struct {
// ... your fields
}
func main() {
fs := jankdb.OSFileSystem{}
opts := jankdb.StoreOptions{
SubDir: "subdir",
FileName: "some_struct.json",
EnableBackup: true, // create a .bak file before overwriting
UseCache: false, // no in-memory caching
EncryptionKey: "", // empty => no encryption
}
someStore, err := jankdb.NewStore[[]SomeStruct](fs, "/path/to/base", opts)
if err != nil {
panic(err)
}
// Load existing data from disk if present
if err = someStore.Load(); err != nil {
panic(err)
}
// Access or modify
somes := someStore.Get()
fmt.Println("Current loot splits:", somes)
// Save changes
if err = someStore.Save(); err != nil {
panic(err)
}
}Key Steps:
- Instantiate a
Store[T]with the desiredFileSystem, base path, andStoreOptions. - Call
Load()once to read existing data from disk. - Call
Get()andSet(...)to read and modify the in-memory data. - Call
Save()to write changes back to disk atomically.
By providing an EncryptionKey, you enable built-in AES-GCM encryption. Data is encrypted before writing to disk and decrypted upon load.
type Identity struct {
MainID string
// ...
}
func main() {
fs := jankdb.OSFileSystem{}
opts := jankdb.StoreOptions{
SubDir: "app",
FileName: "identities.json.enc",
EnableBackup: true,
UseCache: true, // also enable caching
DefaultExpiration: 30 * time.Minute, // cache TTL
CleanupInterval: 5 * time.Minute,
EncryptionKey: "MySuperSecretPassword", // set a passphrase
}
store, _ := jankdb.NewStore[map[string]Identity](fs, "/secure/path", opts)
_ = store.Load() // decrypts if file exists
data := store.Get()
if data == nil {
data = make(map[string]Identity)
}
data["12345"] = Identity{MainID: "SomeID"}
store.Set(data)
_ = store.Save() // automatically encrypts & atomically writes to file
}Notes:
- Do not commit your
EncryptionKeyto source control. - This built-in approach uses a scrypt-derived AES-GCM scheme. For production-grade security, review your key management, scrypt parameters, and consider using more advanced cryptographic solutions.
jankdb can store a copy of your data in memory for faster reads. You set:
UseCache: trueto enable caching.DefaultExpirationto define how long an item remains in cache.CleanupIntervalto define how often expired items are purged.
opts := jankdb.StoreOptions{
FileName: "mydata.json",
UseCache: true,
DefaultExpiration: 15 * time.Minute,
CleanupInterval: 5 * time.Minute,
// ...
}
myStore, _ := jankdb.NewStore[MyData](fs, "/some/dir", opts)
_ = myStore.Load() // populates cache
cachedVal := myStore.Get() // immediateWarning: If multiple processes modify the same file, you’ll need your own synchronization to keep caches consistent.
jankdb is experimental and was created to reduce boilerplate for simple data persistence. It is not intended to replace heavyweight databases. Use it for small to medium “configuration” or “state” files, especially where portability and simplicity matter more than scale.
- Configuration or User Preferences in a CLI or server application.
- Encrypted secrets for small-scale projects that don’t require a dedicated secrets manager.
- Caching ephemeral data for improved performance.
- Single-writer model:
jankdbis designed for a single process or thread writing to the file at a time. - Encryption: This module only provides basic AES-GCM encryption with scrypt-based key derivation. In high-security contexts, you may need more rigorous key management and encryption strategies.
- Concurrency: The
Store[T]type is guarded by async.RWMutex, so concurrent reads and writes from multiple goroutines should work, but the underlying data typeTitself must be safe to manipulate from multiple threads (or you must carefully manage concurrent updates). - Performance: Each
Save()operation rewrites the entire file. If your data is very large, you may need a different approach (e.g., partial updates, a real database).
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License.
Author: @guarzo
Project Link: github.com/guarzo/jankdb