Getting Started
Install Trove, create a storage driver, store and retrieve objects in under 5 minutes.
Installation
Install the core Trove package:
go get github.com/xraph/troveThen install one or more storage drivers:
go get github.com/xraph/trove/drivers/localdrivergo get github.com/xraph/trove/drivers/memdrivergo get github.com/xraph/trove/drivers/s3driverCreate a Driver
Every Trove instance requires a storage driver. Drivers implement driver.Driver and handle the actual backend I/O.
import "github.com/xraph/trove/drivers/localdriver"
drv := localdriver.New()
err := drv.Open(ctx, "file:///var/data/storage")The local driver stores objects as files on disk. Buckets map to subdirectories under the root path. Metadata is stored in sidecar .meta.json files.
import "github.com/xraph/trove/drivers/memdriver"
drv := memdriver.New()
// No Open() call required -- memdriver is ready immediately.The memory driver stores all data in-process using Go maps. It is not persistent and is intended for unit tests and development.
Open a Trove Instance
Pass the driver to trove.Open to create a Trove handle. All subsequent operations flow through this handle.
import "github.com/xraph/trove"
t, err := trove.Open(drv)
if err != nil {
log.Fatal(err)
}
defer t.Close(ctx)Open accepts functional options for configuration:
t, err := trove.Open(drv,
trove.WithDefaultBucket("primary"),
trove.WithChunkSize(8 * 1024 * 1024), // 8 MB streaming chunks
trove.WithPoolSize(32), // max concurrent streams
)Create a Bucket
Buckets are top-level namespaces that group objects. Create one before storing objects:
err := t.CreateBucket(ctx, "uploads")If you set a default bucket with trove.WithDefaultBucket("uploads"), you can pass an empty string for the bucket parameter in subsequent operations and Trove will use the default.
Store an Object
Put stores an object from any io.Reader. It returns *driver.ObjectInfo with the stored object's metadata.
import (
"strings"
"github.com/xraph/trove/driver"
)
info, err := t.Put(ctx, "uploads", "photos/cat.jpg", file,
driver.WithContentType("image/jpeg"),
driver.WithMetadata(map[string]string{
"author": "alice",
"uploaded-by": "web-app",
}),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("stored %s (%d bytes, etag=%s)\n", info.Key, info.Size, info.ETag)For simple text content:
info, err := t.Put(ctx, "uploads", "notes/hello.txt",
strings.NewReader("Hello, Trove!"),
driver.WithContentType("text/plain"),
)Retrieve an Object
Get returns an *driver.ObjectReader that combines a ReadCloser for the content with an Info field containing metadata.
obj, err := t.Get(ctx, "uploads", "photos/cat.jpg")
if err != nil {
log.Fatal(err)
}
defer obj.Close()
// Access metadata via obj.Info
fmt.Printf("content-type: %s, size: %d\n", obj.Info.ContentType, obj.Info.Size)
// Read content
data, err := io.ReadAll(obj)
if err != nil {
log.Fatal(err)
}List Objects
List returns an *driver.ObjectIterator for cursor-based iteration. Use WithPrefix to filter by key prefix.
Collect all results at once:
iter, err := t.List(ctx, "uploads", driver.WithPrefix("photos/"))
if err != nil {
log.Fatal(err)
}
defer iter.Close()
objects, err := iter.All(ctx)
if err != nil {
log.Fatal(err)
}
for _, obj := range objects {
fmt.Printf(" %s %d bytes %s\n", obj.Key, obj.Size, obj.ContentType)
}Iterate one at a time:
iter, err := t.List(ctx, "uploads", driver.WithPrefix("photos/"))
if err != nil {
log.Fatal(err)
}
defer iter.Close()
for {
obj, err := iter.Next(ctx)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Printf(" %s %d bytes\n", obj.Key, obj.Size)
}Additional list options:
| Option | Description |
|---|---|
driver.WithPrefix(p) | Filter objects by key prefix |
driver.WithDelimiter(d) | Group keys by delimiter (e.g. "/") |
driver.WithMaxKeys(n) | Maximum objects per page (default: 1000) |
driver.WithCursor(c) | Pagination cursor from a previous call |
Delete an Object
err := t.Delete(ctx, "uploads", "photos/cat.jpg")Check Metadata
Head returns object metadata without downloading the content. This is useful for checking existence, size, or content type before fetching.
info, err := t.Head(ctx, "uploads", "photos/cat.jpg")
if err != nil {
log.Fatal(err)
}
fmt.Printf("key: %s\n", info.Key)
fmt.Printf("size: %d\n", info.Size)
fmt.Printf("content-type: %s\n", info.ContentType)
fmt.Printf("etag: %s\n", info.ETag)
fmt.Printf("modified: %s\n", info.LastModified)
fmt.Printf("metadata: %v\n", info.Metadata)The ObjectInfo struct contains:
| Field | Type | Description |
|---|---|---|
Key | string | Object key |
Size | int64 | Content length in bytes |
ContentType | string | MIME type |
ETag | string | Entity tag for conditional requests |
LastModified | time.Time | Last modification timestamp |
Metadata | map[string]string | User-defined metadata |
VersionID | string | Version identifier (if versioning enabled) |
StorageClass | string | Storage tier (e.g. "STANDARD", "GLACIER") |
Copy an Object
Copy duplicates an object within the same bucket or across buckets. The source is not modified.
info, err := t.Copy(ctx,
"uploads", "photos/cat.jpg", // source
"archive", "2024/photos/cat.jpg", // destination
)Multi-Backend Routing
Trove can route objects to different storage backends based on bucket, key pattern, or custom logic.
import (
"github.com/xraph/trove"
"github.com/xraph/trove/drivers/localdriver"
"github.com/xraph/trove/drivers/s3driver"
)
primary := localdriver.New()
primary.Open(ctx, "file:///var/data/hot")
archive := s3driver.New()
archive.Open(ctx, "s3://my-archive-bucket?region=us-east-1")
t, err := trove.Open(primary,
// Register a named backend.
trove.WithBackend("archive", archive),
// Route by key pattern (filepath.Match syntax).
trove.WithRoute("*.log", "archive"),
trove.WithRoute("backups/*", "archive"),
// Route by custom function.
trove.WithRouteFunc(func(bucket, key string) string {
if bucket == "compliance" {
return "archive"
}
return "" // fall through to default
}),
)Route resolution priority:
- Route functions (highest)
- Pattern routes
- Default driver (fallback)
Add Middleware
Middleware intercepts the data stream on read and/or write. Register middleware for encryption, compression, or custom processing.
import (
"github.com/xraph/trove/middleware"
"github.com/xraph/trove/middleware/encrypt"
"github.com/xraph/trove/middleware/compress"
)
// Encrypt all objects globally.
t.UseMiddleware(middleware.Registration{
Middleware: encrypt.New(encrypt.WithKeyProvider(
encrypt.NewStaticKeyProvider(myAES256Key),
)),
})
// Compress only text files in the "logs" bucket.
t.UseMiddleware(middleware.Registration{
Middleware: compress.New(),
Scope: middleware.And(
middleware.ForBuckets("logs"),
middleware.ForKeys("*.txt", "*.log", "*.json"),
),
Priority: 10,
})Next Steps
- Architecture -- Understand the storage pipeline, routing, and middleware architecture
- Drivers -- Full reference for all storage backend drivers
- Middleware -- Encryption, compression, deduplication, scanning, and custom middleware
- Streaming -- Chunked transfers, backpressure, resumable uploads, and progress hooks
- Content-Addressable Storage -- Hash-based deduplication and garbage collection
- Virtual Filesystem --
io/fs-compatible filesystem abstraction over object storage
Trove: Multi-Backend Object Storage for Go
Trove is a multi-backend object storage engine for Go with composable middleware, streaming engine, content-addressable storage, and virtual filesystem support.
Architecture
Trove's layered architecture: storage pipeline, multi-backend routing, middleware, streaming, CAS, and VFS.