Only show guides for your app
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 |
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:
for loop with no I/O freezes everything, because there's one thread. Break heavy work up or hand it to a timer.app.lua sets things up and then the loop keeps the process alive.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.
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.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, bind0.0.0.0.
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:
require can find them, or bundle them in with your project.⚠️ 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.