Day#0: Building a Modular System from Scratch

mariusz
mariusz
20 posts
2 followers

Starting a new software project from the ground up is always an exercise in balancing ambition with reality. Over the past two days, I’ve been laying the foundations of a modular monorepo architecture using Next.js for the frontend and NestJS for the backend — and, as always, theory quickly collided with the stubbornness of actual tooling.

Day 0: Getting the Monorepo Right

Turborepo was the tool of choice for managing builds and tasks across frontend, backend, and shared libraries. Setting it up wasn’t particularly difficult, but integrating it with the realities of ESM-based packages (especially in combination with ts-node and TypeORM) introduced some friction.

Lesson learned: If you’re going full ESM, commit to it across the entire project. Mixing ESM and CJS creates obscure and time-consuming debugging sessions.

Day 1: Infrastructure is Everything

This day focused heavily on developer experience: linting, formatting, commit policies, CI configuration, and integration of type sharing between frontend and backend.

Commitlint & Conventional Commits

One of the biggest time sinks came from making commitlint work in an ESM context. Most tutorials and setups assume CommonJS by default. Migrating the configuration to native ESM syntax (with .mjs and "type": "module" where necessary) resolved things, but not before a deep dive into Node.js internals and odd import behavior.

Tip: If you want commitlint with ESM, don’t trust outdated blog posts. Go straight to the issue tracker and README of the tool.

ESLint & Prettier Across Boundaries

Sharing linting and formatting rules across backend and frontend seems straightforward — until it isn’t. Managing plugin versions, environment-specific rules (Node vs browser), and Next.js quirks turned this into a mini-project of its own.

Outcome: A shared ESLint base config, extended appropriately in each application. Format-on-save works, linting is strict, and Prettier enforces a consistent style.

Shared Types? Yes, but…

A major design goal was sharing TypeScript types across frontend and backend. In practice, this required careful TypeScript configuration (base tsconfig, consistent paths and baseUrl) and proper import hygiene.

What worked: Alias mappings with "@tin-types/*" and clear folder structure in libs/types.

What didn’t: Trying to use anything without full integration in both build tools and IDEs — everything needs to speak the same language.

Frontend: Next.js with Tailwind CSS

Next.js with the new App Router and Tailwind CSS setup was mostly smooth. But Turbopack (the default in dev mode) caused unpredictable issues including broken live reload. Falling back to Webpack (NEXT_PRIVATE_TURBOPACK=0) restored sanity.

Backend: NestJS + PostgreSQL + TypeORM

NestJS remains a reliable and productive backend framework. PostgreSQL integration via TypeORM was trivial — until it came time to configure dynamic DataSourceOptions in ESM style. Again, most guides assume CommonJS.

What’s Next?

There’s still a lot to do:

  • Fine-tune live reload and proxy for DX
  • Test deploys to cloud platforms
  • Begin implementation of business logic and user flows

TL;DR

  • Monorepo with Turbo, Next.js, NestJS, PostgreSQL
  • Linting, formatting, shared types, CI
  • ESM causes real-world headaches
  • Turbopack not ready for prime-time (in this use case)

It’s still early days, but the structure is holding. Most important: the system is bootstrapped with maintainability in mind.


Opublikowano

w

,

przez

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *