# Luvit: a Node-style Lua runtime on libuv and LuaJIT

> Luvit wraps libuv and LuaJIT in a Node-shaped standard library, distributed as a self-contained binary through lit. It suits Lua programmers who want asynchronous I/O without writing C bindings, and it is the wrong tool for anyone expecting Node's package ecosystem or a maintained release cadence.

**luvit/luvit** — Lua + libUV + jIT = pure awesomesauce

- Repository: https://github.com/luvit/luvit
- Website: https://luvit.io/
- Stars: 3,969 · Forks: 374
- Language: Lua
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/luvit-luvit

## The gap Luvit fills between plain Lua and a C event loop

Standard Lua ships no socket library, no event loop and no file watcher. Anything concurrent means either blocking calls, a C extension per capability, or a separate process. Luvit's answer is to take libuv, the same event loop library underneath Node.js, and expose it through a Lua API shaped like Node's. The README describes the repository as a metapackage: 'This collection of packages and modules implements a node.js style API for the luvi/lit runtime.' The intended reader is a Lua programmer who wants callbacks, streams and an HTTP server without writing bindings. The README's own framing is the 'Lua Inventor', which tells you the project assumes familiarity with Lua idioms rather than promising to teach them.

## How the luvi, lit and luvit layers stack

There are three moving parts and confusing them makes the rest of the documentation hard to read. luvi is the runtime binary that embeds LuaJIT and libuv. lit is the package manager and bundler that fetches dependencies and can produce a single executable. Luvit is the metapackage plus the luvit/* packages, published to lit, that provide the Node-style modules. Running the interpreter means running a luvi binary that has the Luvit packages bundled into it. The Makefile makes this concrete: the lit target downloads get-lit.sh from the luvit/lit repository at LIT_VERSION 3.8.5, and the luvit target then runs ./lit make to produce the binary. Because packages live in deps/ and are also mirrored in luvit/lit, the README warns maintainers that 'some are duplicated in luvit/lit to ease lit bootstrapping' and that updates must be pushed from either repo while keeping them in sync. That duplication is a real source of drift: a fix landed in one copy does not reach the other until someone pushes it.

## Building Luvit from source and running a first script

The README gives the clone-and-build path directly. It works on Linux, macOS and Windows through Makefile and make.bat, so the same three commands apply. The build fetches lit, which then fetches the dependency tree, so the first run needs network access.

```bash
git clone https://github.com/luvit/luvit.git
cd luvit
make
```

After make finishes, a luvit executable exists in the repository root alongside lit and luvi. The install target copies all three into $(PREFIX)/bin, which defaults to /usr/local, so the binaries end up at /usr/local/bin/luvit, /usr/local/bin/lit and /usr/local/bin/luvi unless you override PREFIX.

The repository ships runnable examples under examples/, including http-server.lua and fs-basic.lua. To run one without rebuilding after every edit, the README points at luvi directly:

```bash
luvi . -- tests/test-http.lua
```

The trailing dot is the Luvit source directory luvi should bundle, and everything after -- is passed to the script as arguments. Expect the HTTP test file to print its assertions and then exit. For an HTTP server, examples/http-server.lua is the closest starting point; the README does not document a default port for it, so read the file before assuming one.

## Native code and binary modules through FFI

Luvit supports FFI and Lua-based binary modules, and the README links a wiki entry titled 'Publishing Compiled Code' for bundling them into an application. This is the escape hatch when the pure-Lua packages do not cover a capability: you can call into a shared library from LuaJIT's FFI rather than writing a C module against the Lua C API. The trade-off is portability. An FFI binding is tied to the ABI of the library it loads, so a bundled application that depends on a system shared object is only as portable as that object. The README does not describe how binary modules are resolved at runtime for a bundled app, only that a wiki page covers publishing them; treat that page, not the README, as the reference.

## Where Luvit is the wrong choice

The release history is the first constraint. The newest release listed in the repository is 2.18.1 from 2021-11-19, and 2.18.0 carries the same timestamp, which suggests a packaging fix rather than a feature release. Meanwhile the default branch's last push was on 2026-04-02, so work continues on master without a tagged release for years. If your team needs versioned artifacts with a changelog you can cite in an audit, that gap matters more than any feature Luvit offers. The second constraint is the ecosystem. Luvit packages come from lit, not npm or LuaRocks, so a Lua library published only to LuaRocks is not directly installable. The third is documentation depth: the README covers building and hacking on core, and it points to a wiki and a Discord for everything else, but it does not document the module APIs themselves. You will be reading deps/ source to learn how a stream or an HTTP client behaves. If you want a Lua web framework with a stable public API and a release cadence, this is not it.

## Luvit against OpenResty and plain LuaSocket

The nearest alternative for Lua networking is OpenResty, which embeds LuaJIT inside nginx and exposes nginx's event loop through cosocket APIs. The difference in approach is architectural rather than cosmetic. OpenResty puts nginx at the centre: your Lua code runs inside request handlers, and the event loop belongs to nginx, which also handles TLS termination, static files and upstream proxying. Luvit puts the Lua process at the centre: libuv is the loop, and you build the server, the TLS layer and the routing yourself from the luvit/* packages. That makes Luvit better suited to long-running daemons, CLI tools and scripts that need sockets or signals, and OpenResty better suited to HTTP services that sit behind a proxy. A second alternative is plain LuaSocket, which gives blocking sockets with no event loop at all. LuaSocket is simpler and has no build step beyond LuaRocks, but it cannot multiplex connections, so it fails as soon as you need concurrency in one process.

## Licence, maintenance and what an upgrade actually costs

Luvit is Apache-2.0, and the repository carries both LICENSE.txt and NOTICE.txt. Apache-2.0 includes an explicit patent grant and requires that you preserve the NOTICE file contents when redistributing, which matters if you bundle the luvit binary into a product. That is a description of the licence text, not legal advice; check with your own counsel. On maintenance, the split between the last release and the last push is the thing to plan around. Because the binary you ship is produced by lit make from deps/, an upgrade means either bumping the lit package versions and rebuilding, or pulling a newer master and rebuilding. The README's own maintainer note says that to resync deps you can run rm -rf deps && lit install, which installs the latest version of every package from lit, and it warns to 'check the diff carefully to make sure you're not undoing any work'. That is the upgrade cost in one line: there is no dependency lockfile described in the README, so a rebuild can pull different package versions than the last one. If reproducibility matters, capture the deps/ tree you built from.

## Conclusion

Adopt Luvit if you already write Lua and need non-blocking sockets, filesystem streams or signals without leaving the language, and if you can pin a lit package version yourself. Do not adopt it if you need Node's npm dependency graph, a documented release schedule, or a project whose issue tracker shows recent activity; the newest release listed in the repository is 2.18.1 from 2021-11-19, while the default branch's last push was on 2026-04-02. Before committing, run make and make test from a clone to confirm the toolchain builds on your platform, then check the ChangeLog and the lit central database for the package versions your application would resolve to.

## FAQ

### How do I install Luvit?

Clone the repository and run make, which fetches lit at version 3.8.5 and then builds the luvit binary through ./lit make. Running make install copies luvit, lit and luvi into $(PREFIX)/bin, which defaults to /usr/local.

### How do I use Luvit to run a script?

Once built, the luvit executable runs Lua scripts, and the repository includes runnable examples such as examples/fs-basic.lua and examples/http-server.lua. During development you can skip rebuilding by running luvi . -- tests/test-http.lua, where the dot is the Luvit source directory and everything after -- goes to the script.

### What is Luvit?

Luvit is a metapackage and a set of luvit/* packages that implement a Node.js-style API for the luvi/lit runtime, built on libuv and LuaJIT. It can be used as a library or as a standalone executable.

## Sources

- [License: Apache-2.0](https://github.com/luvit/luvit/blob/master/LICENSE)
- [luvit/luvit on GitHub](https://github.com/luvit/luvit)
- [Project website](https://luvit.io/)
- [README](https://github.com/luvit/luvit/blob/master/README.md)
- [Releases](https://github.com/luvit/luvit/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/luvit-luvit
