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
forloop 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.luasets 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_PORTis your public port, injected by Falix.tonumberturns the string into a number forlisten.'0.0.0.0'binds every interface — the difference between reachable and not. Bindinglocalhostis the classic "runs but can't be reached" mistake; see Your first web app.- The
printline 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, bind0.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
requirecan 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.