RoExpress | the complete networking stack for Roblox
The first Roblox framework that covers every layer of networking in one place: inbound routing with typed params and middleware, reliable server push, tick-rate binary streaming, outbound HTTP to external APIs, built-in exploit detection, per-player rate limiting, and Deflate compression. Stop patching together RemoteEvents. Start with a stack that is already complete.
GetApp() / GetNetwork() accessors.Module tree
Installation
The fastest way is the Studio command bar | paste one line and you're done. Full install guide →
local H=game:GetService"HttpService";loadstring(H:GetAsync"https://raw.githubusercontent.com/unofficialrobloxtutor/RoExpress/main/install.lua")()
Or with Wally:
[dependencies]
RoExpress = "unofficialrobloxtutor/roexpress@2.4.0"
wally install
Quick start
Server
local RoExpress = require(game.ReplicatedStorage.RoExpress)
local app = RoExpress.GetApp()
-- global middleware | runs before every handler
app:Use("logger", function(Player, Payload)
print(Player.Name, Payload.method, Payload.route)
end)
-- typed :userId param auto-converted to number
app:Get("player/:userId=number", function(req, res)
res:Send({ userId = req.params.userId })
end)
app:Put("player/:userId=number/name", function(req, res)
res:Status(200):Send()
end)
-- push to all connected clients reliably
app:PushAll("round.end", { winner = "PlayerName" })
Client
local RoExpress = require(game.ReplicatedStorage.RoExpress)
local network = RoExpress.GetNetwork()
local listener = RoExpress("Listener")
-- callback style
network:Get("player/123", nil, function(res)
print(res.data.userId)
end)
-- promise style (v2.2+)
network:GetAsync("player/123")
:Then(function(res) return res.data end)
:Catch(function(err) warn(err.message) end)
-- reliable push subscription
listener:On("round.end", function(data)
print("Winner:", data.winner)
end)
Context accessors
There are three access forms. Use typed accessors for App and Network | Luau infers the full type automatically so autocomplete and type checking work without an annotation. The call form (RoExpress("Name")) returns any. Property access (RoExpress.Name) is used for utility modules that don't need instantiation.
| Call | Context | Returns |
|---|---|---|
| Typed accessors | use these | ||
RoExpress.GetApp() | Server | App | fully typed, autocomplete works |
RoExpress.GetNetwork() | Client | Network | fully typed, autocomplete works |
| Named ports | client side | ||
RoExpress("Network", "portName") | Client | Network wired to the named port's RemoteEvent |
RoExpress("Listener", "portName") | Client | Listener wired to the named port's RemoteEvent |
| Other modules | ||
RoExpress("Broadcast") | Server | Broadcast instance (returns any) |
RoExpress("Listener") | Client | Listener wired to the main channel |
RoExpress.Stream | Both | Stream factory |
RoExpress.Bridge | Both | Bridge singleton |
RoExpress.Codec | Both | Codec module |
RoExpress.Harpy | Server | Harpy factory (call .New(config)) |
RoExpress.Tamper | Server | Tamper singleton |
Calling a server-only module on the client (or vice versa) throws an assertion with the context name. Port names on the server side are ignored | ports are created via app:Listen(), not via the accessor.
Request pipeline
Every incoming request passes through the same stages in order:
| # | Stage | Rejects with |
|---|---|---|
| 1 | Version check | Tamper discards packets with a mismatched framework version | silent drop |
| 2 | TokenBucket | per-player rate limiter consumes one token | 429 |
| 3 | Payload validation | method, route, and body shape checked | 400 / Tamper strike |
| 4 | Global middleware | each registered handler runs in order; return false stops the chain | 403 |
| 5 | Route match | Router finds the first matching pattern and extracts typed params | 404 |
| 6 | Route middleware | any :Use scoped to this route | 403 |
| 7 | Handler | your code runs; res:Send() or res:Error() fires the response | 500 on unhandled error |
Version history
| Version | Status | What shipped |
|---|---|---|
| 2.4.0 | current | Deflate compression · Automatic retry · Stream generic channels · Typed accessors |
| 2.3.0 | shipped | PUT & DELETE · Compact handler args · Codec LZ77 upgrade · TypeCoercer: UDim, Rect, UDim2 |
| 2.2.3 | shipped | Issue #6 fix | listener:Once race condition under rapid events |
| 2.2.0 | shipped | TypeCoercer · Promise · Server Push · Tamper · TokenBucket Grant |
| 2.1.0 | shipped | Stream v2 · Named Ports · Benchmark · Bridge.BindOnce |
| 2.0.0 | shipped | Full rewrite | App, Router, Network, Listener, Bridge, Codec, Broadcast |
Next: YouTube gun framework showcase, then awesome-roblox submission. See the Roadmap for the full picture.
Explore the docs
A random selection of guides, references, and examples — shuffles on every visit.