lyyyuna 的小花园

动静中之动, by

RSS

Embedded Vector Database for Go and Node.js -- milvus-lite Wrappers

发表于 2026-04
milvus-lite only had Python bindings, so I made Go and Node.js wrappers

Background

I've been building some AI Agent tooling recently and needed a local vector database for semantic search. Milvus is a solid choice, and its lightweight variant milvus-lite is perfect -- no etcd, no MinIO, just a single file database.

The catch? milvus-lite only ships with Python bindings. If you're writing your agent in Go or Node.js, you're out of luck. The official stance is pretty clear: the Node.js SDK maintainer closed the feature request saying "C++ core is not portable to TypeScript"; the Go version has been sitting in an issue discussion with no progress.

But if you look at how milvus-lite actually works, this reasoning doesn't hold up.

What milvus-lite really is

Under the hood, milvus-lite is a standalone C++ gRPC server binary. The Python layer does exactly two things:

  1. Starts this binary via subprocess.Popen
  2. Communicates through the standard Milvus gRPC protocol

In other words, Python is just a process launcher. The real work is done by the C++ binary, which exposes the exact same gRPC interface as Milvus Standalone.

Any language that can spawn a subprocess and speak gRPC can use milvus-lite. No CGo, no FFI, no porting C++ to anything.

So I built two wrapper libraries:

Installation

Go

go get github.com/lyyyuna/milvus-lite-go/v2

The binary is embedded in platform-specific sub-modules via go:embed. The Go module proxy only downloads the sub-module matching your platform (~25-55MB), not all of them.

Node.js

npm install @lyyyuna/milvus-lite @zilliz/milvus2-sdk-node

Same distribution pattern as esbuild and swc: the main package is pure JS, binaries are split into platform-specific npm packages as optionalDependencies. npm automatically installs only the one for your OS/arch.

Both versions require zero runtime downloads. Install and go.

Usage

Go

package main

import (
    "context"
    "log"

    milvuslite "github.com/lyyyuna/milvus-lite-go/v2"
    "github.com/milvus-io/milvus-sdk-go/v2/client"
    "github.com/milvus-io/milvus-sdk-go/v2/entity"
)

func main() {
    // Start milvus-lite, data persisted to local file
    server, err := milvuslite.Start("./milvus.db")
    if err != nil {
        log.Fatal(err)
    }
    defer server.Stop()

    // Connect with the official SDK -- no changes needed
    c, _ := client.NewClient(context.Background(), client.Config{
        Address: server.Addr(),
    })
    defer c.Close()

    // Standard Milvus operations from here
    schema := entity.NewSchema().WithName("demo").
        WithField(entity.NewField().WithName("id").WithDataType(entity.FieldTypeInt64).WithIsPrimaryKey(true).WithIsAutoID(true)).
        WithField(entity.NewField().WithName("vector").WithDataType(entity.FieldTypeFloatVector).WithDim(128))

    c.CreateCollection(context.Background(), schema, entity.DefaultShardNumber)
}

Node.js

import { start } from "@lyyyuna/milvus-lite";
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

const server = await start("./milvus.db");

const client = new MilvusClient({ address: server.addr });

await client.createCollection({
    collection_name: "demo",
    fields: [
        { name: "id", data_type: DataType.Int64, is_primary_key: true, autoID: true },
        { name: "vector", data_type: DataType.FloatVector, dim: 128 },
    ],
});

await server.stop();

The API is minimal for both: Start/start to launch, grab the address, connect with the official Milvus SDK. Data operations are identical to connecting to a remote Milvus cluster -- switching to production is just changing the address.

Supported Platforms

OS Arch Go npm
macOS arm64 (Apple Silicon)
macOS amd64 (Intel)
Linux amd64
Linux arm64

How It Works

The core idea is simple: don't touch the C++ core, just handle process management and distribution.

Where the binary comes from

The milvus-lite Python package on PyPI is a wheel file (basically a zip). Inside it, alongside the Python code, there's a pre-compiled milvus binary and a bunch of shared libraries (libknowhere, libglog, libtbb, etc.).

I wrote a script that downloads wheels for each platform from PyPI, extracts the milvus_lite/lib/ directory, and drops it into the corresponding platform package.

Distribution

Go uses a multi-module monorepo approach. One repo, multiple go.mod files -- one per platform:

platform/
├── darwin-arm64/go.mod    # independent module, go:embed the binary
├── darwin-amd64/go.mod
├── linux-amd64/go.mod
└── linux-arm64/go.mod

The Go module proxy excludes sub-directories with their own go.mod when packaging a module. So go get downloads the main module (a few KB) plus only your platform's sub-module (~25-55MB). At runtime, build tags select the right embedded data, which gets extracted to a temp directory.

npm uses optionalDependencies, the same approach as esbuild. The main package declares four platform packages as optional deps. npm skips the ones that don't match the current platform.

Startup sequence

  1. Extract (Go) or locate (npm) the binary and shared libs
  2. Set LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS)
  3. Spawn the milvus process via exec.Command (Go) or child_process.spawn (Node.js)
  4. Wait for the gRPC port to become ready
  5. Return the address for the user to connect with the official SDK

A known quirk

milvus-lite's ShowCollections gRPC response populates collection_names but not collection_ids. The Python and Node.js SDKs iterate over collection_names, so they work fine. The Go SDK iterates over collection_ids, so client.ListCollections() returns an empty list.

The Go wrapper provides a workaround:

names, _ := milvuslite.ListCollections(ctx, server.Addr())

Wrapping up

The code is not large -- a few hundred lines for each language. The real effort went into understanding milvus-lite's architecture, figuring out platform distribution mechanics for Go modules and npm, and navigating Go's semver rules for v2+ modules.

If you're building AI Agents in Go or Node.js and need a local embedded vector database for semantic search, RAG, or knowledge bases, give it a try.

lyyyuna 沪ICP备2025110782号-1