NATS client library

I’ve been working on a NATS client library over the last few weeks. It’s now at the point that I’m happy to share it with others, but definitely not production ready at this point. It’s very much based on the official NATS Go and C libraries, but using Zig idioms. NATS Core should work well, JetStream is not as well tested yet. The goal is to have feature parity with the official client libraries. And the API is also very similar, so if you follow the official docs, the functions names and options they use are named the same here.

I know there are a couple of other libraries for NATS, but they are either abandoned and/or their API is really different from the official one and/or I didn’t trust the code when looking at it.

For now I’m using blocking sockets, would definitely like to switch to non-blocking sockets an an IO loop (maybe using libxev), but that will have to wait a little bit and it’s not as important as threads will be needed for message dispatching anyway.

7 Likes

Compare to this nats client

Open issue for problematic code - don’t hesitate

Also register you code in nats s/w list - it’s the usual way to publish 3rd party nats code.

Welcome to the club !!!

Hey, I’ll be honest, I saw your library before I started and I didn’t like it. Just the naming put me off, but then I looked further and saw that there is no reconnection handling, the parser is significantly less efficient than the state machine in the official libraries and similar things.

no problem

Personally I don’t like automatic re-connection provided by client libraries. It may create real mess in the flow. Bit it is my personal experience/opinion. So it was by intent

Also I don’t use any C library - just native Zig for fun

And sorry offthetopic - may be you know how to activate log.debug in Release* mode ?

The reconnection is an important part of the libraries, because you can dynamically extend the cluster, the clients will immediately discover new nodes to connect to via the INFO message and that enables much a better HA solution.

For the logging, I have log level in std_options set to debug and my custom log handler filter what gets printed based on runtime parameters.

1 Like

Great, thank you
does log visible in Release* mode?

Yes, if you explicitly set it to debug, it will be there even in release builds. For example, this is what I use:

1 Like

looks it works in exe, but not in tests :joy:

You can add a custom test runner, I currently don’t have it set to debug always, but I should, thanks for the reminder :slight_smile:

i did it without success whole story

btw for non-blocked socket

It works for me:

lukas@gill:~/projects/nats.zig$ TEST_FILTER="parser split msg" TEST_LOG_CAPTURE=false zig build test-unit --release=safe
root.test_0 (0.00ms)
(nats/debug): chunk_size: 1
(nats/debug): chunk_size: 2
(nats/debug): chunk_size: 3
...

Exact now I found the root of the problem:
there are two addTest in my build.zig:

    // Creates a step for unit testing. This only builds the test executable
    // but does not run it.
    const lib_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/all_tests.zig"),
        .target = target,
        .optimize = optimize,
        .single_threaded = false,
        .error_tracing = true,
        .test_runner = .{ .path = b.path("testRunner.zig"), .mode = .simple },
    });

and

    const exe_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/all_tests.zig"),
        .target = target,
        .optimize = optimize,
        .error_tracing = true,
        .test_runner = .{ .path = b.path("testRunner.zig"), .mode = .simple },
    });

Only one of them has test_runner

and

pub const std_options: std.Options = .{
    .logFn = log,
    .log_level = .debug,
};

in test_runner.zig

New client is added to the list on nats site fork.

Let’s wait merge to main

I’ve been working on a zenoh lobrary, but the complexity is insane so for now I’ve just been trying to write bindings for the rust built c binaries.

What has it been like implementing NATS in pure zig? Did you just dive into the spec or have you done other implementations before?

Unfortunately zenoh is implementation defined so I don’t have a spec to reference, and I haven’t learned how to read rust…

NATS is much easier in this regard, because it has proper specs, and it’s done in layers, so the complexity is fairly isolated, plus it has multiple client libraries that can be for cross-referencing.

The whole project started as an experiment how far can I get if I give Claude Code enough reference material. I wouldn’t even attempt doing this, if I didn’t have prior good experience with these AI tools I mainly just focused on the high level parts, how to design the API in Zig, the low level details were extracted and translated by Claude Code for me. I started with a clone of the C library and then started diverging as I could do more and more things the Zig way.

1 Like

I had two NATS projects before Nats client Zig implementation:

Also NATS has huge documentation.
But anyway - without reverse engeneering (running go and php clients and capturing messages) - no chance :joy:

So I understand how hard is implement “well-known” protocols

As far as I understand there is python lib

python does not have multithreading (my very little knowledge) => python implementation should be more clean and straitforward.

examples in this repo are scripts, and github action contains example of running zenoh

May be it’s better starting point

[Edited] I dived deeper in pyhon repo. It’s also Rust binding :scream:
So code itself isn’t very helpfull

Don’t tell me please that it also impossible to capture messages between zenoh client and zenoh - this will be the final blow

1 Like

Updated list of Zig clients for NATS