natsql
v1.0 — MIT Licensed

Query your NATS JetStream
state with SQL

Zero infrastructure beyond NATS.

Define materialized views over JetStream streams, then query the resulting state with SELECT ... WHERE ... — no Postgres, no Redis, no Kafka. Just NATS.

How it works

Stream → Materialize → Query

natsql is a thin SQL query layer over NATS KV, fed by a configurable materializer that consumes your streams.

1. Define views

Specify source stream, key fields, and columns in YAML/JSON config

2. Publish events

The materializer consumes JetStream messages in order, automatically

3. KV storage

Materialized state is stored in a NATS KV bucket — PK lookups are O(1)

4. Query with SQL

Query via NATS, HTTP, or in-process Go — returns typed JSON

“Give a developer a stream, and they’ll build event-driven systems. Give them a materialized view engine, and they’ll query their state.”

Event JetStream Materializer KV Bucket SQL JSON
Quick start

Try it in 60 seconds

No Docker, no databases, no config. Just Go.

main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/nats-io/nats.go/jetstream"
	"github.com/gacopys/natsql"
)

func main() {
	ctx := context.Background()
	cfg := &natsql.Config{
		Views: []natsql.ViewConfig{{
			Name: "users", SourceStream: "events",
			KeyFields: []string{"user_id"},
			Columns: []natsql.ColumnConfig{
				{Name: "user_id", From: "id", Type: natsql.ColumnTypeString, PrimaryKey: true},
				{Name: "name", From: "name", Type: natsql.ColumnTypeString},
			},
		}},
	}
	eng, _ := natsql.NewEmbedded(cfg)
	defer eng.Close()

	js, _ := jetstream.New(eng.NC())
	js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
		Name: "events", Subjects: []string{"events.>"},
	})
	eng.Start(ctx)
	js.Publish(ctx, "events.user-created", []byte(`{"id": "abc", "name": "Alice"}`))
	time.Sleep(time.Second)

	fmt.Println(eng.Query(ctx, "SELECT name FROM users WHERE user_id = 'abc'").Results)
}
$ go mod init demo && go mod tidy && go run .

Browse all examples on GitHub →

Why natsql

Everything you need,
nothing you don’t

Queryable State

SELECT from your event streams without maintaining snapshots or bolting on a second database. PK lookups are O(1) KV gets.

Zero Infrastructure

No Postgres, no Redis, no Kafka. natsql runs entirely on NATS JetStream. Embedded NATS means zero dependencies for development.

SQL-Powered

Standard read-only SQL via the vitess sqlparser. Project columns, filter by PK, combine conditions with AND.

Event-Driven

Materialize any JetStream stream into a queryable KV snapshot. Malformed events go to a DLQ — your pipeline keeps running.

Perfect for

  • NATS developers who need simple queryable state without leaving the NATS ecosystem
  • Event-driven system builders who want to materialize a stream into a KV snapshot and query it
  • Team leads looking to reduce infrastructure surface area — one less Postgres or Redis cluster
  • Anyone prototyping — embedded NATS means you can be up in 60 seconds with zero dependencies