Build a video sharing app with StreamGate
A self-hostable video sharing web app built on Next.js and powered by FastPix. Upload videos or record your screen and camera directly in the browser — get a permanent shareable link in seconds, no database required.
StreamGate is a self-hosted video sharing web app built with Next.js and powered by FastPix. It lets users upload videos or record their screen and camera in the browser, then share a permanent link within seconds. No database is required.
Live demo: streamgate.dev
Overview
StreamGate implements a complete video upload and playback workflow using FastPix APIs. Key capabilities include:
- Drag-and-drop file uploading with a progress bar
- In-browser screen and camera recording through the MediaRecorder API
- Adaptive HLS playback delivered over FastPix's global CDN with an adaptive bitrate ladder
- Webhook event handling for asset lifecycle events
- Permanent shareable URLs for every uploaded video
Before you begin
System requirements
| Requirement | Version | Notes |
|---|---|---|
| Node.js | 18 or later | Use Node.js 20 LTS for best compatibility with Next.js 16 |
| Package manager | Latest | npm, pnpm, or yarn |
| FastPix account | Required | Provides API credentials and media storage |
| Internet connection | Required | Required for API calls and media delivery |
Get your FastPix API credentials
FastPix APIs use HTTP Basic Auth. Your Access Token ID is the username; your Secret Key is the password.
To generate credentials:
- Log in to the FastPix Dashboard.
- Go to Manage → Access Tokens.
- Copy your Access Token ID and Secret Key.
For detailed steps, see Authentication with Basic Auth.
Set up StreamGate
Clone the repository and install dependencies
git clone https://github.com/FastPix/streamGate.git
cd streamGate
npm installConfigure environment variables
Create a .env.local file in the project root:
touch .env.localAdd the following variables:
FASTPIX_ACCESS_TOKEN_ID=your-access-token-id
FASTPIX_SECRET_KEY=your-secret-key
FASTPIX_WEBHOOK_SECRET=your-webhook-secret
NEXT_PUBLIC_BASE_URL=http://localhost:3000WARNING:
Never commit
.env.localto version control. The file is already listed in.gitignore.
For a full description of each variable, see Environment variables reference.
Start the development server
npm run devOpen http://localhost:3000 in your browser.
Routes and API endpoints
Upload
| Method | Route | Description |
|---|---|---|
POST | /api/uploads | Creates a signed FastPix upload URL; returns uploadId and url |
GET | /api/uploads/[id] | Polls upload status; returns mediaId when the asset is created |
GET | /api/assets/[id] | Returns asset status and playbackId when processing is complete |
Playback
| Route | Description |
|---|---|
/v/[mediaId] | Watch page that embeds the FastPix HLS player with Open Graph metadata |
Recording
| Route | Description |
|---|---|
/record | In-browser recording page; supports screen capture and webcam |
Webhooks
| Method | Route | Description |
|---|---|---|
POST | /api/webhooks/fastpix | Verifies HMAC-SHA256 signatures and handles FastPix asset lifecycle events |
Integrate StreamGate into an existing app
You don't need to run StreamGate as a standalone project. You can copy individual parts into your own codebase.
Option A: Copy the API routes (backend only)
Copy the following files into your Next.js project for server-side FastPix integration:
lib/fastpix.ts
app/api/uploads/route.ts
app/api/uploads/[id]/route.ts
app/api/assets/[id]/route.ts
app/api/webhooks/fastpix/route.ts # optional
Install the FastPix Node SDK:
npm install @fastpix/fastpix-nodeThen call the routes from your existing frontend:
// 1. Get a signed upload URL
const { uploadId, url } = await fetch('/api/uploads', { method: 'POST' }).then(r => r.json());
// 2. Upload the file directly to FastPix
await fetch(url, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
});
// 3. Poll until the asset is ready (2-minute timeout)
let mediaId;
for (let i = 0; i < 60; i++) {
await new Promise(r => setTimeout(r, 2000));
const data = await fetch(`/api/uploads/${uploadId}`).then(r => r.json());
if (data.mediaId && data.status !== 'waiting') {
mediaId = data.mediaId;
break;
}
}
// 4. Get the playback ID and render the player
const { playbackId } = await fetch(`/api/assets/${mediaId}`).then(r => r.json());Option B: Copy the React components
Copy these ready-made components into your Next.js or React project:
<Uploader /> - drag-and-drop file upload with a progress bar:
import Uploader from '@/components/Uploader';
<Uploader /><Player /> - FastPix HLS player web component:
import Player from '@/components/Player';
<Player playbackId="your-playback-id" /><Recorder /> - in-browser screen and camera recorder:
import Recorder from '@/components/Recorder';
<Recorder />Install the required dependencies:
npm install @fastpix/fp-player swr fix-webm-durationOption C: Use the FastPix Node SDK directly
For non-Next.js backends, use the FastPix Node SDK without any of the StreamGate files:
npm install @fastpix/fastpix-nodeimport { Fastpix } from '@fastpix/fastpix-node';
const fp = new Fastpix({
security: {
username: process.env.FASTPIX_ACCESS_TOKEN_ID,
password: process.env.FASTPIX_SECRET_KEY,
},
});
const result = await fp.inputVideo.upload({
corsOrigin: '*',
pushMediaSettings: {
accessPolicy: 'public',
maxResolution: '1080p'
},
});
console.log(result.data.uploadId, result.data.url);For the full SDK reference, see the FastPix API reference.
Webhooks
StreamGate verifies incoming FastPix webhook payloads using HMAC-SHA256. To enable verification, set the FASTPIX_WEBHOOK_SECRET environment variable.
Register your endpoint
In the FastPix Dashboard, go to Manage → Webhooks and add:
https://your-domain.com/api/webhooks/fastpix
Supported events
| Event | Description |
|---|---|
video.media.created | FastPix has accepted the uploaded file |
video.media.ready | The media has been processed and is ready to stream |
video.media.failed | Processing failed |
Note: If
FASTPIX_WEBHOOK_SECRETis not set, signature verification is skipped. This is acceptable for local development but not recommended in production.
Error handling
All API routes return JSON error responses with an error field and an HTTP status code.
| Status code | Cause |
|---|---|
401 | Webhook signature verification failed |
500 | FastPix API error or unexpected server failure |
Example: handling an upload error
const res = await fetch('/api/uploads', { method: 'POST' });
if (!res.ok) {
const { error } = await res.json();
console.error('Upload failed:', error);
}FastPix API errors are logged to the server console with the full status code, response body, and headers.
Deploy to Cloudflare Pages
Cloudflare Pages is the recommended deployment target for StreamGate.
Install the adapter and Wrangler CLI
npm install --save-dev @cloudflare/next-on-pages wranglerCreate wrangler.toml
wrangler.tomlIn the project root, create a wrangler.toml file with the following content:
name = "streamgate"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"NOTE:
The
nodejs_compatflag is required. StreamGate uses the FastPix SDK and the Node.jscryptomodule for webhook signature verification.
Add build scripts to package.json
package.json"scripts": {
"pages:build": "npx @cloudflare/next-on-pages",
"pages:deploy": "npm run pages:build && wrangler pages deploy"
}Deploy
npm run pages:deploySet environment variables
- Open the Cloudflare Dashboard.
- Go to Pages → your project → Settings → Environment variables.
- Add all four environment variables for both the Production and Preview environments.
- Set
NEXT_PUBLIC_BASE_URLto your production domain, for examplehttps://streamgate.pages.dev.
Environment variables reference
| Variable | Required | Description |
|---|---|---|
FASTPIX_ACCESS_TOKEN_ID | Yes | Basic Auth username for the FastPix API |
FASTPIX_SECRET_KEY | Yes | Basic Auth password for the FastPix API |
FASTPIX_WEBHOOK_SECRET | No | HMAC-SHA256 key for verifying webhook payloads |
NEXT_PUBLIC_BASE_URL | No | Base URL for shareable links. Defaults to http://localhost:3000 |
What's next
Updated about 2 hours ago