nidobin.com Tech Details
Created by brian on
I started working on nidobin in early 2025 as my first large-scale, full-stack, 100% solo web dev project. I am a front-end-focused web developer in my day job, and I've worked for digital marketing agencies for almost 20 years now which means I spend most of my dev time turning other peoples' creative visions to reality. I take a lot of pride and joy in that, but there's something to be said about making something entirely on your own -- so here I am.
It may seem like a simple project, but there's a lot happening under the hood. This is where I'll outline the technologies used to create nidobin to hopefully inspire other indie web developers and maybe spur a conversation or two.
The general architecture
To keep things organized, I went with a monorepo structure. It makes sharing code and types between the front-end and back-end super easy.
nidobin-api: A Node.js app handling all the data, auth, and business logic.nidobin-web: The Next.js app for the UI. It's server-side rendered so search engines can actually read the content.packages/nidobin-types: This is the glue holding it all together—a shared library of TypeScript definitions so the API and client are always on the same page.
Back-end
I originally used Express, but refactored the entire app to Fastify a few months ago mainly as a learning exercise -- and it's a good thing I did, because I've grown to really freaking love Fastify. It's generally faster and I really like the developer experience you get with its plugin system.
- Runtime: Node.js (v24) & TypeScript (v5.9)
- Framework: Fastify v5
- Validation: Zod. I use this to validate every request body and query parameter before it touches any business logic.
The API application runs on a Debian 13 VPS that I picked up dirt-cheap on a Black Friday sale, and could easily be scaled-up if needed.
Database
Instead of using a heavy ORM like Prisma or TypeORM, I decided to stick to raw SQL using node-postgres.
I mostly did this for control and performance, but also to experiment with different optimization techniques. Writing raw SQL gives me complete control over the query execution plan, so there's no "magic" generating inefficient joins behind my back. It forces me to think about indexes and data structure upfront. It’s a bit more boilerplate, but the resulting speed and clarity are worth it... especially for a project that is maintained by 1 person.
Authentication
I'm particularly proud of the authentication system. Instead of deferring to third-party providers like Auth0 or Firebase, I built a robust, custom JWT-based auth system from scratch.
- Dual token strategy: Short-lived access tokens (15m) for security, paired with long-lived refresh tokens (30d) for user convenience.
- Token rotation & reuse detection: Every time a refresh token is used, it's rotated. If an old token is ever reused (signaling a potential leak), the entire token family is immediately revoked to protect the user.
- Defense in depth:
- HTTP-only cookies to prevent XSS attacks.
- Bcrypt for password hashing.
- Rate limiting on login endpoints to prevent brute-force attacks.
- Timing attack protection by always performing hash comparisons, even for invalid users.
This approach gives me complete ownership of user data and the flexibility to implement security features exactly how I want them.
Infrastructure
The whole API is containerized with Docker and orchestrated with Docker Compose. This keeps my dev environment exactly the same as production, so I don't run into any "it works on my machine" issues.
I put Nginx in front of everything to act as a reverse proxy. I'm also using it for zone rate limiting to protect the API from low-level abuse.
Front-end
Since I'm a front-end dev by trade, I wanted to use the latest tools in the React ecosystem.
- Framework: Next.js 16... app router, naturally.
- Styling: Tailwind CSS v4 with light/dark themes established via primitives, semantics and tokens -- this keeps the door open for other themes in the future.
- State management: TanStack Query (I owe you a beer, Tanner). This handles server state caching and background refetching, which makes the app feel responsive and snappy.
- Animation: Motion (formerly Framer Motion) for interactions.
- Component architecture: atomic design methodology (praise be to Brad).
The website runs on Vercel for now, but I'll be Dockerizing and self-hosting it (like the API) if/when I outgrow the free-tier usage limits.
End-to-End Type Safety
The real secret sauce is in packages/nidobin-types.
Because both the API and the website import from this shared package, I get end-to-end type safety. If I change the shape of a User object in the database schema, the API knows about it. If the API response changes, the website build will fail immediately, telling me exactly where the issue is.
It eliminates an entire class of bugs where the front-end expects a field that the back-end no longer sends. It’s like having a safety net that spans the entire stack.
Conclusion
There's a lot that goes into a project like this, despite how simple it may seem. Leave a comment below or drop me a line at hello@nidobin.com if you're inspired to build your own full-stack indieweb project!
Last updated