Skip to content
Krish
Full-stack site with its own API

This website

A portfolio built as evidence: a front end, an API, a database and monitoring, all running and all visible.

My role
Design and full-stack build
Status
Live, with a public status page
Built with
Next.js, React, TypeScript, three.js, Fastify, PostgreSQL, Drizzle, OpenTelemetry
Who it's for
Hiring managers and engineers who want to see how it is built
The problem

Why it needed to exist

A portfolio that only describes engineering asks the reader to take it on trust. I wanted the site itself to be the evidence, so that the parts a reader might ask about can be opened and checked.

Hiring managers and engineers who want to see how it is built, and anyone who wants to send a message.

The product

What it does

My role

What I did

I designed and built all of it: the pages and the scene, the API and its schema, the tests and the deployment.

Under the hood

How it works

  1. Browser

    Next.js pages

    Static pages with a WebGL scene that loads after first paint.

  2. Web

    Next.js on Vercel

    Serves the pages and refreshes the notes when I show or hide one.

  3. API

    Fastify and Zod

    One schema per endpoint drives validation, responses and the OpenAPI document.

  4. Data

    Postgres on Neon

    Notes, messages, rate-limit counters and request timings, with migrations kept in the repository.

  5. Telemetry

    OpenTelemetry

    Traces across web and API, and the figures behind the status page.

Status

Loading…

Key decisions

Choices that shaped it

One typed content package
The pages and the public API read the same validated data, so they cannot disagree with each other.
I choose what shows
Nothing is on the site until I have read it and chosen it. No quote is ever generated, and there are no placeholders.
One-time invite links, not an open form
An open form invites spam and leaves a reader wondering who wrote a note. A private link works once, expires, and carries the name it was made for. Only a hash of its code is stored, so a link cannot be read back from the database.
An inbox, not email or a queue
Messages arrive rarely, so a database table I read on the admin page is enough. There is no mail service to fail and no queue to keep alive.
Rate limits and metrics in the database
They live in Postgres, so they hold across every instance of the API and there is no cache server to run. Addresses are stored only as a keyed hash.
Drop spam silently
A filled honeypot or a high spam score gets the same answer as a real message, so a bot learns nothing from it.
Keep Zod out of the browser
Content is plain typed data checked by tests, which removed about 96 KB of gzipped JavaScript from every page.
Measure real requests
The latency and error figures come from live traffic in a rolling window, with monitoring and docs requests left out so they do not flatter the numbers.
Stand the scene down without a GPU
A software renderer cannot draw the field at a usable frame rate, and skipping it also skips the three.js download.
Outcome

Where it stands

It runs, and the figures above are read from the running API rather than written down here.

Next: A chat that answers questions about my work from this site's own content; Privacy-first analytics.

Next projectTreeify