Skip to content
WeftKitBeta

Go Integration

Go Integration

WeftKit Standalone speaks standard wire protocols, so every existing Go database driver works without modification. This guide covers the most common drivers for each WeftKit engine.

Prerequisites

  • WeftKit Standalone running and reachable (see Deployment)
  • Go 1.21 or later
  • A go.mod file in your project

Module Dependencies

go
// go.mod
module example.com/myapp

go 1.21

require (
    github.com/lib/pq                          v1.10.9
    github.com/jackc/pgx/v5                    v5.6.0
    gorm.io/gorm                               v1.25.10
    gorm.io/driver/postgres                    v1.5.9
    go.mongodb.org/mongo-driver/v2             v2.1.0
    github.com/redis/go-redis/v9               v9.6.1
    github.com/neo4j/neo4j-go-driver/v5        v5.22.0
    github.com/aws/aws-sdk-go-v2               v1.30.3
    github.com/aws/aws-sdk-go-v2/config        v1.27.27
    github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4
    github.com/aws/aws-sdk-go-v2/credentials   v1.17.27
)

1. WeftKitRel via database/sql + lib/pq

WeftKitRel speaks the PostgreSQL v3 wire protocol. The lib/pq driver connects to it the same way it connects to PostgreSQL.

Connecting

go
package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/lib/pq"
)

func main() {
    dsn := "host=localhost port=5432 user=app_user password=secret dbname=mydb sslmode=disable"

    db, err := sql.Open("postgres", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Connection pool settings
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)

    if err := db.Ping(); err != nil {
        log.Fatal("cannot reach WeftKitRel:", err)
    }
    fmt.Println("connected to WeftKitRel")
}

Querying

go
func listUsers(ctx context.Context, db *sql.DB, minAge int) ([]User, error) {
    rows, err := db.QueryContext(ctx,
        "SELECT id, name, email FROM users WHERE age > $1 ORDER BY name",
        minAge,
    )
    if err != nil {
        return nil, fmt.Errorf("query users: %w", err)
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
            return nil, fmt.Errorf("scan user: %w", err)
        }
        users = append(users, u)
    }
    return users, rows.Err()
}

Executing Writes

go
func createUser(ctx context.Context, db *sql.DB, name, email string, age int) (int64, error) {
    var id int64
    err := db.QueryRowContext(ctx,
        "INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING id",
        name, email, age,
    ).Scan(&id)
    if err != nil {
        return 0, fmt.Errorf("insert user: %w", err)
    }
    return id, nil
}

Transactions

go
func transferFunds(ctx context.Context, db *sql.DB, fromID, toID int64, amount float64) error {
    tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
    if err != nil {
        return fmt.Errorf("begin transaction: %w", err)
    }
    defer tx.Rollback() // no-op if committed

    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance - $1 WHERE id = $2",
        amount, fromID,
    )
    if err != nil {
        return fmt.Errorf("debit: %w", err)
    }

    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance + $1 WHERE id = $2",
        amount, toID,
    )
    if err != nil {
        return fmt.Errorf("credit: %w", err)
    }

    return tx.Commit()
}

2. WeftKitRel via jackc/pgx/v5

pgx is a high-performance PostgreSQL driver that avoids the database/sql abstraction overhead. Use it when you need named arguments, batch queries, or the fastest possible query throughput.

Connecting with a Pool

go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/jackc/pgx/v5/pgxpool"
)

func newPool(ctx context.Context) (*pgxpool.Pool, error) {
    connString := "postgresql://app_user:secret@localhost:5432/mydb"

    config, err := pgxpool.ParseConfig(connString)
    if err != nil {
        return nil, err
    }
    config.MaxConns = 20
    config.MinConns = 2

    pool, err := pgxpool.NewWithConfig(ctx, config)
    if err != nil {
        return nil, fmt.Errorf("create pool: %w", err)
    }
    return pool, nil
}

Single Connection

go
import "github.com/jackc/pgx/v5"

