Saikospeed Communities
Preview

Why I built it
Car culture lives in scattered Discords, Instagram comment threads, niche Facebook groups, and forum software that hasn’t been rethought in fifteen years. None of those surfaces are great at the things enthusiasts actually do — share build progress over months, ask fitment-specific questions, find local meets, and follow specific cars rather than specific people. Saikospeed Communities is the platform I wanted to use as an enthusiast: build journals as first-class objects, communities organized around platforms and disciplines, and a feed that ranks substance over engagement bait.
It is also the kitchen-sink project that touches every technology I work with — REST + WebSocket APIs, relational + object storage, auth, media pipelines, search, moderation, ranking, and a frontend that has to hold up under real interaction.
Feature set
- Posts and threads — text, images, videos, polls, build entries, with markdown-style formatting
- Comments — nested threads with collapse, edit history, and quote-replies
- Real-time interactions — likes, comment counts, presence, and notifications update without refreshing
- Media uploads — drag-and-drop, multi-file, with client-side compression and server-side transcoding for video
- Follows + social graph — follow users, cars, and communities; per-source mute and per-tag filters
- Communities — platform/discipline spaces (e.g. K-series, drift, time attack) with their own feeds and moderators
- Build journals — long-running posts with chronological entries, parts lists, and dyno/track logs
- Search — full-text + tag + community-scoped search with typeahead
- Notifications — in-app, email digest, and push, with per-event-type preferences
- Moderation — report queue, soft-delete, shadow ban, and per-community rules with auto-flagging
- Profiles + garages — each user has a profile and a garage of cars; cars are followable entities
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Web client (Next.js, App Router) │
│ - Server Components for shells, Client Components for UI │
│ - SWR for HTTP cache, native WS client for live updates │
└────────────┬───────────────────────────────────┬────────────┘
│ HTTPS (REST + GraphQL) │ WSS (events)
▼ ▼
┌──────────────────────────────┐ ┌────────────────────────────┐
│ API gateway (Node/Fastify) │ │ Realtime gateway │
│ - Auth, rate limit, schema │ │ (WebSocket + pub/sub) │
└────────────┬─────────────────┘ └──────────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Service layer (Node/TypeScript, modular monolith) │
│ ├─ identity auth, sessions, OAuth, profiles │
│ ├─ social follows, communities, garages │
│ ├─ content posts, comments, build journals │
│ ├─ media upload sign, transcode jobs, CDN purge │
│ ├─ feed ranking, fan-out, timelines │
│ ├─ search indexing + query │
│ ├─ notify in-app, email, push │
│ └─ moderation reports, rules, automod │
└──┬──────────┬──────────┬──────────┬──────────┬──────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌────────┐ ┌──────┐ ┌──────────┐
│ Pg │ │ Redis│ │ S3 + │ │ OS │ │ Worker │
│ (rel │ │ cache│ │ CloudF │ │ (sea │ │ queue │
│ data)│ │ +pubs│ │ ront │ │ rch) │ │ (BullMQ) │
└──────┘ └──────┘ └────────┘ └──────┘ └──────────┘
Layers
- Frontend — Next.js (App Router). Server Components render the static shell of feeds, profiles, and posts at the edge. Client Components handle anything interactive (composers, comment threads, real-time counters). HTTP data is fetched with SWR for stale-while-revalidate caching; live updates come over a single WebSocket connection.
- API gateway — Fastify. A thin gateway in front of the service layer handles auth (JWT verification + session lookup), rate limiting per route + per user, request validation against shared TypeScript schemas, and request logging. REST for resource endpoints, GraphQL for the feed/profile composite queries.
- Realtime gateway — WebSocket + Redis pub/sub. A separate WebSocket service holds connections and subscribes them to topics (
user:{id},post:{id},community:{slug}). Service-layer writes publish to Redis; the gateway fans out to subscribed sockets. Keeping it separate from the API gateway means the request path stays stateless and a noisy realtime fan-out can’t take down core HTTP traffic. - Service layer — modular monolith. A single Node/TypeScript codebase split into bounded modules with internal interfaces. One deploy, one repo, but each module owns its tables and exposes a typed service object. Easy to break out into services later if any module’s load profile demands it.
- Workers — BullMQ on Redis. Anything slow or fan-out-heavy runs as a background job: media transcoding, feed fan-out on follow, search indexing, email/push delivery, automod scans. Web requests stay fast because they only enqueue.
Data
- Postgres — the system of record. Users, posts, comments, follows, communities, garages, build journals. Heavy use of partial indexes, composite indexes for feed queries, and
tsvectorcolumns for fast in-DB search where OpenSearch is overkill. - Redis — session cache, rate limits, hot-feed cache, presence, and pub/sub for the realtime gateway.
- S3 + CloudFront — original media + transcoded variants. Uploads go direct from the browser via pre-signed POSTs, so the API never proxies bytes.
- OpenSearch — full-text + faceted search over posts, comments, communities, and users. Updated asynchronously by the search-indexer worker on content events.
Feed and ranking
A hybrid model: a chronological “Following” feed plus a ranked “For You” feed. The ranked feed scores candidates from followed entities, joined communities, and recent platform-wide activity, then re-ranks on freshness, engagement velocity, author affinity, and a content-type mix penalty so the feed doesn’t degenerate into one media type. Fan-out-on-write for users with small audiences, fan-out-on-read for high-follower accounts — the standard hybrid that keeps both the timeline and the celebrity case fast.
Auth
Email + password with Argon2id, plus OAuth (Google, Apple). Sessions are JWTs (short-lived access + rotating refresh) backed by a Redis session store so revocation is immediate. The realtime gateway authenticates the WebSocket handshake against the same session store.
Media pipeline
- Client requests a pre-signed POST from
media. - Browser uploads the original directly to S3.
- S3 event triggers a transcode job (FFmpeg in a worker) that produces the streaming variants and a poster frame.
- On completion the post becomes visible and the realtime gateway pushes a
media.readyevent to the author.
Moderation
Reports queue into Postgres, automod runs simple rules first (rate, link patterns, banned-term lists), then escalates to a per-community moderator queue. Soft-deletes preserve the audit trail; shadow bans return success to the offender while hiding their content from everyone else, which kills most low-effort spam without pushing the bad actor to make new accounts.
Tech stack
- Frontend: Next.js (App Router), React Server Components, TypeScript, SWR, Tailwind, native WebSocket client
- API: Node.js, Fastify, GraphQL (composite reads) + REST (resources), Zod schemas shared with the client
- Realtime: WebSocket gateway (Node) + Redis pub/sub
- Workers: BullMQ on Redis (transcode, fan-out, search index, notifications, automod)
- Data: PostgreSQL (system of record), Redis (cache + pub/sub + queues), OpenSearch (search), S3 + CloudFront (media)
- Media: Pre-signed S3 uploads, FFmpeg-based transcode, image variants generated on demand
- Auth: Argon2id passwords, OAuth (Google/Apple), JWT access tokens + Redis-backed refresh sessions
- Infra: AWS, IaC via Terraform, blue/green deploys, structured logs + traces (OpenTelemetry)
Status
In development — launching soon. Feed, posts, comments, follows, garages, and the realtime layer are wired end-to-end; communities, search, and moderation tooling are next.