How we built the Moot MVP with a no-code backend - Part 1

Sam Roberts

Sam Roberts

Co-founder, Moot

3rd October 2022

Moot - Make meetings engaging for your team

When building a new product, there’s many assumptions you bring to the table. It’s important to de-risk those assumptions relatively quickly and focus on the few things that are going to move the needle in terms of the value and experience the product provides to customers.

The backstory is that we started to build Moot around April 2022 (this year) and released the first MVP in late June. That’s three months. We aimed to keep the tech as simple as possible with the focus of being able to create core functionality that we could test with customers. As a result, 99% of our backend logic was handled (and still is handled) in Supabase - an open source Firebase alternative.

So let’s discuss some of the decisions we made, what the intentions were & whether we were successful or not.

Move quick & build momentum

Moot is a Next.js application hosted on Vercel. Right from the start we loved how fast we could ship our new features, without thinking about DevOps.

We ultimately opted for using libraries and frameworks that would give us the full power of flexible Javascript, and this meant a frontend stack of React, Tailwind & DaisyUI.

However, when it came to the point to introduce a backend (which was pretty soon after), we wanted the same flexibility there too. We wanted to keep things as simple as possible and not get too tied down to building an entire architecture ourselves.

Our first thought was initially Rails, which I had experience in, but soon after Sam figured Supabase would do everything we need to in those early months.

He was right. Supabase provides you with a REST API on top of a PostgresDB, which means that we don't even have to write dedicated backend code. And although we were skeptical that Supabase could handle all of our needs, we were soon convinced that it could handle enough logic so that we could validate our core video feature (built with peer-to-peer WebRTC), as well as store all the data associated with each company who would use Moot in its early form.

So now every time a wants to change or interact with team mates on Moot, we just call the API directly from our frontend using their javascript library. It’s also secure too. Not only were we able to build a functional app in little time, we are also able to restrict users to only retrieve and store data they have permissions for right from the supabase interface.

Focus on what matters.

Just like Firebase, Supabase have their own authentication feature. The difference is that it has almost no lock-in (at least compared to Firebase) & its also so much more broad in terms of its authentication options. So for us it was a no brainer, as instead of rebuilding an entire auth system or having to use a paid by default option, like Twilio, we’d let Supabase handle the authentication and we’d spend our energy on the product itself.

Authentication with Supabase is super easy to implement - just use the correct calls via the supabase-js library.

Here’s a screenshot of the Moot authentication screen: mvp-moot-authentication-screen

and here’s a snippet of the code we used to activate authentication and sign up and send verification emails to customers (with react-hot-toast to show the user the result of the request).

const signUp = async (payload: UserCredentials) => {
  try {
    setLoading(true)
    const { error } = await supabase.auth.signUp(payload)
    if (error) {
      toast.error(error.message)
    } else {
      toast.success(
        'Signup successful! Please check your inbox for a confirmation email.'
      )
    }
  } catch (error) {
    toast.error(error.error_description || error)
  } finally {
    setLoading(false)
  }
}

Initially we had some concerns to whether Supabase would provide the flexibility to make custom authentication processes. But it did great.

As a B2B SaaS, we’re selling Moot to leaders of teams and organisations, rather than individuals themselves. This means our invite system is important, as once an operator signs up they will need to invite their teams.

This was a perfect use case for the magic links that Supabase provides as a default authentication method, where we can create a conditional for the type of user to present them with a different onboarding flow.

Keeping things lean - Storage & state management

One of the most interesting aspects of Moot is our presence feature, and how it pairs with the persistence of the dashboard so that blocks maintain state between meetings. Here again Supabase provided the flexibility we needed to get product out the door.

The Storage of Supabase is built on top of AWS S3, which lets you store all type of files. Great for images, avatars or other dynamic content. Like with the authentication, it is a no-brainer to implement an image-upload. The images can either be available public, or only be retrieved with specific permissions.

Note: Images are stored in a bucket and are accessible through the supabase studio.

Image upload to Supabase is as simple as the following block of code:

const { data: uploadData, error: uploadError } = await supabase.storage
  .from('avatars')
  .upload(`images/${user.id}`, selectedFile, {
    cacheControl: '3600',
    upsert: true,
  })
const { publicURL, error: urlError } = supabase.storage
  .from('avatars')
  .getPublicUrl(`images/${user.id}`)

