Couple of days ago I wrote BoltApi, a REST API on top of BoltDB which is a library based key-value store. Yes, its a library which means you use it by calling the library APIs on your app as opposed to talking to a separate process.

You can run it like:

boltapi -dbpath=./app.db -port=8080

It uses go-json-rest to expose REST endpoints, which means its easy to offer one of the middlewares for the BoltApi. So if I want to add gzip compression later on, or basic auth then we just include those middlewares or better yet offer command line flag like:

boltapi -dbpath=./app.db -port=8080 -with-basic-auth -with-gzip

Making the API ready for production out of the box. The REST API itself is simple, it exposes the following endpoints:

Buckets endpoint

/api/v1/buckets

GET  - List buckets
POST - Add bucket

Bucket endpoint (singular)

/api/v1/buckets/:name

GET    - List bucket items
POST   - Add item on the bucket
DELETE - Delete bucket

Bucket item endpoint

/api/v1/buckets/:name/:key

GET    - Retrieve item
PUT    - Update item
DELETE - Delete item

Building the REST API was straight forward, BoltDB offers APIs for read-only and read/write transactions. Here’s an excerpt from BoltApi for retrieving a bucket:

func (restapi *RestApi) GetBucket(w rest.ResponseWriter, r *rest.Request) {
	bucketName := r.PathParam("name")
	items := []*BucketItem{}
	if err := restapi.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			return ErrBucketMissing
		}

		return bucket.ForEach(func(k, v []byte) error {
			bucketItem := &BucketItem{Key: string(k)}
			bucketItem.DecodeValue(v)
			items = append(items, bucketItem)
			return nil
		})
	}); err != nil {
		log.Println(ApiError{ErrBucketGet, err})
		switch err {
		case ErrBucketGet:
			rest.Error(w, ErrBucketGet.Error(), http.StatusInternalServerError)
		case ErrBucketMissing:
			rest.Error(w, ErrBucketMissing.Error(), http.StatusInternalServerError)
		}
		return
	}
	w.WriteJson(items)
}

The db.View is the read-only transaction and there’s also db.Update which supports read and write. Notice how compact the code is, the param to both transactions is just a function that accepts bolt.Tx - BoltDB’s transaction object and everything you do inside that function is enclosed in that transaction.

So next time you have a Go project that needs key-value storage, consider BoltDB or BoltApi if you want a REST API on top of it.