In our previous article, we spoke about setting up realtime editor using TipTap and Hocuspocus which is stateless. This means we don’t save the contents to database or load an data from database, in most cases we’ve to be doing this in order to persist the written content. We use realtime editors for collaborating with our team on a changelog , when writing help articles, or even writing a product requirements document on featureOS.
In this article, I will walkthrough the steps to do the same. Luckily Tiptap with its HocusPocus plugin provides it out of the box and comes quite flexible too.
Provisioning the database
For this demo, I have created a MySQL database using Planetscale and connecting to it with Prisma ORM
Install Prisma and Setup
Run the following commands from the root of our project.
Once you run these commands the Prisma will get install and creates a schema file for us under prisma/schema.prisma
Now in your .env
file (create if its not present) paste your database connection url that you obtain from your database provider DATABASE_URL
Now, lets create our schema, we need a table called documents
in which we store the document id and content. This can be different for each application, just for our demo we’re creating the most basic ones. Update the following in your prisma schema file
After you update, we need to publish the migration to our database, we use prisma db push
for it
Once the changes are migrated, our db is good to go, now we need to create a Prisma client to make the calls. Create a new file under prisma/prismaClient.ts
with the following contents
Now we’re ready to setup our web socket server to handle the storage and retrieval or document part.
Server Side Setup
We need to configure the following two functions in our hocuspocus web socket server.
These two functions primary does the storing and retrieval of data from our desired backend store.
Lets modify our code little, in order to keep things clear I will create a new file called documentHelpers.js
in which I will write these to functions
And will import these two in our main file server/hocuspocus-server.js
With the basic structure ready, now lets add the functionality for these two functions.
onStoreDocument
The onStoreDocument function will be called every time the document is updated in the client side to store in the data store of our preference.
HocusPocus will give us the document content in binary format which we need to convert to base64 and store in our db, we use js-base64
library for it
In order to make the understanding simpler, the list of things you need to do in onStoreDocument are
- Get the document and documentName
- Validate the documentName if required
- Encode the document to Y js Update format
- Converted the encoded binary data to base64
- Store the base64 in your database
Here is the sample code for the same with Prisma and MySQL DB in documentHelpers.js
With this you’ll be able to store the document in your database for every update from all the connected clients. But in order to reduce frequent calling of onStoreDocument, we can use debounce to differ it. To do that just update our server/hocuspocus-server.js
with the following configuration
We’ve solved one piece of the puzzle, which is storing the data to our store. But the bigger part is to load from the store when a user is trying to load the content in the client side.
onLoadDocument
The onLoadDocument
function handles the extraction and sending the document format to our clients when a new editor is connected to a specific document.
Similar to storing part, I will first summarise the step to do before the code,
- Get the document name from the incoming data
- Find the base64 content in DB and extract it
- Convert the base64 content back to unit8Array
- Apply an Y js update with incoming document
- Return the document
So lets code these steps in onLoadDocument
, import toUint8Array
function from js-base64
and add the below code to server/documentHelpers.js
file
And we’re done with the server setup. We now need to do some changes to our client part to do the following
- Create dynamic documents based on the URL
- Load data from websocket server.
In order to create dyanmic path, I will move the /editor
endpoint to /editor/[id]
so that we can randomly create new realtime documents
In the new [id].tsx
page get the id
from the router query and send it to RichTextEditor
as documentName
And in the RichTextEditor lets receive the documentName
from the props and initiate the provider with it.
That’s all! Now you have a realtime editor which can store and retrieve data from your own datastore.
In the next article, I’ll talk more about the permission setup for the documents with onAuthenticate
function of hocuspocus server.