conn, err := pgx.Connect(ctx, "postgresql://app_user:secret@localhost:5432/mydb")
if err != nil {
    log.Fatal(err)
}
defer conn.Close(ctx)

Collecting Rows into a Struct

go
type Product struct {
    ID    int64
    Name  string
    Price float64
}

func listProducts(ctx context.Context, pool *pgxpool.Pool) ([]Product, error) {
    rows, err := pool.Query(ctx, "SELECT id, name, price FROM products WHERE active = true")
    if err != nil {
        return nil, err
    }
    return pgx.CollectRows(rows, pgx.RowToStructByName[Product])
}

Named Arguments

go
import "github.com/jackc/pgx/v5"

func searchProducts(ctx context.Context, pool *pgxpool.Pool, category string, maxPrice float64) ([]Product, error) {
    rows, err := pool.Query(ctx,
        "SELECT id, name, price FROM products WHERE category = @category AND price <= @max_price",
        pgx.NamedArgs{
            "category":  category,
            "max_price": maxPrice,
        },
    )
    if err != nil {
        return nil, err
    }
    return pgx.CollectRows(rows, pgx.RowToStructByName[Product])
}

Batch Queries

go
func insertProducts(ctx context.Context, pool *pgxpool.Pool, products []Product) error {
    batch := &pgx.Batch{}
    for _, p := range products {
        batch.Queue(
            "INSERT INTO products (name, price) VALUES ($1, $2)",
            p.Name, p.Price,
        )
    }

    results := pool.SendBatch(ctx, batch)
    defer results.Close()

    for range products {
        if _, err := results.Exec(); err != nil {
            return fmt.Errorf("batch insert: %w", err)
        }
    }
    return nil
}

3. WeftKitRel via GORM

GORM is the most popular Go ORM. Point its PostgreSQL driver at WeftKitRel.

Connecting

go
package main

import (
    "log"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

func newDB() (*gorm.DB, error) {
    dsn := "host=localhost user=app_user password=secret dbname=mydb port=5432 sslmode=disable"
    return gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
}

Defining a Model

go
import "time"

type User struct {
    ID        uint      `gorm:"primarykey"`
    Name      string    `gorm:"not null"`
    Email     string    `gorm:"uniqueIndex;not null"`
    Age       int
    CreatedAt time.Time
    UpdatedAt time.Time
    Orders    []Order   `gorm:"foreignKey:UserID"`
}

type Order struct {
    ID        uint    `gorm:"primarykey"`
    UserID    uint    `gorm:"not null;index"`
    Total     float64 `gorm:"not null"`
    Status    string  `gorm:"default:'pending'"`
}

Auto-Migration

go
func migrate(db *gorm.DB) error {
    return db.AutoMigrate(&User{}, &Order{})
}

CRUD Operations

go
// Create
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
result := db.Create(&user)
if result.Error != nil {
    return result.Error
}
fmt.Println("created user with ID:", user.ID)

// Read by primary key
var found User
db.First(&found, user.ID)

// Read with conditions
var adults []User
db.Where("age > ?", 18).Order("name").Find(&adults)

// Update
db.Save(&User{ID: 1, Name: "Alice Smith", Email: "alice@example.com", Age: 31})

// Update specific fields
db.Model(&User{}).Where("age < ?", 18).Update("status", "minor")

// Delete
db.Delete(&User{}, user.ID)

Preloading Associations

go
var users []User
db.Preload("Orders").Where("age > ?", 18).Find(&users)

for _, u := range users {
    fmt.Printf("%s has %d orders\n", u.Name, len(u.Orders))
}

Transactions with GORM

go
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&User{Name: "Bob", Email: "bob@example.com"}).Error; err != nil {
        return err // triggers rollback
    }
    if err := tx.Create(&Order{UserID: 2, Total: 49.99}).Error; err != nil {
        return err // triggers rollback
    }
    return nil // commit
})

4. WeftKitDoc via go.mongodb.org/mongo-driver/v2

