Luvit in depth

The Lua application runs Luvit — Lua on libuv, the event loop behind Node. Here's how its async model, timers, and HTTP server work, a server that binds SERVER_PORT, and an honest look at the ecosystem.

Falix's Lua application doesn't run plain Lua — it runs Luvit, which is Lua bolted onto libuv, the same event loop that powers Node.js. That single fact explains almost everything about writing for it: it's asynchronous and event-driven, not the synchronous Lua you might know from game modding. This guide covers Luvit's event loop, timers, and HTTP server, and is honest about where the ecosystem stands.

At a glance
You need A Falix server running the Lua application
Plan Any — on free it runs while your session timer has time left, premium runs 24/7
Time Twenty minutes
New to the Lua app? C#, Deno, Dart, Elixir, and Lua on Falix

The event loop

Falix starts your program with ./luvit app.lua (the entry file is the Lua file variable, default app.lua). Luvit then runs an event loop: a single thread that never blocks on I/O. Instead of waiting for a network read or a timer, you register a callback and Luvit calls it when the event is ready. If you've written Node.js, this is exactly that model, in Lua.

The practical consequences:

  • Don't block the loop. A long, tight for loop with no I/O freezes everything, because there's one thread. Break heavy work up or hand it to a timer.
  • Work happens in callbacks. Network handlers, timers, and file reads all call you back later — your app.lua sets things up and then the loop keeps the process alive.
  • The process stays up as long as there's something to wait for — an open server or a repeating timer. A script that just runs top-to-bottom and registers nothing will exit, and the server will show as stopped.

Timers

Timers are the simplest way to see the loop in action. They live in the timer module:

local timer = require('timer')

-- run once, after 2 seconds
timer.setTimeout(2000, function ()
  print('two seconds later')
end)

-- run every 5 seconds
timer.setInterval(5000, function ()
  print('tick at ' .. os.date('%H:%M:%S'))
end)

Delays are in milliseconds. setInterval keeps the process alive on its own, which makes it handy for a heartbeat or a polling worker.

An HTTP server

Luvit ships an http module modelled on Node's. Here's a complete app.lua that serves HTTP and — following the universal Falix rule — reads SERVER_PORT and binds all interfaces:

local http = require('http')

local port = tonumber(process.env.SERVER_PORT) or 8080

http.createServer(function (req, res)
  res:setHeader('Content-Type', 'text/plain')
  res:finish('Hello from Luvit on Falix!\n')
end):listen(port, '0.0.0.0')

print('Listening on port ' .. port)
  • process.env.SERVER_PORT is your public port, injected by Falix. tonumber turns the string into a number for listen.
  • '0.0.0.0' binds every interface — the difference between reachable and not. Binding localhost is the classic "runs but can't be reached" mistake; see Your first web app.
  • The print line doubles as your success signal in the Console.

🎯 Good to know: These are the same port rules every language on Falix follows. Nothing about Luvit changes them — read SERVER_PORT, bind 0.0.0.0.

Dependencies and the ecosystem — honestly

Luvit has its own package manager, lit, and a runtime bundler, luvi, used to fetch libraries and build single-file executables. Worth knowing where things stand on Falix:

  • There's no Packages page for the Lua application. Keep any libraries you use alongside your code in the server folder so require can find them, or bundle them in with your project.
  • The ecosystem is small and quiet. Luvit is a niche runtime with a modest community and far fewer libraries than Node, Python, or even plain Lua/LuaJIT. It's actively usable and genuinely pleasant for lightweight async Lua services, but you'll lean on the official docs rather than a big tutorial ecosystem, and some problems you'd solve with a library elsewhere you'll write yourself here.

⚠️ Heads up: Code written for standard Lua or LuaJIT often won't run unchanged on Luvit. Anything that does blocking I/O — synchronous file or socket reads — expects Luvit's async APIs instead. Check that your entry file name matches the Lua file variable, too, or nothing starts.

If Luvit's async model is what you want, the official site at luvit.io documents the modules and lit. If you find the ecosystem too thin for your project, choosing a language can point you at a runtime with more support.


Next steps

Was this guide helpful?