Module Accessors
Three patterns for accessing RoExpress modules. As of v2.5, all three are fully typed — Luau infers method lists and return types without any manual annotation.
1 | Call form RoExpress("ModuleName")
The primary way to get a constructed module instance. The root module is callable via a typed __call overload — Luau resolves the correct return type from the string literal you pass.
-- Server script
local RoExpress = require(game.ReplicatedStorage.RoExpress)
local app = RoExpress("App") -- RoExpress.App
local broadcast = RoExpress("Broadcast") -- RoExpress.Broadcast
local maid = RoExpress("Maid") -- RoExpress.Maid (fresh instance)
-- Client script
local network = RoExpress("Network") -- RoExpress.Network
local listener = RoExpress("Listener") -- RoExpress.Listener
Autocomplete suggests every valid module name as soon as you open the string. The return type narrows to the correct type for that module — RoExpress("App") returns RoExpress.App, RoExpress("Network") returns RoExpress.Network, and so on. All methods and their parameters are available immediately.
local app = RoExpress("App")
app:Get("player/:id=number", function(req, res)
-- req and res are both fully typed — no annotation needed
res:Send({ userId = req.params.id })
end)
RoExpress("App") called from two different scripts returns the exact same instance. Modules are only constructed once.
2 | Property access RoExpress.ModuleName
Used for utility modules and singleton references that are not constructed — the property is the module table itself. These are lazily loaded on first access and cached immediately.
-- Both contexts
local Bridge = RoExpress.Bridge -- event bus singleton
local Hook = RoExpress.Hook -- Roblox signal hooks
local Debounce = RoExpress.Debounce -- cooldown wrappers
local Codec = RoExpress.Codec -- compression
local Base64 = RoExpress.Base64 -- encode / decode
local Promise = RoExpress.Promise -- async utilities
local TypeCoercer = RoExpress.TypeCoercer -- type serialisation
-- Server only
local RTTP = RoExpress.RTTP -- outbound HTTP factory
local Tamper = RoExpress.Tamper -- exploit detection
local Cross = RoExpress.Cross -- cross-server messaging
RTTP is a factory — access it as a property, then call .New(config) to create an instance:
local RTTP = RoExpress.RTTP
local webhook = RTTP.New({ base = WEBHOOK_URL, retries = 3 })
Version and Types are loaded eagerly on require.
3 | Named port RoExpress("Network", "portName")
Client-only. Passing a second argument returns a Network or Listener wired to that port's dedicated RemoteEvent rather than the main channel. The port must already exist on the server via app:Listen().
-- Client: connect to the "combat" port
local combatNet = RoExpress("Network", "combat") -- RoExpress.Network
local combatListener = RoExpress("Listener", "combat") -- RoExpress.Listener
combatNet:Post("shoot/:targetId=number", nil, function(res)
showHit(res.data)
end)
combatListener:On("hit.confirmed", function(data)
print(data.damage)
end)
-- Server: create the port (not via accessor)
local app = RoExpress("App")
app:Listen("combat", function(port)
port:Post("shoot/:targetId=number", function(req, res)
res:Send(handleShoot(req.player, req.params.targetId))
end)
end)
Port instances are also cached by "ModuleName:portName" key.
Full module reference
| Module | Context | Access | Return type |
|---|---|---|---|
| Server | |||
| App | Server | RoExpress("App") | RoExpress.App |
| Broadcast | Server | RoExpress("Broadcast") | RoExpress.Broadcast |
| Tamper | Server | RoExpress.Tamper | RoExpress.Tamper singleton |
| RTTP | Server | RoExpress.RTTP | Factory — call .New(config) |
| Cross | Server | RoExpress.Cross | RoExpress.Cross singleton |
| Port | Server | app:Listen("name", fn) | RoExpress.Port — via App only |
| Client | |||
| Network | Client | RoExpress("Network") | RoExpress.Network |
| Listener | Client | RoExpress("Listener") | RoExpress.Listener |
| Benchmark | Client | RoExpress("Benchmark") | any |
| Named ports (client only) | |||
| Network on port | Client | RoExpress("Network", "name") | RoExpress.Network |
| Listener on port | Client | RoExpress("Listener", "name") | RoExpress.Listener |
| Both contexts | |||
| Bridge | Both | RoExpress.Bridge | RoExpress.Bridge singleton |
| Hook | Both | RoExpress.Hook | RoExpress.Hook singleton |
| Maid | Both | RoExpress("Maid") | RoExpress.Maid — fresh instance each call |
| Debounce | Both | RoExpress.Debounce | RoExpress.Debounce utility |
| Stream | Both | RoExpress("Stream") | any |
| Codec | Both | RoExpress.Codec | any |
| Base64 | Both | RoExpress.Base64 | any |
| TypeCoercer | Both | RoExpress.TypeCoercer | any |
| Promise | Both | RoExpress.Promise | RoExpress.Promise factory |
How the typing works
The root RoExpress value is typed as an intersection of a property table and an overloaded callable type:
-- Simplified view of what Luau sees after require()
type RoExpressRoot = {
Bridge : RoExpress.Bridge,
Hook : RoExpress.Hook,
Debounce : RoExpress.Debounce,
-- ... all property-access modules
} & (("App") -> RoExpress.App)
& (("Network") -> RoExpress.Network)
& (("Broadcast") -> RoExpress.Broadcast)
& (("Maid") -> RoExpress.Maid)
-- ... all callable modules
Luau resolves the string literal you pass against the overload list and narrows the return type. Autocomplete for the string argument shows all valid module names. Autocomplete on the returned value shows all methods for that module.
Wrong context errors
Calling a server-only module on the client (or vice versa) throws immediately with a clear message. You will never get a silent nil.
-- In a LocalScript:
local app = RoExpress("App")
-- Error: [RoExpress] 'App' is server-only
See also
App · Network · Maid · Types | all exported Luau types · Request Pipeline