SupaVlog: Vlog Application Starter Kit Built with Supabase, Stream, Hookdeck, and Next.js
Social is becoming dominated by video. TikTok, Instagram, YouTube + YouTube Shorts, Facebook Video + Reels, the list goes on. Increased remote work has significantly increased the use of video for communicating either live or via asynchronous recordings. Yet, the ability to quickly create your own video-based application - and most definitely one that scales - is harder than you might think.
So, I set out to create a vlog (video blog) starter kit to make it easier for others to spin up their own video blog. And I called it SupaVlog.
In this article, I'll cover the system architecture, components, component interaction, and key technical details of SupaVlog, an event-driven application built with Supabase, Stream, Hookdeck, and Next.js, that is easily deployable to Vercel. I'll also cover my thought process behind the choices I made.
The features of the SupaVlog starter kit are:
- Sign up with an email, password, and username
- Login with an email and password
- Record a Vlog in the browser
- Video and video thumbnail storage
- View a list of all Vlogs
- View a list of Vlogs for a specific user, including themselves
There's plenty of scope for several other features: from social interactions and moderation to video enhancements, and accessibility support such as captioning and translation.
You can take a look at the application template source in the SupaVlog GitHub repository and see the deployed application at supavlog.com. You can see a few vlogs that our team has created, but we've disabled signup. Please deploy your own instance of SupaVlog and make it your own.
Systems and components
Let's start by taking a look at the systems and components that make up SupaVlog.
A common theme in this article is to choose reliable and scalable third-party services for systems and components that are difficult to self-manage and self-maintain. In many cases, the functionality offered by these systems, whilst foundational to the application, aren't things I want to build myself. For example, although Video is core to the application, I want to focus on the vlog recording and viewing experience and not on video recording and storage infrastructure.
Generically, the components I want to offload to third-party services are:
- Video Recording
- Video and image storage
Here are the systems and components I chose to build SupaVlog and why.
Supabase is a full back-end as a service, offering many services that are core to building any application. I want to focus on building the features within the front-end application, so I chose to use Supabase for the following components:
Authentication - Nobody wants to build their own authentication. Supabase Auth provides a simple and powerful authentication service that supports email and password, third-party provider login, and more.
Database - Supabase provides a Postgres database that is easy to use and scale. But other services offer this too. However, the tooling that Supabase provides and the integration it has with the additional Supabase services made it the obvious choice.
The SupaVlog database schema consists of a
videostable containing details of all the vlogs created and a
profilestable that is presently only used to manage
Storage - As with the database, there are other options available. Specifically, Stream (who I'll cover momentarily) offers video storage and also auto-generates thumbnails. However, I felt that using Supabase for the Database, where tables would be referencing the video and thumbnail URLs in storage and where access to these services could be achieved through a single SDK, reduced complexity. There's also the strong likelihood that anyone using the SupaVlog template may want to be able to customize thumbnails and store other types of media.
Edge Functions - I could have used Next.js route handlers or Vercel Edge functions to consume the webhooks from Stream via Hookdeck. However, I want to keep my Next.js application focused on being a front-end and it also made sense to have the Edge Functions running in Supabase, close to the Storage and Database services they interact with.
I spent longer than expected choosing a video component. The key requirement is to allow video recording from the web browser and didn't want to roll my own front-end video functionality along with back-end processing and storage.
During my journey, I looked at Zoom, Agora, Loom (yeah, they have an SDK), Vonage Video (OpenTok-based solution), Dolby.io (Video via the acquisition of Voxeet), Twilio (deprecated), and Mux (which seemed the most promising result in search, but I learned they'd also deprecated their Real-Time Video/Spaces SDK). I chose Stream who I'd previously heard of due to their great demos focusing on chat and activity streams.
Stream's Video and Audio product is in open beta, but it has React components as a first-class part of the product and I managed to get a working app with Next.js in about fifteen minutes thanks to their React quickstart guide. They also provide asynchronous events via webhooks, which is a requirement for the event-driven architecture I want to build and it also made sense that video recordings would take some time to process and be available for storage.
Hookdeck is an event gateway: a backbone for the event-driven architecture, enabling applications to integrate with the asynchronous functionality of third-party and first-party services. The platform offers features such as event verification, payload transformations, filtering, queuing, authenticated and rate-limited event delivery, and automatic retries. Hookdeck is a dedicated hosted service with — like many hosted services — a fundamental promise of reliability and scalability.
Hookdeck has been built to support the full software development lifecycle (SDLC). In the case of building (or customizing) a SupaVlog instance, it provides the ability to receive events from the public Internet on your localhost (a localtunnel) during development, and tooling to enable you to inspect and replay events which is essential when building event-driven applications. It would be highly tedious to have to create a new video to trigger webhooks every time I want to test various features within the application.
The ability to ingest webhook events, filter, and retry in @Hookdeck has been amazingly helpful today.— Phil Leggetter (@leggetter)January 18, 2024
💡I often forget you can save common filters for quick access.
I'm still debugging. But without Hookdeck my development experience would be much more painful.pic.twitter.com/Qh2b2SXLjQ
When an application is deployed, Hookdeck manages reliably ingesting and delivering webhook events and includes features such as rate limiting and automatic retires during unexpected downtime. If your deployment becomes popular you're in safe hands and you can use Hookdeck metrics to analyze and iterate on your product.
I chose Next.js because it has a massive ecosystem of libraries and integrations to make use of. In particular, Supabase has an easy-to-use Next.js Auth quickstart with a tried and tested integration with Next.js. Next.js is a React framework for simple integration with Stream's React video components. Next.js is backed by Vercel, making the combination an obvious choice.
Once I'd chosen Next.js, Vercel was an obvious choice for the SupaVlog front-end application deployment. The Vercel team is always going to be on top of providing the best support for deploying and running Next.js applications.
Vercel has proven itself and has some big logos, so is a safe choice from a reliability and scalability perspective.
How the components communicate
The diagram below contains numbers that correspond to specific component communication. I'll cover each of these in turn, with some more complex than others.
1. Sign up and Login
As discussed, the signup and login functionality uses Supabase Auth. For the moment it uses email and password authentication, but the application can be updated to use the integrated third-party support for a large list of authentication providers and mechanisms including Google, Azure, Facebook, Apple, and SAML 2.0.
The SupaVlog signup flow includes a request for the user to choose a username that queries a
profiles table in the Supabase Database using the Supabase SDK to check if the username is available. The authentication workflow requires the user to confirm their email, at which point the username is also fully claimed.
2. Record a Vlog
Video recording is handled by Stream, and the UI is built with the Stream React Components. This is pretty customizable, and SupaVlog uses a select number of control components: a custom record button, microphone mute and selection, video toggle and selection, and screen share.
3. Video events trigger webhooks
The Stream platform triggers webhook events during the use of the Stream SDK within the Next.js application, and also as the call recording is processed. It's worth noting that in SupaVlog we reference videos (or vlogs) whereas Stream has the concept of a call that can include multiple recordings.
It may have been possible to capture all events client-side and add data to the Supabase database there, however, that would rely on the user keeping the browser open which we cannot rely on. So, where possible, all interactions between Stream and Supabase are orchestrated via Hookdeck.
See implementation caveats for details on some workarounds I had to put in place.
4. Webhooks are received by Hookdeck
The most common use case our customers have for Hookdeck is to act as their inbound webhook infrastructure and this is the use case for SupaVlog.
Hookdeck ingests events, verifies the events are from Stream, applies a filter to enable event routing, and delivers the webhooks events to the Supabase Edge Function. If there are any problems, Hookdeck can notify the engineering team (I don't cover this feature in this post), and retry event delivery, based on a defined retry configuration. The following sections go into specific detail.
Upon receipt of the Stream webhook events, Hookdeck performs verification using HMAC SHA-256 with hex encoding. Hookdeck provides built-in support for numerous providers and generic support for HMAC, API Key, and Basic Auth. In this case, the Stream webhook is verified using HMAC with the verification signature in the
Stream presently supports a single webhook URL to be configured and Hookdeck enables fanout, where the same webhook event can be delivered to multiple destinations.
The next sections cover how Hookdeck is configured to deliver the webhooks to the local development environment and Supabase Edge Functions.
5. Receive the webhooks locally during development
The Hookdeck CLI provides a localtunnel feature to receive events on your localhost during development. This also ensures that the local development environment has the same structure as the production environment.
In this case, the CLI is used to receive the Stream webhooks in locally running Supabase Edge Functions (another example of great developer tooling).
SupaVlog has two Edge Functions focusing on single units of work:
video-uploaded- This function is called by a webhook from Stream via Hookdeck when a video is ready and identified in the HTTP body of a webhook by a
typewith a value of
call.recording_ready. This edge function downloads the video from Stream (step 7), uploads it to a Supabase Storage bucket (step 8), and stores the video URL in the Supabase Database.
thumbnail-uploaded- This function is triggered by Stream when the call has ended and is identified in the HTTP body of a webhook by a
typewith a value of
call.recording_thumbnail_ready. Within this payload is a URL to an auto-generated thumbnail from the call. This edge function downloads the thumbnail from Stream (step 7), uploads it to a Supabase Storage bucket (step 8), and stores the thumbnail URL in the Supabase Database.
The video and thumbnail URLs contain a time-limited token that enables the assets to be downloaded from the CDN, so the downloads must occur before the token expires.
The Hookdeck configuration to support delivering what is effectively the same webhook event to each of the Edge Functions requires two connections to be configured:
In the above configuration:
stream-inboundis a Hookdeck Source that is configured to receive webhooks from Stream.
local-upload-thumbnailis a Hookdeck Destination, configured to forward requests on the localhost to the path
local-upload-videois a Hookdeck Destination, configured to forward requests on the localhost to the path
6. Hookdeck delivers webhook events to Supabase Edge Functions
In production, the Hookdeck configuration is almost identical with two changes:
- The Supabase Anon Key must be updated to match the one in production
- The SupaVlog production connections are configured so that only deliver
call.recording_readyevents are routed to the
upload-videoEdge Function and
upload-thumbnailEdge Function. This is achieved by adding Hookdeck filters to filter and route requests thereby reducing the load on the Edge Functions. You can also do this in development if you like.
You can also configure which events are sent in your Stream Project.
7. Supabase Edge Function retrieves thumbnails and videos from Stream
As discussed, Stream does have a CDN that stores the recorded videos and the thumbnails. However, I want to store those in a Supabase bucket. I've already covered the details of the
upload-thumbnail Edge Functions in an earlier section. So, in short, this step within the diagram covers downloading the assets into memory within the Edge Function prior to the next step.
If any errors occur within the Edge Function they will return a non-2xx HTTP response code and Hookdeck will retry the request based on the connection retry strategy and configuration.
8. Edge Function stores thumbnail and video in Supabase Storage and Database
With the video and thumbnail assets downloaded, the Edge Function uploads the assets to a Supabase Storage bucket and stores the URLs in the Supabase Database. All of this is achieved using the Supabase SDK. Simple as that.
As mentioned, the Stream Video & Audio product is in Beta. So, at the time of publishing this article the
call.recording_ready webhook only contained information about the recording and nothing about the user who created it. This means the current codebase has some workarounds:
- User information is injected into the unique Stream Call ID and then extracted in the Supabase Edge Function. This ID can then be used to reference the ID of the user who owns the video assets in the database interactions.
- Each Stream call can have more than one recording. This would be a really nice feature, allowing a Vlog entry to have multiple videos. However, because the user ID had to be injected into the Call ID, the workaround is to only allow a single recording per call.
Additionally, recorded videos presently have a large white area around them. I'm looking at removing this using a different Stream recording layout.
Try out SupaVlog and Contribute
That covers the general architecture, the systems and components, and the component interactions within SupaVlog.
I hope SupaVlog Application Starter Kit is useful and that it helps you build your own vlog or other scalable video-based application with Supabase, Stream, Hookdeck, and Next.js.
You can take a look at the application template source in the SupaVlog GitHub repository which contains all the details to deploy your own version. Feel free to raise an issue in the repo if you have any questions, feedback, or feature requests. And pull requests are very much appreciated and welcome!