Client/Server Module Bridge | NOT a networking module

Hello world!

This module allows you to make module scripts that usually would only work on the client or the server, work for both the client and the server. For example, it can be used to make a Datastore module (GetData, UpdateData, etc) be accessible from the client
This is not some kind of unfiltered enabled though, it is perfectly safe, mainly replacing remotes you’d use to get the data from the server, or to update it

This module aims to simplify Client/Server communication. By using this module instead of remote, the client (or server) doesn’t have to use a remote, the client can use the module script as the server would, creating a cohesive structure for the server and the client. The client will also retain autocomplete, unlike a remote
No more hidden connections to remotes scattered across multiple scripts

The way it works is simple. The methods that are marked as being for the server will be overwritten to a function that sends whatever the client gives it, to the server, and calls that method, from the server. In the case of a “RemoteFunction”, the result is sent back to the client. It works the same way for server → client.
It also has a little system for sanity checks. While it is not needed to use it (ie, it can be done in the method itself), it encourages the use of sanity checks

Example code

local ClientServerModuleBridge = require(game.ReplicatedStorage.ClientServerModuleBridge)

local ServerModule = ClientServerModuleBridge:InitializeServerModule(script)
local ClientModule = ClientServerModuleBridge:InitializeClientModule(script)

local TestModule = {}

-- // Server examples // --

local function SanityChecks(Player : Player, Key, Data)
	if type(Key) ~= "string" then return end -- When nil or false is returned, that means the sanity check failed, and UpdatePlayerData wont be called
	return true
end

function TestModule:UpdatePlayerData(Player : Player, Key, Data)
	-- Update the data into the datastore or something
	print(Player, Key, Data)
end

ServerModule:SetRemoteEvent(TestModule, TestModule.UpdatePlayerData, true, SanityChecks)

function TestModule:GetPlayerData(Player : Player, Key : string)
	local SaveData = {"Hello World"}
	-- Do datastore stuff or something
	return SaveData
end

ServerModule:SetRemoteFunction(TestModule, TestModule.GetPlayerData, true) -- SanityCheckFunction is optional

-- // Client examples // --

function TestModule:PromptSaveSucceded(Player : Player, Message)
	-- Show the player a ScreenGui with message
	-- It is probably a good idea to do this in a seperate PromptModule, but this is to showcase that some functions can be clientsided, while others are serversided
end

ClientModule:SetRemoteEvent(TestModule, TestModule.GetClientData, true)

return TestModule

How to use

First up, you need to make either a ClientModule, a ServerModule, or both. You must pass the ModuleScript, and there are two optional arguments, a RemoteEvent and a RemoteFunction, if you want to specify which ones to use, if you don’t want it to automatically make them under the ModuleScript
(The first argument can actually be anything, it’s where the remotes will be parented)
If you specify a RemoteEvent and a RemoteFunction, it MUST be the same for the client and the server

local ClientServerModuleBridge = require(game.ReplicatedStorage.ClientServerModuleBridge)

local ServerModule = ClientServerModuleBridge:InitializeServerModule(script)
local ClientModule = ClientServerModuleBridge:InitializeClientModule(script)

ServerModule is for creating server sided methods or functions. When the client will call the method, a remote will be used behind the scenes
ClientModule is for client sided methods, so the server will use a remote

Next up, you need a function, in this case, I’ll take the example of a Datastore method, though, just a mimic

function TestModule:GetPlayerData(Player : Player, Key : string)
	local SaveData = {"Hello World"}
	-- Do datastore stuff or something
	return SaveData
end

One thing that is very important, every method that will use the Client/Server Bridge Module must have the Player as the first argument, even in cases where it isn’t useful (ie, calling the method above from the client. You can actually send anything for the player argument in this case, anything other than nil)

Now, you can use one of two methods, :SetRemoteEvent() or :SetRemoteFunction(). SetRemoteEvent is the equivalent of a RemoteEvent, SetRemoteFunction is the equivalent of a RemoteFunction, for getting data back

function TestModule:GetPlayerData(Player : Player, Key : string)
	local SaveData = {"Hello World"}
	-- Do datastore stuff or something
	return SaveData
end

