The first real commit in this repo is literally titled “Initial commit,” and the second one is “feat: added inital project build setup with NX.” Yes, “inital” is misspelled. I noticed it about four commits later and decided it wasn’t worth a rebase to fix a typo in a commit message nobody reads except future me writing this blog post.
Why Nx, and why one repo
I knew going in that Gatify would have a NestJS backend and an Angular frontend, and that they’d eventually need to share TypeScript types for things like VirtualKey and ModelDeployment. I could have kept them as two separate repos and published a small shared package to npm, or copy-pasted interfaces between them and kept them in sync by hand. Neither of those appealed to me. Nx gives me one dependency graph, one set of lint/test/build commands that work the same way regardless of which project you’re touching, and a place to put shared libraries later without needing a second repo or a private registry.
I did not, however, front-load the library structure. At this stage the workspace was just two apps:
apps/
api/ NestJS 11
api-e2e/ e2e scaffold for the API
web/ Angular 22, standalone components, zonelessNo libs/ folder yet. That came later, in the dashboard chapter, once actual duplication showed up and I had a concrete reason to extract something. I’ve worked on codebases where someone set up a beautiful libs/shared-ui, libs/shared-utils, libs/shared-types structure on day one, before there was a single line of business logic to justify it, and it just becomes empty ceremony. I’d rather feel the pain of duplication once and then fix it deliberately.
The actual tool versions
Concretely, this is what got scaffolded, and what’s still in package.json as of this writing:
- Nx 23.2.0
- NestJS 11 (
@nestjs/core,@nestjs/common,@nestjs/platform-express) - Angular 22.1, generated with
--standaloneand--zoneless - Node 24.x, npm as the package manager (no pnpm or yarn, just because npm workspaces were enough for what I needed)
- Tailwind CSS 4.3.3 on the frontend, configured through
@tailwindcss/postcss
The API’s webpack.config.js is close to the Nx default for a NestJS app, generated by @nx/webpack’s NxAppWebpackPlugin:
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, 'dist'),
clean: true,
},
plugins: [
new NxAppWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
generatePackageJson: false,
sourceMap: true,
}),
],
};I’ll be honest, I didn’t hand-write most of this. This is Nx’s generated scaffold for a NestJS app, and I left it mostly alone. The interesting decisions in this project are almost all application-level, not build-tooling-level, and I didn’t see a reason to fight the generator’s defaults on day one.
Local infrastructure
Gatify needs Postgres for durable data and Redis for rate-limit counters. For local development that’s a plain compose.yaml at the repo root:
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-gatify}
POSTGRES_USER: ${POSTGRES_USER:-gatify}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gatify_local}
ports:
- '${POSTGRES_PORT:-5432}:5432'
volumes:
- gatify-postgres-data:/var/lib/postgresql/data
redis:
image: redis:8-alpine
restart: unless-stopped
ports:
- '${REDIS_PORT:-6379}:6379'
volumes:
- gatify-redis-data:/dataThat’s it for stage one: npm install, docker compose up -d, and two blank apps that build and lint cleanly through Nx. No database schema, no auth, no gateway logic. I wanted that boring baseline provable before adding anything real, mostly because I’ve been burned before by chasing down a build issue three features deep that turned out to be a scaffold problem from day one.
Next: the actual database schema, and the Prisma 7 config changes that immediately made “boring baseline” a little less boring than I expected.