Beyond users and images, we also maintained persistence for each block so that teams could use Moot as a high level point of reference both in and out of meetings. To do this

We also made each block sync in real time through yjs (a CRDT library)- which gives us a model for sharing types to make Moot collaborative. Some other libraries work well with yjs, but even for libraries that don’t directly integrate, we were able to use the shared data types from yjs to sync data between users, and also even in an offline state. This allows live collaboration, as well as the ability to merge and avoid conflicts when you work offline and return online.

From the Moot canvas block, here’s an example of how we’re able to store the shapes and bindings as maps in tldraw, which keeps them in-sync between users:

const yShapes: Y.Map<TDShape> = useMemo(() => {
  return doc.getMap('shapes')
}, [])
const yBindings: Y.Map<TDBinding> = useMemo(() => {
  return doc.getMap('bindings')
}, [])

For the blocks, we serialize them all to simple text for storage in supabase. This includes their position, relevant data, and various options. Some of this gets updated quite frequently, like the text and whiteboard widgets where the updates are pushed whenever a user makes a change (and synced between users using yjs). Some others are more static, like the bookmarks, which are essentially urls stored in an object.

Here’s what we’d do if we wanted to fetch the block data from the DB:

useEffect(() => {
  const fetchData = async () => {
    const { data, error } = await supabase
      .from('widget_data')
      .select('*')
      .eq('id', id)
    if (error) {
      return console.error(error)
    }
    setData(data[0].content)
  }
  fetchData()
}, [id])

const updateData = async (data) => {
  const { data: newData, error } = await supabase
    .from('widget_data')
    .update({ content: { data } })
    .eq('id', id)
  if (error) {
    return console.error(error)
  }
  return newData
}

And here’s a screenshot of how that looked on the MVP of the Moot dashboard: mvp-the-moot-dashboard

We also took advantage of yjs to build our own bespoke collaborative blocks, as well as other open source packages (like tiptap) for our blocks so that nothing required a dedicated backend architecture. Beautiful!

Scaling beyond Supabase

Honest, Supabase was simply to be a choice we made to move fast and get product out the door. We imagined as soon as we had any sort of substantial product that we wouldn’t need it anymore. However, we’re still going strong! Paired with services like Vercel we can basically skip so much of the leg work of Backend development & DevOps that the vast majority of our time is spent on product development.

Supabase are also going from strength to strength with new features. We are already planning to swap out our own multiplayer cursor feature with their in-built solutions around presence & because the Supabase DB architecture is built on Postgres, we don’t feel the pressure of lock-in that most services artificially manufacture. It’s also open source.

Conclusion

We’ve only been working on Moot for a few months, as a small two person team, yet we’ve built a comprehensive dashboard and video tool with persistent state across company accounts, and our different blocks (whiteboards, notes, bulletins etc). As a result we have customers which are using our product because it gives them something comprehensive they can’t get anywhere else.

Best of all? It’s all on the free plan of Supabase.

As someone who has built out an entire backend logic before, this has been awesome. Simply because it means that the majority of our dev time is spent on our own product ideas & responding to feedback. Not reinventing the wheel rebuilding the same logic as most teams have been forced to do.

Ultimately, Supabase takes care of many key aspects like authentication, account verification, file storage and data storage. It even has support for serverless functions for more sophisticated backend logic. We fully recommend building the foundations of your next product on Supabase if you think it might make sense. You can check it out here!

If you have any questions regarding our implementation or anything mentioned in this post, don't hesitate to reach out to James or myself on Twitter. We’re building Moot to give companies a real space to collaborate and make remote engaging. We’ve got more and more operators getting in touch to use Moot every day, and we’re defining the category of the multiplayer work hub.

Part 2 (how we built Moot video with a no code backend) coming up next!


Moot is the all-in-one workspace for remote teams. Initially Moot substitutes for quick, human fuelled, real time collaboration. As a multiplayer work hub, we’re building block widgets for everything you can imagine. Brainstorming, note taking, face-to-face calling, chat, celebrations and more. A fully customisable, plug and play system to build your remote org, exactly how it needs to be. All with a native video experience.

Does that sound exciting? If so, get in touch, book a demo, partner with us. Whether remote, hybrid or async, we're building the infrastructure for the future of work & we want your company to be part of the journey.

Get started with Moot in 2 minutes

Free to start. Instant space. No sign-up required.