Portfolio API
Private REST API that serves every piece of content on jmrg.dev: posts, projects and the per-language CMS documents. Fastify 5 over HTTP/2, Mongoose 9 and separate public and admin queries, with Clerk authentication on every route except the health check and the preflight.
Node.js · TypeScript · Fastify · MongoDB · Mongoose · Clerk · Zod · Docker

What it solves
The previous portfolio kept data and interface in a single deployment: changing one blog post meant rebuilding the whole website. Here content has its own service, with its own contract and its own lifecycle, and the frontend merely consumes it.
Decisions worth explaining
The API is private end to end. Every request requires a token, public reads such as the blog listing included. The health check and the CORS preflight are exempt, the latter because it cannot carry credentials by definition. The frontend issues its requests from the server, so that token never reaches the browser.
Public and admin queries are not the same query. The public listing filters by published state and trims the fields nobody consumes; the admin one returns the whole document.
Opt-in pagination. With no parameters the listing returns a plain array; with page and limit,
an envelope carrying its metadata. Ordering breaks ties by identifier, because sorting on date alone
repeated documents across pages whenever two shared the same one.
One document per language
Every piece of content exists once per language, keyed by the pair identifier and locale. Languages are deliberately asymmetric: an article can ship in Spanish before it has an English version. The frontend declares only the languages that actually exist in its alternate tags, rather than advertising a translation that would return a 404.
Stack
| Layer | Technology |
|---|---|
| Runtime | Node 24, strict TypeScript |
| HTTP | Fastify 5 over HTTP/2 |
| Persistence | MongoDB with Mongoose 9 |
| Auth | Clerk, with an e-mail allowlist for the admin panel |
| Validation | Zod at the edge of every route |
| Errors | Problem Details (RFC 9457) |