I design and build fast, considered products for the web and mobile — from the database schema to the last pixel of the interface.
Karthik
A self-hosted CMS for developers: model content in a visual editor, publish with live preview and releases, and deliver it over REST, GraphQL and real-time events.
Own your content, outright.
Most headless CMSs make you rent your content, or keep the features that matter for an enterprise plan. I wanted one you host yourself — that editors still enjoy using.

A schema editor that respects your data.
15 field types, reusable components and conditional fields. Rename or convert a field and every existing entry is migrated in the same transaction.

Enterprise workflow, in the box.
Live preview, releases, version history, SSO and permissions down to single fields — per role, per module, per content type.

Every type is an API on save.
REST with nested filters, a typed GraphQL schema that rebuilds itself, real-time events and signed webhooks — plus a typed client. This site reads its content with it.
import { createClient } from '@nodepress-cms/client';
const cms = createClient({ url: process.env.NODEPRESS_URL });
const { data } = await cms.collection('project').list({
where: { featured: { eq: true } },
sort: 'createdAt:asc',
});- 01GSR CalculatorAndroid appA focused tool for Indian gold and silver investors: enter today’s prices and the Gold–Silver Ratio shows which metal offers more value right now — with portfolio analysis and an average-price calculator.2026

- 02NodePress Client & CLI
Developer toolingA zero-dependency, fully typed TypeScript client for the NodePress API, and create-nodepress-app: scaffolding, schema sync, type generation and an MCP server for AI agents.2026 - 03NodePress Website
Marketing siteThe product site for NodePress — a dark, product-led design built around the real admin, with scroll-driven storytelling and code highlighted at build time.2026 - 04BIT RunnerBrowser gameA tiny endless runner hidden in this site — Canvas 2D, procedural Web Audio sound effects and no assets at all. Press G to play.2026Press G
Interfaces
Fast, accessible, production-grade front-ends in React and Next.js — from design systems to interactive, motion-rich experiences.
- React
- Next.js
- TypeScript
- Motion
- Tailwind
Systems
APIs, data models and real-time systems built for production traffic: REST and GraphQL, queues, caching and the infrastructure to run them.
- Node.js
- NestJS
- PostgreSQL
- Redis
- GraphQL
Creative code
Pushing the browser past static pages — generative visuals, WebGL scenes, games and interactions people remember.
- Three.js
- WebGL
- Canvas
- GSAP
- Web Audio
For more than seven years I have been shipping production software at the intersection of design and engineering.
I also build my own products, end to end — among them NodePress, an open-source headless CMS, and GSR Calculator, an Android app for gold and silver investors.
- Based in
- India
- Experience
- 7+ years
NodePress
Founder & lead engineer- Designed and built an open-source headless CMS end to end: API, admin, CLI, client library and docs
- Content modelling with 15 field types, components and data migrations; REST, typed GraphQL and real-time events
- Role-based access down to individual fields, SSO, plugins with signed packages, and a one-command server installer
2026 — nowLeo9studio
Frontend engineer- Built real-time dashboards for 50k+ daily active users
- Migrated to Next.js, cutting LCP from 4.2s to 1.3s
- Created a component library adopted by 6 product teams
2018 — now
Projects, experience, services and this bio are content types in my own CMS. The page is rendered on the server, cached for five minutes, and falls back to built-in copy if the API is ever unreachable.
// lib/cms.ts — every section on this page comes from here
const cms = createClient({
url: process.env.NODEPRESS_URL,
apiKey: process.env.NODEPRESS_API_KEY,
fetch: (url, init) => fetch(url, { ...init, next: { revalidate: 300 } }),
});
const [profile, projects] = await Promise.all([
cms.collection('profile').get('main'),
cms.collection('project').list({ limit: 100 }),
]);