Connecting Clients Directly
In every design so far, a server sits between the client and the database. In our design, it is our API. Real Courselore’s server sends finished HTML pages instead, but it sits in the same place. Why not let the browser talk to the database itself?
- A browser cannot connect to most databases. PostgreSQL and MongoDB, for example, use their own protocols, and a browser can only send HTTP requests and open WebSockets.
- The client would hold the database’s password. Anyone could read it out of the page and use it.
- The database’s permissions are not built for this. They are meant for a few trusted programs, not for thousands of users who may each see different rows.
So the server holds the password and decides what each user may see.
Some services are built for the client to connect directly. Firebase and Supabase both are. They accept HTTP requests and WebSockets from browsers, they sign in each user, and they check each request against rules stored with the data. They are also real-time databases.
Chat in Supabase, from the browser
Here is the code in Marcus’s browser that shows the latest 50 messages in Team 4’s room, room 12, and then receives every new one:
const { data: messages } = await supabase
.from("chat_messages")
.select("*")
.eq("room_id", 12)
.order("message_id", { ascending: false })
.limit(50);
showMessages(messages);
supabase
.channel("room-12")
.on(
"postgres_changes",
{
event: "INSERT",
schema: "public",
table: "chat_messages",
filter: "room_id=eq.12",
},
(change) => showMessage(change.new)
)
.subscribe();
The first part is a query on chat_messages: the rows of room 12, newest first, at most 50. It is the same query our server runs for GET /rooms/12/messages?limit=50, but the browser sends it to Supabase directly. The second part is the subscription from the previous section, now sent by the browser and limited to room 12. To post, Priya’s client inserts a row into chat_messages through the same library.
Compare this with what we built: the REST API, a WebSocket at /courses/7/live, a set of connections for each room, the pings, and the catch-up with after. With Supabase, we write none of it. The library keeps a WebSocket open to Supabase’s servers and receives the new rows on it.
Who checks what Marcus may read
With the browser talking to the database directly, our server can no longer check whether Marcus may read a room. That check moves into the database itself, as rules stored with the data. Supabase uses PostgreSQL’s row level security: a rule on chat_messages lets a user read a row only if the user is a member of the row’s room. Firestore has the same idea, called Security Rules.
Backend as a service
Supabase and Firebase give us more than a database. The vendor runs the database on its own servers, and it also provides authentication and the library the client uses. A service like this is called a backend as a service.
It is different from a database as a service, such as Amazon RDS or MongoDB Atlas. There, the vendor runs only the database. Our server connects to it instead of to a database on our own machine, and our API still sits between the client and the database.