Tuesday, July 26, 2016

Make Your Own Firebase

You read right. We're going to be duplicating what I think is the best part of Firebase (before Google made everything the coolest part): the real time database. This is really neat because essentially every client will have the same copy of an object in the database, essentially removing the need to keep asking the server for updates. We're going to be copying the very basics of this behavior, but it can be extended way beyond what we're doing here.

We're going to use NodeJS as our server, Socket.io as our real time delivery channel, and SQLite as our data storage backend. This difference in storage engine is very different to that of Firebase. Firebase uses a JSON structure, so we're going to have to rethink some things to make this work for a relational database like SQLite.

First and foremost, we don't have paths to objects. We don't even have objects. We have rows. I guess we can abstract those to objects though. The most simple paradigm any database can have is a simple key-value store. So that's what we're going to make. We'll make both of these fields arbitrarily large (text fields) so you can store whatever you want in them. Keys can be paths and values can be stringified JSON. The only downside is if you change one part of your JSON object, the whole object gets broadcasted to those who are listening to it.

So here is what's happening. For our server, the whole database will be kept in memory, or you can choose to write it to disk, whichever you prefer. It will support the basic CRUD operations (create, retrieve, update, delete), and allow any client to subscribe to an object so it can react to updates. And, just for fun, we can broadcast any javascript entity (other than functions) to everybody else. That might come in handy at some point.

I'll break most of the server down here. We'll ignore the first 18 lines because it just sets up the HTTP server and loads dependencies. The next two lines are important, though:

var db = new sqlite.Database(":memory:");
db.run("CREATE TABLE data (key text primary key, value text)");

This sets up a database in memory and creates the key value table. You can change the memory string to a file path and it'll write stuff to disk so it's persistent. Next, we namespace the socket so that you could potentially have multiple databases or tables with access control (out of this project's scope):

var datasocket = io.of("/data");

Next, we define the behavior of the sockets. I won't quote the code here but I'll run through quickly what it does.

updateObject is the first thing we see. If the object exists and is different from the value we want to save to it, we write it to the database. We then take the data and broadcast it to anybody who's subscribed to that socket.io room. Rooms are named after keys.

fetchObject gets the object by key requested from a client and returns that object only to the socket that sent the request. It does this by sending it to a room with the socket's id (socket.io makes a room for each socket individually on connection).

createObject creates an object in the database if it doesn't exist and broadcasts it to everybody connected on the namespace. It's up to the clients if they want to subscribe to the object.

deleteObject removes the object from memory and broadcasts that event to anybody subscribed to that object at delete time.

subscribe and unsubscribe joins or leaves a room with the supplied object key as the id. This is how a client gets updates about another object. You don't have to be subscribed to an object to update or delete it.

broadcast simply takes an object and sends it to everybody else connected on the namespace. It's more of a convenience, really.

The client-side code is basically exactly the same in terms of what the functions do, so take a look at that source and see how it works and how to hook into certain events.

You can find everything on my GitHub repo. I hope you learned something from this project, because I certainly did!

No comments:

Post a Comment