Building LIA (Part 1 Implementation): Clean Architecture and Argon2id in a Real Fastify + Prisma Registration Flow
LIA is a hyperlocal employability platform I'm building for an isolated coastal district in Brazil — think fixed retail jobs, gigs, and a reputation layer, all matched by proximity instead of routed through a national job board. This post is about the implementation: the actual folder structure, the real RegisterUserUseCase, and the Argon2id decision — pulled straight from the repository, not reconstructed from memory. The Clean Architecture folder structure LIA's backend is organized in four layers, and the direction of dependency is non-negotiable: outer layers depend on inner layers, never the other way around. backend/src/ ├── domain/ │ ├── entities/ │ └── repositories/ # interfaces only ├── application/ │ ├── dto/ │ └── use-cases/ ├── infrastructure/ │ ├── database/ │ └── repositories/ # Prisma implementations ├── presentation/ │ ├── controllers/ │ └── routes/ └── shared/ └── errors/ Let's walk through the registration feature end to end, following that exact order. Domain — the entity and the repository contract The User entity is a plain interface. No decorators, no ORM annotations, no framework leaking in: typescript// domain/entities/user.ts export interface User { id: string; name: string; email: string; password: string; createdAt: Date; updatedAt: Date; } The repository is defined as a contract, not an implementation. The domain doesn't know — and doesn't care — whether it's backed by PostgreSQL, an in-memory map, or something else entirely: typescript// domain/repositories/user.repository.ts import { RegisterUserDTO } from '../../application/dto/register-user.dto.js'; export interface UserRepository { create(data: RegisterUserDTO): Promise<{ id: string; name: string; email: string; createdAt: Date; updatedAt: Date; }>; findByEmail(email: string): Promise<{ id: string; name: string; email: string; password: string; createdAt: Date; updatedAt: Date; } | null>; } Notice create() never returns the password hash. That's not an accident — it's the same "strip