Skip to content
Shekhar Kumar

All projects

Side project · Shipped

Multi-tenant AI app platform

One Go + TypeScript backend serving several consumer AI apps, isolated per tenant, shipped with blue-green deploys.

Role
Lead developer
Period
Dec 2025 – May 2026
Updated

TL;DR

  1. One backend platform for several consumer apps: a Go API and a TypeScript AI service shared by every app, with each tenant's data isolated by app ID.
  2. AI as a shared service: multi-provider LLM chat (OpenAI, Gemini, xAI) with RAG over pgvector, conversation memory, SSE streaming, and token and coin billing.
  3. Shipped with blue-green CI/CD on GitHub Actions: build, verify, health-check the idle slot, flip nginx, and roll back automatically on failure.
apps on one backend
2
LLM providers
3
commits, ~80% mine
289

Follow a tenant request · ship it

Illustrative

  1. Companion app

    Expo · Next.js

  2. App ID

    X-App-ID · cached

  3. Auth

    JWT + session

  4. Guards

    rate limit · plan

  5. AI routes

    Go · ai-common

  6. AI service

    TypeScript · LangGraph

step 1 of 6

A companion-app user sends a chat message

The app calls the shared backend with its own tenant ID in the X-App-ID header.

POST /api/v1/... chat   X-App-ID: <companion app uuid>
Two views of the real system. Follow a request from the AI companion app or NotifyPay through the shared middleware into its own tenant's data (or watch one without X-App-ID get rejected), then step through the production blue-green deploy, including what happens when a health check fails.

The problem

Every new consumer app, whether an AI character chat app or an Android payments app, needs the same plumbing: users and auth, subscriptions and plans, rate limits, notifications, AI billing, observability and a safe way to deploy. Rebuilding it per app is slow and multiplies the places things break.

This platform builds it once. Apps plug in as tenants: they share the common modules and the AI service, add their own routes on top, and can never see each other's data.

My role · Lead developer (about 80% of 289 commits): the multi-tenant Go platform, the TypeScript AI service, and the CI/CD pipeline and server setup.

How it works

  1. Tenant from the first byte

    Every request carries X-App-ID. Middleware validates it against the apps table (cached in Redis), stores it in the request context, and rejects requests without one; only global admin routes are exempt.

  2. Isolation in the schema

    Every table has an indexed app_id (except the root apps table and global admins), UUID keys and soft deletes, and every query filters by app_id. PostgreSQL through GORM, with SQL migrations.

  3. Shared modules, app-specific routes

    Users, auth, subscriptions and notifications live once in common modules; each app (an AI companion app, NotifyPay) adds its own routes, handlers and YAML config on top. A new app is a new tenant, not a new backend.

  4. A middleware chain every app gets

    Gin middleware for request IDs, OpenTelemetry tracing, logging tagged with the app ID, timeouts, panic recovery, JWT auth with sessions, Redis-backed distributed rate limiting with an in-memory fallback, and plan checks against cached subscription state.

  5. An AI service in TypeScript

    Express with LangChain.js and LangGraph orchestrates OpenAI, Gemini and xAI models: RAG over documents stored in pgvector, memory extracted from conversations, replies streamed over SSE, and token quotas and coins enforced with Redis caching.

  6. Observable, and shuts down cleanly

    OpenTelemetry traces, metrics and logs go to SigNoz, and every component stops in an ordered graceful shutdown.

  7. Blue-green CI/CD

    GitHub Actions builds a static arm64 Go binary and the TypeScript service, uploads both into the idle slot with configs and migrations (verifying each upload), starts it, health-checks it, flips the current symlink and the nginx upstream, and rolls back automatically if anything fails. Dev deploys on every push to the dev branch; production deploys on demand.

Key decisions

  • One shared schema with app_id on every row

    instead of: A database or schema per tenant

    One migration path and one connection pool serve every app, with isolation enforced at the door by middleware and in every query.

  • Go for the platform, TypeScript for AI

    instead of: One language for everything

    Each side uses the ecosystem it needs: a fast, typed Go core for the API, and TypeScript for LangChain.js, LangGraph and the provider SDKs.

  • Blue-green deploys behind a health-gated switch

    instead of: Restarting the service in place

    The new version runs and proves itself on the idle slot before nginx sends it a single request, and the previous slot stays on disk for an instant rollback.

  • Distributed rate limiting with an in-memory fallback

    instead of: Per-process limits only

    Limits hold across instances through Redis, and the API keeps protecting itself if Redis is unavailable.

  • A static arm64 binary built in CI

    instead of: Building on the server

    CI produces one self-contained, stripped binary that runs on the arm64 VM with no toolchain installed there.

Outcomes

  • Two apps on one backend: an AI companion app (Expo mobile + Next.js web) and NotifyPay (an Android app).
  • Multi-provider AI with RAG, memory, streaming and billing, shared by every tenant.
  • Blue-green deploys for both services: dev on every push, production on demand, with automatic rollback.

Stack

  • Go
  • Gin
  • GORM
  • PostgreSQL
  • pgvector
  • Redis
  • TypeScript
  • LangGraph
  • OpenTelemetry
  • GitHub Actions