WeftKitDoc speaks the MongoDB Wire protocol. The official MongoDB Go driver connects to it without any changes.

Connecting

go
package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/v2/mongo"
    "go.mongodb.org/mongo-driver/v2/mongo/options"
)

func newMongoClient(ctx context.Context) (*mongo.Client, error) {
    uri := "mongodb://app_user:secret@localhost:27017"
    client, err := mongo.Connect(options.Client().ApplyURI(uri))
    if err != nil {
        return nil, err
    }
    if err := client.Ping(ctx, nil); err != nil {
        return nil, err
    }
    return client, nil
}

Inserting Documents

go
import (
    "go.mongodb.org/mongo-driver/v2/bson"
    "go.mongodb.org/mongo-driver/v2/mongo"
)

type Article struct {
    Title   string   `bson:"title"`
    Author  string   `bson:"author"`
    Tags    []string `bson:"tags"`
    Views   int      `bson:"views"`
}

func insertArticles(ctx context.Context, col *mongo.Collection) error {
    // Single insert
    _, err := col.InsertOne(ctx, Article{
        Title:  "Getting Started with WeftKit",
        Author: "Alice",
        Tags:   []string{"database", "weftkit"},
        Views:  0,
    })
    if err != nil {
        return err
    }

    // Bulk insert
    docs := []interface{}{
        Article{Title: "MVCC Deep Dive", Author: "Bob", Tags: []string{"internals"}},
        Article{Title: "Vector Search", Author: "Carol", Tags: []string{"ai", "search"}},
    }
    _, err = col.InsertMany(ctx, docs)
    return err
}

Querying Documents

go
// Find one
var article Article
filter := bson.D{{Key: "author", Value: "Alice"}}
err := col.FindOne(ctx, filter).Decode(&article)
if err != nil {
    return err
}

// Find many
cursor, err := col.Find(ctx, bson.D{{Key: "tags", Value: "database"}})
if err != nil {
    return err
}
defer cursor.Close(ctx)

var articles []Article
if err := cursor.All(ctx, &articles); err != nil {
    return err
}

Updating Documents

go
filter := bson.D{{Key: "title", Value: "Getting Started with WeftKit"}}
update := bson.D{{Key: "$set", Value: bson.D{
    {Key: "views", Value: 1500},
    {Key: "featured", Value: true},
}}}
_, err = col.UpdateOne(ctx, filter, update)

Aggregation Pipeline

go
pipeline := mongo.Pipeline{
    {{Key: "$match", Value: bson.D{{Key: "views", Value: bson.D{{Key: "$gt", Value: 100}}}}}},
    {{Key: "$group", Value: bson.D{
        {Key: "_id", Value: "$author"},
        {Key: "total_views", Value: bson.D{{Key: "$sum", Value: "$views"}}},
        {Key: "article_count", Value: bson.D{{Key: "$sum", Value: 1}}},
    }}},
    {{Key: "$sort", Value: bson.D{{Key: "total_views", Value: -1}}}},
}

cursor, err := col.Aggregate(ctx, pipeline)

Creating Indexes

go
import "go.mongodb.org/mongo-driver/v2/mongo/indexopt"

indexModel := mongo.IndexModel{
    Keys:    bson.D{{Key: "author", Value: 1}, {Key: "views", Value: -1}},
    Options: options.Index().SetUnique(false),
}
_, err = col.Indexes().CreateOne(ctx, indexModel)

5. WeftKitMem via go-redis/v9

WeftKitMem speaks Redis RESP3. The go-redis client connects to it directly.

Connecting

go
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/redis/go-redis/v9"
)

func newRedisClient() *redis.Client {
    return redis.NewClient(&redis.Options{
        Addr:         "localhost:6379",
        Password:     "secret",
        DB:           0,
        PoolSize:     10,
        MinIdleConns: 2,
        DialTimeout:  5 * time.Second,
        ReadTimeout:  3 * time.Second,
        WriteTimeout: 3 * time.Second,
    })
}

