nest-crud-query
v1.0.8
Published
Sistema de gestión de **Usuarios** y **Direcciones** construido con **NestJS** y **TypeORM**, que implementa una relación **One-to-One (Uno a Uno)** siguiendo buenas prácticas de arquitectura y mantenibilidad.
Readme
User & Address Management System
Sistema de gestión de Usuarios y Direcciones construido con NestJS y TypeORM, que implementa una relación One-to-One (Uno a Uno) siguiendo buenas prácticas de arquitectura y mantenibilidad.
📋 Descripción General
El sistema define que un Usuario posee una única Dirección.
- El Usuario es el propietario de la relación (contiene la clave foránea).
- La relación está configurada para manejar cascadas y eliminación automática de registros huérfanos.
🧱 Estructura de Entidades
1️⃣ Entidad User
📍 Ubicación: src/user/entities/user.entity.ts
Características clave:
- Propietario de la relación (
@JoinColumn()) - Cascada habilitada (
cascade: true) - Eliminación de huérfanos (
orphanedRowAction: 'delete')
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(() => Address, (a) => a.user, {
cascade: true,
orphanedRowAction: 'delete',
})
@JoinColumn()
address: Address;
}2️⃣ Entidad Address
📍 Ubicación: src/address/entities/address.entity.ts
Características clave:
- Define la relación inversa hacia
User - No contiene la columna física de la relación
- Evita dependencias circulares
🚀 Características Principales
🔗 Relación Unidireccional Controlada
Al colocar @JoinColumn() únicamente en la entidad User:
- Se evita dependencia circular en TypeORM
- La tabla
usercontiene la columnaaddressId - El esquema de base de datos se mantiene limpio
🗑️ Gestión de Registros Huérfanos
Funcionalidad avanzada mediante orphanedRowAction: 'delete':
- Si actualizas un usuario y estableces
address: null - TypeORM detecta que la dirección ya no tiene dueño
- ✅ La dirección se elimina físicamente de la base de datos
🛠️ Uso del Servicio
Para que las cascadas funcionen correctamente, no se recomienda usar update() directamente.
Utiliza preload() + save():
// Ejemplo en UserService
async update(id: number, updateUserDto: UpdateUserDto) {
const user = await this.userRepository.preload({
id,
...updateUserDto,
});
if (!user) throw new NotFoundException();
return this.userRepository.save(user);
}⚙️ Tecnologías Utilizadas
- NestJS – Framework backend para Node.js
- TypeORM – ORM para TypeScript
- PostgreSQL / MySQL – Según el driver configurado
📦 Configuración del Módulo
El proyecto utiliza Módulos Dinámicos mediante CreateCrudModule para reducir boilerplate y generar CRUDs automáticamente.
Implementación del Módulo de Usuario
📍 Ubicación: src/user/user.module.ts
import { Module } from '@nestjs/common';
import { CreateCrudModule } from 'crud-query/src/module';
import { User } from './entity';
@Module({})
export class UserModule extends CreateCrudModule(User) {}⚙️ ¿Cómo funciona esta estructura?
🔄 Inyección Automática
CreateCrudModule(User) registra internamente:
TypeOrmModule.forFeature([User])- Servicio genérico
- Controlador CRUD
📡 Endpoints Generados
GET /user– Listado con filtrosPOST /user– Crear usuario + direcciónPATCH /user/:id– Actualización parcialDELETE /user/:id– Eliminación (lógica si se configura)
🧩 Extensibilidad
Puedes sobrescribir controladores y servicios cuando necesites lógica personalizada.
🔌 Ejemplo de Payload (POST / PATCH)
Gracias a la cascada, puedes crear un usuario y su dirección en una sola petición:
{
"name": "Juan Pérez",
"address": {
"street": "Avenida Siempre Viva 742"
}
}✅ Requisitos Previos
Asegúrate de:
- Configurar
TypeOrmModule.forRoot()enAppModule - Importar
UserModuleenAppModule
🛠️ Personalización de Lógica (Override)
Cuando el CRUD genérico no es suficiente, puedes extender Servicios y Controladores.
1️⃣ Servicio Personalizado
📍 Ubicación: src/address/service.ts
export default class AddressService extends CrudQueryService<Address> {
constructor(@InjectRepository(Address) repo: Repository<Address>) {
super(repo);
}
async create(dto: any) {
console.log('Lógica personalizada antes de crear la dirección');
return super.create(dto);
}
}2️⃣ Controlador Personalizado
📍 Ubicación: src/address/controller.ts
const ControllerCrud = CreateCrudController('address');
export default class AddressController extends ControllerCrud {
constructor(private readonly addressService: AddressService) {
super(addressService);
}
@Get('stats')
async getStats() {
return { total: 100 };
}
}3️⃣ Registro en el Módulo
@Module({})
export class AddressModule extends CreateCrudModule(
Address,
'address',
AddressController,
AddressService,
) {}🔄 Flujo de Datos
- Request → AddressController
- Controller → Lógica personalizada o genérica
- Service → Reglas de negocio
- Database → Repositorio TypeORM
🎯 Beneficios de esta Arquitectura
- ✨ Código limpio y mínimo
- 🧠 Tipado fuerte con TypeScript
- 🔧 Fácil mantenimiento y escalabilidad
- 🚀 Heredas mejoras automáticamente al actualizar
crud-query
📌 Este README describe una arquitectura pensada para crecer sin sacrificar claridad ni control.
