A video editor that runs entirely in the browser. Clips, projects and effects stay on the device, and the editing rules are pure TypeScript with no DOM and no clock, so they test without one.
A visual two-way sync platform. Connects Airtable, Supabase and Postgres and keeps records in step in both directions, with team access enforced by row-level security.
A social content platform with an AI chat copilot, a drag-and-drop content calendar, thread planning and X analytics. Scheduled posts run on AWS Lambda.
I build the backend pieces other things depend on: render services, queues, and integrations that keep data in sync between systems. I care about the failure cases, like what happens when a worker dies mid-job or a third-party API goes slow, and I design so one bad request can't take everything else down.
Putting a browser behind a queue so it can't take the API down
An HTML-to-PDF service in .NET. Rendering means running Chromium, which is slow, memory-hungry, and fails in ways an HTTP handler has no good answer for. So rendering doesn't belong in the request: the API takes the job, Redis holds it, and a separate worker does the rendering.
In progress · queue, worker and safety checks built and tested
Nate JamesBackend & Infrastructure, Data Integrations, Full-stack Web
A claimed job sits in a Redis processing set. If a worker dies holding it, a sweeper puts it back in the queue, checking every 30 seconds.
Browser contextsCapped
One per CPU core, at least 2. Bursts wait on a semaphore instead of running the host out of memory.
SSRF checks2 per URL
Private and cloud-metadata addresses are blocked before a job is queued and again inside the browser, which closes the redirect and DNS-rebinding gap.
Tests23
API, SSRF rules, and page-break fixtures: nested tables, floats across a break, orphans and widows, table headers with rowspan.
How it's built
API hostASP.NET Core
⇢
Redis queuepending + processing
→
Renderer workerPlaywright · browser pool
→
PostgreSQLEF Core · job status
Chromiumpinned revision
BUILTNEXT: API ENQUEUES JOBS
The hard part
A browser is a terrible thing to put in a request path. Chromium takes hundreds of megabytes, a page with a slow web font can hang for the full timeout, and when it dies it takes whatever shares its process with it. Rendering inline means one bad template slows down every other caller.
Moving it to a worker means a job can't be lost when that worker dies. I chose at-least-once delivery: a job that isn't marked done within five minutes goes back in the queue. The tradeoff is that a slow but still-running render can occasionally run twice, which is cheaper than a customer's PDF silently never arriving. The Chromium revision is pinned for the same reason: a renderer that quietly picks up a different browser is a bug you'd only find in production.
Build log
Project skeleton
Solution split into api, core, data, queue and renderer projects.
Database wiring
EF Core with separate reader and writer endpoints, and an IAM token in place of a stored password for Aurora.
Queue and renderer
Redis queue with a processing set and visibility timeout, the Playwright worker, and the browser pool.
Full local stack running
One script starts Postgres and Redis in Docker, applies migrations, and runs the worker and API.
Diagnostics and tests
A console harness for running one HTML file through Chromium, plus unit tests for the API and SSRF rules.
Page-break fixture suite
Real-browser tests for the layouts PDF engines usually get wrong.
Connect the API to the queue
POST /v1/convert will return a job id right away instead of a placeholder PDF, and callers poll /v1/jobs/{id}.
The API
From the repo
POST /v1/convertHTML or a URL in, PDF out. Page size, header and footer options.
GET /v1/jobs/{id}Job status from Postgres.
GET /v1/usageRender time used by the current API key.
A video editor whose editing rules don't need a browser to test
A video editor that runs entirely in the browser, with no server, database or login. You tap a person or object, FlareCut tracks it, and effects follow it through the clip. Footage, projects and your effects library stay on your device.
The editing rules live in pure TypeScript, so they run in plain unit tests without a browser.
Servers0
Projects and the effects library are stored on the device in IndexedDB.
Domain importsNone
The core never touches React, the DOM, canvas, workers, the clock or random. Time and IDs are passed in.
TracingOnce per file
Each video is fingerprinted by its hash, so traced objects are reused and duplicated clips keep their effects.
How it's built
React UIplayer.ts · composition root
→
Applicationactions · job queue · ports
→
Domaintimeline rules · effects
·
Analysis workerMediaPipe pose · hands · face
Canvas + WebGLrenderer · particles
IndexedDBfilms · effects library
MY CODE (DEPENDENCIES POINT INWARD)SERVICES IT USES
The hard part
Tracking only works if the effect stays attached to the right thing while you edit. Trim a clip, move it, or duplicate it, and an outline drawn at "timeline second 4" now points at the wrong frame.
So masks and tracking are stored by source file and source frame, never by timeline position, and every effect has exactly one target that it follows wherever the target goes. Those rules are written down in the repo and enforced in the domain layer, which is why they can be tested 410 ways without opening a browser. Tracking also cancels out camera motion and flags frames it isn't sure about, so you can fix them on a roto screen.
Build log
Tap-to-track editor
First version: analyze the video first, then track what you tap.
Timeline and first effects
Effect clips, impacts, zoom, sound, and body-part targeting. Landing page with a waitlist.
Footage tools
Split, cutouts and paths. Multiple videos, with cutouts as their own layers.
Roto screen
Check and fix cutouts frame by frame. Whole-person tracking.
Effect engines
Frame, text, motion and object engines, particles as data, and projects saved on the device.
Trace once, fixed layer order
Videos fingerprinted so tracing is reused. Timeline rules written down. Merged to main.
From the commit history: 65 commits, Sep 25 to Sep 30, 2026.
Keeping records in step between apps that weren't built to talk to each other
A two-way data sync platform. Connect Airtable, Google Sheets, Supabase or a database, map tables and fields, pick a direction, and Syncflow keeps them in step.
Source to destination, the reverse, or two-way, set per table mapping.
Conflict rules3
When both sides changed: source wins, destination wins, or the latest edit wins.
Change checksEvery 5 min
A scheduled Lambda polls for changes, alongside a webhook handler for sources that push.
AccessRow-level
Teams only see their own connections and jobs, enforced in Postgres rather than in app code.
How it's built
Airtable · SheetsOAuth
Postgres · MySQL · Mongoconnection string
→
SupabaseOAuth edge function · RLS
→
data-syncLambda · every 5 min + webhooks
→
Destinationmapped fields
metadata-syncLambda · tables + fields
MY CODESERVICES IT USES
The hard part
Most of these tools have no change feed. To avoid re-reading whole tables every five minutes, the sync uses each source's own "last modified" field: Airtable's Last Modified Time, Notion's last_edited_time, or an updated_at column. Large tables (10k+ rows) are required to have one, so incremental sync still scales.
Two-way sync means both sides can change the same record. Each table mapping carries its own conflict rule, and field mappings can transform values on the way through. Fetching a source's schema started as an edge function and moved into a database function, so it only runs for a logged-in user and can't be handed arbitrary input.
Build log
Project start
Flows list and detail pages.
Database and auth
Tables created, auth context fixed, first integrations.
Provider schema lookup
Edge function to list a source's databases and tables.
Rate limiting
Backend rate limits, with docs.
Schema lookup locked down
Moved to a database function: logged-in callers only, no arbitrary input.
Permissions
Page guards, metadata sync config, and a job permission fix.
Sync logic done
Filters split out into their own types next.
Services in TypeScript
Sync services converted and linked to SQL, then tests.
From the commit history: 112 commits, May 21 to Aug 2, 2025.
Filling and signing any PDF without breaking its form
A browser PDF signer. Open a PDF, fill its existing fields, place a signature, and download. No account needed, and files stay in the browser for the session.
Text, date, signature, checkbox and radio, read from whatever PDF is uploaded.
Field names assumedNone
Import reads the form's own structure instead of expecting known names.
After downloadStill fillable
Values go back into the original fields, so the file works in Preview, Chrome and other readers.
Server for guestsNone
Upload, edit, download. Nothing is saved to a backend.
How it's built
PDF fileuploaded
→
AcroForm importcatalog + page widget scan
→
EditorPDF.js rendering
→
pdf-lib exportvalues written back
→
Downloadform intact
MY CODESERVICES IT USES
The hard part
Real-world PDFs are messy. Signature boxes often never make it into the form's catalog, and field names are whatever the author typed. So import doesn't trust either: it reads the AcroForm catalog, then scans every widget on every page, and classifies each one from its PDF type and flags (/Tx, /Sig, /Btn, radio or checkbox).
Export writes the values back into those same widgets instead of flattening the page, so the downloaded file is still a working form. A drawn signature is embedded as an image over its box. In September I cut the product down to signing only and hid merge, split and rotate from the menu, because one job done well beat six done halfway.
Build log
Editor rebuild
New editor and header navigation.
Free accounts
Most of the free path working.
MVP release
Login removed so guests can sign without an account.
Analytics and SEO
Analytics, plus pages for common signing searches.
Sign only
Narrowed the product to signing.
Cleanup
Removed dead and competing pages, canonical URLs, updated FAQ.
Search analytics
Ahrefs analytics added.
From the commit history: 77 commits, Dec 31, 2025 to Sep 26, 2026.
Publishing posts on schedule when nobody has the app open
A social content platform for X. An AI copilot helps you write and refine posts, a drag-and-drop calendar plans them, a thread planner structures longer posts, and analytics show what drove followers and replies.
Scheduled posts are published by a Lambda on a timer, not by the web app.
Plans3 tiers
Usage limits per feature and billing period, tracked in one quota system.
PaymentsStripe
Checkout and webhooks, with webhooks handled on Lambda.
Data models10
Posts, conversations, library and usage in Postgres through Prisma.
How it's built
Next.js appUI + API routes
→
PostgresPrisma · Neon
→
EventBridgetimer
→
PublisherLambda
→
X APIpublish + analytics
S3media uploads
MY CODESERVICES IT USES
The hard part
A scheduled post has to go out at 9:00 whether or not anyone has the app open, and a Next.js app on Netlify only runs when someone makes a request. So scheduling lives outside the app: posts are saved to Postgres, and an EventBridge timer wakes a Lambda that publishes whatever is due through the X API.
The scheduler took several rounds of fixes in September before it was reliable. Scheduling is refused if the user hasn't connected an account, so a post can't silently fail at 9:00. Every AI action also counts against a per-feature quota for the billing period, so a plan's limits hold no matter which screen the request comes from.
Build log
Project start
First screens and X OAuth.
Editing and scheduling
Posts can be edited and scheduled from the content feed.
Safer scheduling
Scheduling refused when no account is connected.
Payments
Pricing and Stripe, then webhook fixes for credits.
Scheduler queue working
First working background scheduler.
AI chat
First version of the copilot chat.
Library and scheduler
Interactive content library. v3 merged in October.
Calendar
Calendar loads editor data, plus bulk post generation.
Analytics
Analytics dashboard and thread fixes.
From the commit history: 499 commits, Jul 29 to Nov 7, 2025.
A shared limit in DynamoDB holds every Lambda to the free tier together.
WorkerEvery minute
A scheduled Lambda picks up pending analysis jobs.
Edge functions6
Payments, Stripe webhooks, email, forex rates and validation, and stock lookups.
Migrations13
Database schema changes kept in Supabase migrations.
How it's built
React appimport · dashboards
→
SupabasePostgres · Auth · edge functions
→
analysis_jobspending → processing
→
Lambda workerevery minute
→
DynamoDBrate-limit window
Geminitrade analysis
MY CODESERVICES IT USES
The hard part
The AI review first ran in a Supabase edge function. That broke twice over: edge functions stop after 60 seconds, and Google tightened the free-tier rate limit. So in June I moved analysis out of the request. Importing trades now creates a job, and a Lambda worker wakes every minute, marks one pending job as processing, and runs it.
With several Lambdas running, each one counting its own calls would blow through the limit. The count lives in DynamoDB instead: a worker can only record a new call with a conditional update that succeeds while fewer than 15 calls sit in the current one-minute window, so the limit holds across all of them. Earlier work went into the parts traders notice first, like imports with missing exit prices and correct P&L.
Build log
Project start
Navigation, broker sync and trade import.
Import edge cases
Empty accounts and trades with no exit price handled.
P&L
Improved P&L calculation and a refactored import.
Payments
Stripe integrated.
Behavior analysis
Behavior page, then debriefs built before adding AI.
AI analysis
Edge function with ChatGPT as the main model and Gemini as fallback.
Strategy
Behavior chart done, strategy page started.
Background worker
Rate limits and the 60-second limit led to a Lambda job worker.
Job processing
Background analysis jobs running.
From the commit history: 328 commits, Feb 11 to Aug 2, 2025.
Want pages like these for your projects?
I'll turn your 3 best projects into case study pages clients and hiring managers actually read. You send the repos or a quick rundown, I write them up, and you approve everything before it goes live.
How each project is built, the key decisions, and the hard part