String Commands

go
ctx := context.Background()

// Set with expiration
err := client.Set(ctx, "session:abc123", "user_42", 30*time.Minute).Err()

// Get
val, err := client.Get(ctx, "session:abc123").Result()
if err == redis.Nil {
    fmt.Println("key not found")
} else if err != nil {
    log.Fatal(err)
} else {
    fmt.Println("session value:", val)
}

// Atomic increment
views, err := client.Incr(ctx, "article:42:views").Result()

Hash Commands

go
// Set multiple hash fields
err = client.HSet(ctx, "user:42",
    "name", "Alice",
    "email", "alice@example.com",
    "plan", "pro",
).Err()

// Get all hash fields
fields, err := client.HGetAll(ctx, "user:42").Result()
fmt.Println(fields["name"], fields["plan"])

// Get specific field
email, err := client.HGet(ctx, "user:42", "email").Result()

List Commands

go
// Push to list
client.LPush(ctx, "tasks:queue", "task_1", "task_2", "task_3")

// Pop (blocking, wait up to 5 seconds)
result, err := client.BLPop(ctx, 5*time.Second, "tasks:queue").Result()
if err == nil {
    fmt.Println("processing task:", result[1])
}

// Read range without removing
items, err := client.LRange(ctx, "tasks:queue", 0, -1).Result()

Sorted Set Commands

go
// Add members with scores
client.ZAdd(ctx, "leaderboard",
    redis.Z{Score: 1500, Member: "alice"},
    redis.Z{Score: 2200, Member: "bob"},
    redis.Z{Score: 980, Member: "carol"},
)

// Get top 10 (descending)
top, err := client.ZRevRangeWithScores(ctx, "leaderboard", 0, 9).Result()
for _, entry := range top {
    fmt.Printf("%s: %.0f\n", entry.Member, entry.Score)
}

Pipeline

go
pipe := client.Pipeline()
pipe.Set(ctx, "key:1", "value_1", 10*time.Minute)
pipe.Set(ctx, "key:2", "value_2", 10*time.Minute)
pipe.Incr(ctx, "pipeline:counter")
cmds, err := pipe.Exec(ctx)
if err != nil {
    log.Fatal(err)
}
for _, cmd := range cmds {
    fmt.Println(cmd.String())
}

Pub/Sub

go
// Publisher
go func() {
    for {
        client.Publish(ctx, "events:orders", `{"order_id":42,"status":"shipped"}`)
        time.Sleep(time.Second)
    }
}()

// Subscriber
pubsub := client.Subscribe(ctx, "events:orders")
defer pubsub.Close()

ch := pubsub.Channel()
for msg := range ch {
    fmt.Println("received:", msg.Payload)
}

6. WeftKitGraph via neo4j.com/neo4j-go-driver/v5

WeftKitGraph speaks the Bolt protocol. The official Neo4j Go driver connects to it directly.

Connecting

go
package main

import (
    "context"
    "log"

    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

func newGraphDriver(ctx context.Context) (neo4j.DriverWithContext, error) {
    uri  := "bolt://localhost:7687"
    auth := neo4j.BasicAuth("app_user", "secret", "")

    driver, err := neo4j.NewDriverWithContext(uri, auth)
    if err != nil {
        return nil, err
    }
    if err := driver.VerifyConnectivity(ctx); err != nil {
        return nil, err
    }
    return driver, nil
}

Read Session

go
type Person struct {
    Name string
    Age  int
}

func findFriends(ctx context.Context, driver neo4j.DriverWithContext, name string) ([]Person, error) {
    session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
    defer session.Close(ctx)

    result, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
        records, err := tx.Run(ctx,
            "MATCH (p:Person {name: $name})-[:FRIEND]->(f:Person) RETURN f.name AS name, f.age AS age",
            map[string]interface{}{"name": name},
        )
        if err != nil {
            return nil, err
        }

        var friends []Person
        for records.Next(ctx) {
            record := records.Record()
            name, _ := record.Get("name")
            age, _ := record.Get("age")
            friends = append(friends, Person{Name: name.(string), Age: int(age.(int64))})
        }
        return friends, records.Err()
    })
    if err != nil {
        return nil, err
    }
    return result.([]Person), nil
}