ServerModule:SetRemoteFunction(TestModule, TestModule.GetPlayerData, true, SanityChecks) -- SanityCheckFunction is optional

The first argument is the table, and the second one is the function

The third argument is a boolean, true if self is passed to the function self, false if not. Set this to true for the : notation, and false for the . notation. (Otherwise, if you know self is, you know what to do)

The fourth argument is optional, it is a function, for sanity checks. If the function returns nil or false, the sanity check failed. If true or any other value is returned, the sanity check succeeded

local function SanityChecks(Player : Player, Key, Data)
	if type(Key) ~= "string" then return end -- When nil or false is returned, that means the sanity check failed, and UpdatePlayerData wont be called
	return true
end

This is pretty much it :P

Using this module involves putting pretty much every module script inside of replicated storage, so it isn’t ideal for games that already use remotes extensively, that would require a bit of rearranging…

Files & model

ClientServerModuleBridge.lua (13.3 KB) ← Not valid lua, it should be luau, but the forum doesn’t like luau…
TestModule.lua (1.4 KB)

ClientServerModuleBridge.rbxm (3.1 KB)
TestModule.rbxm (1.4 KB)

https://create.roblox.com/store/asset/17277377848

Final thoughts

I tried as much as possible to give this module a cohesive structure. It was a bit challenging given the task it does, one example of that are the arguments for SetRemoteEvent/SetRemoteFunction, the second argument is the function, rather than being the index of the function, which was done purely for usability. Doing it this way involves getting the index by searching for the function in the table, not ideal

I also kind of just realized that there is no way to detect if the function is running from the server, or from a request from the client. I was thinking, ohh you can just use RunService:IsClient(), but no duh… I’ll probably add a variable or something to fix this. Not sure what would be the best way though…?
The sanity check function could be used for that, with some imagination, the sanity check function and the module function both run in the same thread, so coroutine.running() could be used to detect if the sanity check function was ran during the same thread

7 Likes

What is the difference between this and Knit Framework? Dont both do the same thing (make server/client communication easier)?

I didn’t know how Knit worked until I checked for this reply

One big difference is that his module isn’t a framework, and is actually quite simple

Another difference is that, inside your module script, there is no Client table, for the better or for worse. I designed this to basically add to (or modify) a module script, rather than having to structure it in a specific way. The client and the server have access to the same functions

1 Like

Isn’t this exactly like how Knit framework runs?

Knit seems to do something similar, but this works differently, and this is not a framework. This is a 262 lines long module script
I explained the differences in the reply just above

So, just trying to understand this rq - you can access the functions in the module from either a local or server script, just the way a module script works, but without using remote events?

Yeah, it allows you to basically use it, as if it was a module script with functions that work on both the client and the server, but for functions that work only on the server or the client

So, for example, you can have functions that handle Datastore stuff (on the server). Using ClientServerModuleBridge, you can allow the client to call these functions. These functions will never be ran on the client, instead it is rerouted to a remote, and called on the server, and sent back if using SetRemoteFunction()

The module basically replaces the function with another one (on the client, for the example above) that instead sends a remote, … . So it seamlessly transfers the arguments to the server and the result back (again, for SetRemoteFunction())


It still uses RemoteEvents and RemoteFunctions, they get parented to the module by default. You just don’t interact with them direcly

1 Like

Not bad functionality in general, however I still feel like most people will use Knit rather than this module as it has all of this functionality + more, along with having a lot more organization and such.

I’ll def test it out and try to use it later tho, but yeah that’s my thoughts on it

1 Like

Also, question - does this work with remote functions as of now? Or is that on your list of updates for later?

It does. You can use :SetRemoteFunction(), for remote functions, or :SetRemoteEvent(), for remote events (that is, on ServerModule, or ClientModule)

1 Like

did you know that;

Zap exists
Red exists
ByteNet exists
Warp exists
Knit exists

1 Like

I say that it’s a “Networking Module”, but that isn’t the goal of the module. The goal of the module is providing a neat system for making module scripts work on both the server and the client, which by extension kind of ends up being a different way to do networking

Don’t use this for efficient networking or whatever, it’s not built for that

I changed the title to NOT a networking module

Is it clear now that this is not comparable to knit