Write Session

go
func createFriendship(ctx context.Context, driver neo4j.DriverWithContext, name1, name2 string) error {
    session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite})
    defer session.Close(ctx)

    _, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
        _, err := tx.Run(ctx, `
            MERGE (a:Person {name: $name1})
            MERGE (b:Person {name: $name2})
            MERGE (a)-[:FRIEND]->(b)
        `, map[string]interface{}{"name1": name1, "name2": name2})
        return nil, err
    })
    return err
}

7. WeftKitKV via AWS SDK for Go v2

WeftKitKV exposes a DynamoDB-compatible REST API. Use the AWS SDK for Go v2 with a custom endpoint pointing at WeftKit.

Connecting

go
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/credentials"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func newDynamoClient(ctx context.Context) (*dynamodb.Client, error) {
    cfg, err := config.LoadDefaultConfig(ctx,
        config.WithRegion("us-east-1"),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            "weftkit",  // access key — any value accepted by WeftKitKV
            "weftkit",  // secret key — any value accepted by WeftKitKV
            "",
        )),
        config.WithEndpointResolverWithOptions(
            aws.EndpointResolverWithOptionsFunc(
                func(service, region string, options ...interface{}) (aws.Endpoint, error) {
                    return aws.Endpoint{URL: "http://localhost:8000"}, nil
                },
            ),
        ),
    )
    if err != nil {
        return nil, err
    }
    return dynamodb.NewFromConfig(cfg), nil
}

PutItem

go
func putItem(ctx context.Context, client *dynamodb.Client) error {
    _, err := client.PutItem(ctx, &dynamodb.PutItemInput{
        TableName: aws.String("Sessions"),
        Item: map[string]types.AttributeValue{
            "session_id": &types.AttributeValueMemberS{Value: "sess_abc123"},
            "user_id":    &types.AttributeValueMemberN{Value: "42"},
            "expires_at": &types.AttributeValueMemberN{Value: "1735689600"},
            "data":       &types.AttributeValueMemberS{Value: `{"theme":"dark"}`},
        },
    })
    return err
}

GetItem

go
func getItem(ctx context.Context, client *dynamodb.Client, sessionID string) (map[string]types.AttributeValue, error) {
    output, err := client.GetItem(ctx, &dynamodb.GetItemInput{
        TableName: aws.String("Sessions"),
        Key: map[string]types.AttributeValue{
            "session_id": &types.AttributeValueMemberS{Value: sessionID},
        },
    })
    if err != nil {
        return nil, err
    }
    return output.Item, nil
}

DeleteItem

go
func deleteItem(ctx context.Context, client *dynamodb.Client, sessionID string) error {
    _, err := client.DeleteItem(ctx, &dynamodb.DeleteItemInput{
        TableName: aws.String("Sessions"),
        Key: map[string]types.AttributeValue{
            "session_id": &types.AttributeValueMemberS{Value: sessionID},
        },
    })
    return err
}

Connection String Quick Reference

| Engine | Driver | Connection String | |---|---|---| | WeftKitRel | lib/pq | host=localhost port=5432 user=app_user password=secret dbname=mydb sslmode=disable | | WeftKitRel | pgx | postgresql://app_user:secret@localhost:5432/mydb | | WeftKitRel | GORM | host=localhost user=app_user password=secret dbname=mydb port=5432 sslmode=disable | | WeftKitDoc | mongo-driver | mongodb://app_user:secret@localhost:27017 | | WeftKitMem | go-redis | Addr: "localhost:6379", Password: "secret" | | WeftKitGraph | neo4j-driver | bolt://localhost:7687 | | WeftKitKV | aws-sdk-go-v2 | http://localhost:8000 (endpoint override) |

Next Steps