Skip to content

Repository files navigation

UserManager API

Reto opcional de construcción de una API REST de gestión de usuarios.

Descripción

Este proyecto tiene como objetivo construir paso a paso una API REST capaz de gestionar usuarios, autenticación, roles, seguridad, base de datos e integración con un frontend.

Instalación

Instalar dependencias:

npm install

Arrancar en modo desarrollo:

npm run dev

La API se ejecutará inicialmente en:

http://localhost:3000

Endpoints disponibles

Health

GET /api/health

Respuesta esperada:

{
  "status": "ok",
  "message": "UserManager API funcionando",
  "timestamp": "2026-01-01T10:00:00.000Z"
}

Endpoints simulados de usuarios

GET /api/users
GET /api/users/:id
POST /api/users
PATCH /api/users/:id
DELETE /api/users/:id

Estos endpoints todavía no trabajan con datos reales. De momento sirven para practicar métodos HTTP, rutas, parámetros y body.

Rutas temporales de debug

Estas rutas se han creado para practicar cómo leer datos de una petición HTTP.

POST /api/debug/body
GET /api/debug/params/:id
GET /api/debug/query
GET /api/debug/headers
PATCH /api/debug/users/:id

Más adelante estas rutas podrán eliminarse, ya que no forman parte de la API final.

Endpoints de usuarios

GET /api/users

Devuelve el listado de usuarios cargados en memoria.

Respuesta de ejemplo:

{
  "message": "Listado de usuarios",
  "total": 3,
  "data": []
}

Endpoints de usuarios

GET /api/users
GET /api/users/:id

GET /api/users/:id

Devuelve un usuario concreto a partir de su ID.

Respuesta correcta:

{
  "message": "Usuario encontrado",
  "data": {
    "id": 1,
    "name": "Ana García",
    "email": "ana@email.com",
    "role": "USER",
    "isActive": true
  }
}

Posibles errores:

{
  "error": "El ID debe ser un número"
}
{
  "error": "Usuario no encontrado"
}

Crear usuario

POST /api/users

Body:

{
  "name": "María López",
  "email": "maria@email.com",
  "password": "123456"
}

Respuesta correcta:

{
  "message": "Usuario creado correctamente",
  "data": {
    "id": 4,
    "name": "María López",
    "email": "maria@email.com",
    "role": "USER",
    "isActive": true
  }
}

Posibles errores:

{
  "error": "name, email y password son obligatorios"
}
{
  "error": "La contraseña debe tener al menos 6 caracteres"
}
{
  "error": "El email ya está registrado"
}

Actualizar usuario

PATCH /api/users/:id

Permite modificar parcialmente los datos de un usuario.

Campos permitidos:

name
email
isActive

Body de ejemplo:

{
  "name": "Ana Martínez"
}

Respuesta correcta:

{
  "message": "Usuario actualizado correctamente",
  "data": {
    "id": 1,
    "name": "Ana Martínez",
    "email": "ana@email.com",
    "role": "USER",
    "isActive": true
  }
}

Posibles errores:

{
  "error": "El ID debe ser un número",
  "received": "abc"
}
{
  "error": "Usuario no encontrado",
  "id": 999
}
{
  "error": "Debes enviar al menos un campo para actualizar"
}
{
  "error": "El email ya está registrado"
}

Eliminar o desactivar usuario

DELETE /api/users/:id

En este proyecto, esta ruta no borra físicamente el usuario. Realiza un borrado lógico marcando:

isActive = false

Respuesta correcta:

{
  "message": "Usuario desactivado correctamente",
  "data": {
    "id": 1,
    "name": "Ana García",
    "email": "ana@email.com",
    "role": "USER",
    "isActive": false
  }
}

Posibles errores:

{
  "error": "El ID debe ser un número",
  "received": "abc"
}
{
  "error": "Usuario no encontrado",
  "id": 999
}

Validaciones básicas

La API realiza validaciones manuales antes de crear o actualizar usuarios.

Validaciones principales:

  • name debe ser un texto no vacío.
  • email debe ser un texto no vacío.
  • password debe ser un texto no vacío.
  • password debe tener al menos 6 caracteres.
  • email debe contener @.
  • isActive debe ser boolean.

Ejemplo de error:

{
  "error": "El nombre debe ser un texto no vacío"
}

Validación de email

La API normaliza los emails antes de guardarlos o compararlos.

Proceso aplicado:

  • trim()
  • toLowerCase()
  • Validación básica de formato.
  • Comprobación de duplicados.

Ejemplo:

"  USUARIO@EMAIL.COM  " -> "usuario@email.com"

Si se intenta crear o actualizar un usuario con un email ya existente, la API responde:

{
  "error": "El email ya está registrado"
}

Código:

409 Conflict

Códigos de estado utilizados

La API utiliza códigos HTTP para indicar el resultado de cada petición.

Código Significado Uso en el proyecto
200 OK Consulta, actualización o desactivación correcta
201 Created Usuario creado correctamente
400 Bad Request Datos incorrectos o incompletos
404 Not Found Usuario no encontrado
409 Conflict Email duplicado

Ejemplo de error 404:

{
  "error": "Usuario no encontrado",
  "id": 999
}

Ejemplo de error 409:

{
  "error": "El email ya está registrado"
}

Gestión centralizada de errores

La API utiliza un middleware global para devolver errores con un formato común.

Formato general:

{
  "error": "Mensaje del error",
  "statusCode": 400,
  "details": {},
  "path": "/api/users/abc",
  "method": "GET",
  "timestamp": "2026-01-01T10:00:00.000Z"
}

También se ha añadido un middleware para rutas no encontradas:

GET /api/ruta-inventada

Respuesta:

{
  "error": "Ruta no encontrada",
  "statusCode": 404
}

Persistencia

Hasta el día 15, la API trabaja con usuarios en memoria.

Esto significa que los datos se pierden al reiniciar el servidor.

A partir de la siguiente fase, prepararemos una base de datos para guardar los usuarios de forma persistente.

Tabla principal prevista:

users

Campos principales:

id
name
email
password_hash
role
is_active
created_at
updated_at

Base de datos con Docker Compose

El proyecto utiliza Docker Compose para levantar PostgreSQL y Adminer.

Servicios:

postgres  -> Base de datos PostgreSQL
adminer   -> Interfaz web para consultar la base de datos

Comando para arrancar:

docker compose up -d

Comando para parar:

docker compose down

Adminer:

http://localhost:8080

Datos de conexión:

Sistema: PostgreSQL
Servidor: postgres
Usuario: usermanager
Contraseña: usermanager_password
Base de datos: usermanager_db

Modelo persistente User

El modelo principal del proyecto será User.

Campos principales:

id
name
email
passwordHash
role
isActive
createdAt
updatedAt

Reglas importantes:

email único
passwordHash nunca se devuelve
role por defecto USER
isActive por defecto true
createdAt y updatedAt automáticos

Este diseño se convertirá más adelante en un modelo Prisma.

ORM y acceso a datos

El proyecto usará Prisma como ORM principal para comunicarse con PostgreSQL.

Se ha elegido Prisma porque:

Encaja bien con TypeScript.
Permite definir modelos claros.
Incluye migraciones.
Genera un cliente tipado.
Permite explorar datos con Prisma Studio.

Flujo previsto:

API Express → Repository → Prisma → PostgreSQL

SQL directo, TypeORM y Sequelize se han considerado como alternativas, pero no serán el camino principal del reto.

Prisma

El proyecto utilizará Prisma como ORM principal para comunicarse con PostgreSQL.

Instalación:

npm install -D prisma
npm install @prisma/client

Inicialización:

npx prisma init --datasource-provider postgresql

Archivos importantes:

prisma/schema.prisma
.env
.env.example

Validar esquema:

npx prisma validate

Generar cliente:

npx prisma generate

Modelo Prisma User

El modelo principal del proyecto será User.

enum Role {
  USER
  ADMIN
}

model User {
  id           Int      @id @default(autoincrement())
  name         String
  email        String   @unique
  passwordHash String
  role         Role     @default(USER)
  isActive     Boolean  @default(true)
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
}

Reglas principales:

email único
passwordHash obligatorio
role por defecto USER
isActive por defecto true
createdAt automático
updatedAt automático al modificar

Migraciones con Prisma

El proyecto usa Prisma Migrate para versionar la estructura de la base de datos.

Primera migración:

npx prisma migrate dev --name init

Esto genera:

prisma/migrations/<timestamp>_init/migration.sql

Y crea en PostgreSQL:

User
_prisma_migrations

La tabla User almacena los usuarios de la aplicación.

La tabla _prisma_migrations guarda el historial interno de migraciones de Prisma.

Prisma Studio

Prisma Studio permite explorar visualmente los datos de la base de datos.

Comando:

npx prisma studio

O mediante script:

npm run prisma:studio

URL habitual:

http://localhost:5555

Uso en el proyecto:

Comprobar tablas.
Revisar usuarios.
Ver datos iniciales del seed.
Comprobar cambios realizados desde la API.
Detectar errores de persistencia.

Prisma Studio es una herramienta de desarrollo. La gestión real de usuarios se hará desde la API.

Seed de datos iniciales

El proyecto incluye un seed para crear usuarios iniciales.

Archivo:

prisma/seed.ts

Ejecutar seed:

npx prisma db seed

O mediante script:

npm run prisma:seed

Usuarios iniciales:

Email Role Estado
admin@email.com ADMIN activo
user@email.com USER activo
inactive@email.com USER inactivo

Nota:

Los passwordHash son temporales hasta implementar bcrypt en la fase de seguridad.

Consultas básicas con Prisma

La API ya puede consultar usuarios desde PostgreSQL usando Prisma Client.

Archivo de cliente compartido:

src/prisma.ts

Este proyecto usa Prisma 7 con adapter PostgreSQL:

import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client";

Rutas temporales de prueba:

Método Ruta Acción
GET /api/debug/prisma/users Listar usuarios
GET /api/debug/prisma/users-active Listar usuarios activos
GET /api/debug/prisma/users/:id Buscar usuario por ID
POST /api/debug/prisma/users Crear usuario

Regla:

Las respuestas no deben incluir passwordHash.

Separación de rutas

El proyecto empieza a organizarse por capas.

Primera carpeta creada:

src/routes/

Archivos actuales:

src/routes/health.routes.ts
src/routes/debug-prisma.routes.ts

server.ts monta los routers:

app.use("/api/health", healthRouter);
app.use("/api/debug/prisma", debugPrismaRouter);

Esta separación permite que server.ts quede más limpio y que el proyecto pueda crecer hacia una arquitectura con controladores, servicios y repositorios.

Controladores

El proyecto empieza a separar la lógica HTTP en controladores.

Carpeta creada:

src/controllers/

Archivos actuales:

src/controllers/health.controller.ts
src/controllers/user.controller.ts

Ejemplo de ruta simplificada:

debugPrismaRouter.get("/users", getUsers);

La lógica de la petición queda en el controlador:

getUsers
getUserById
createDebugUser

Esta separación prepara el proyecto para añadir servicios y repositorios.

Servicios

El proyecto ya incluye una capa de servicios.

Carpeta creada:

src/services/

Archivo principal:

src/services/user.service.ts

Los servicios contienen lógica de negocio como:

  • Validar datos.
  • Normalizar email.
  • Comprobar usuario inexistente.
  • Gestionar email duplicado.
  • Crear usuarios.

El controlador queda más limpio y llama a funciones como:

getUsersService()
getUserByIdService(id)
createDebugUserService(req.body)

En este punto, el servicio todavía usa Prisma directamente. En el siguiente paso se añadirá una capa de repositorios.

Repositorios

El proyecto ya incluye una capa de repositorios.

Carpeta creada:

src/repositories/

Archivo principal:

src/repositories/user.repository.ts

Funciones actuales:

  • findAllUsers
  • findActiveUsers
  • findUserById
  • findUserByEmail
  • createUser

Flujo actual de la API: Route → Controller → Service → Repository → Prisma → PostgreSQL.

El servicio ya no usa Prisma directamente. Ahora el acceso a datos queda concentrado en el repositorio.

CRUD persistente de usuarios

La API ya tiene rutas reales para gestionar usuarios con PostgreSQL y Prisma.

Rutas principales:

Método Ruta Acción
GET /api/users Listar usuarios
GET /api/users/:id Consultar usuario
POST /api/users Crear usuario
PATCH /api/users/:id Actualizar usuario
DELETE /api/users/:id Desactivar usuario

Flujo interno:

Route → Controller → Service → Repository → Prisma → PostgreSQL

El borrado es lógico:

DELETE /api/users/:id → isActive = false

La API nunca devuelve passwordHash.

Autenticación

El proyecto ya incluye una primera ruta de autenticación para registro de usuarios.

Ruta:

POST /api/auth/register

Body esperado:

{
  "name": "Usuario Nuevo",
  "email": "nuevo@email.com",
  "password": "123456"
}

Respuesta correcta:

201 Created.

Reglas:

  • El email no puede estar repetido.
  • La contraseña se guarda como passwordHash usando bcrypt.
  • El usuario se registra con role USER por defecto.
  • El usuario se registra activo por defecto.
  • passwordHash nunca se devuelve al cliente.

Todavía no se genera token JWT. Eso se añadirá más adelante.

Login

Ruta:

POST /api/auth/login

Body esperado:

{
  "email": "user@email.com",
  "password": "user123"
}

Respuesta correcta:

200 OK

Respuesta aproximada:

{
  "message": "Login correcto",
  "data": {
    "user": {
      "id": 2,
      "name": "Usuario Demo",
      "email": "user@email.com",
      "role": "USER",
      "isActive": true
    }
  }
}

Reglas:

  • El email debe existir.
  • La contraseña debe coincidir con el passwordHash.
  • El usuario debe estar activo.
  • passwordHash nunca se devuelve.
  • Todavía no se devuelve token JWT.

Login

Ruta:

POST /api/auth/login

Body esperado:

{
  "email": "user@email.com",
  "password": "user123"
}

Respuesta correcta:

{
  "message": "Login correcto",
  "data": {
    "user": {
      "id": 2,
      "name": "Usuario Demo",
      "email": "user@email.com",
      "role": "USER",
      "isActive": true
    },
    "token": "eyJhbGciOiJIUzI1NiIs..."
  }
}

El token es un JWT firmado con JWT_SECRET.

Variables necesarias:

JWT_SECRET="cambia_esta_clave_en_produccion"
JWT_EXPIRES_IN="1h"

Reglas:

  • El token se genera solo si el login es correcto.
  • El token contiene userId, email y role.
  • El token no contiene password ni passwordHash.
  • Todavía no se usa para proteger rutas.

Middleware de autenticación

El proyecto ya puede verificar tokens JWT enviados por el cliente.

Formato de la cabecera:

Authorization: Bearer <token>

Middleware creado:

src/middlewares/auth.middleware.ts

El middleware:

  • Lee la cabecera Authorization.
  • Comprueba que el formato sea Bearer.
  • Verifica el token con JWT_SECRET.
  • Guarda los datos autenticados en req.user.
  • Bloquea la petición si el token falta o es inválido.

Ruta de prueba:

GET /api/auth/me

Rutas protegidas:

/api/users/*

Todavía no se aplican permisos por rol. Eso se trabajará en el siguiente paso.

Roles y permisos

El proyecto distingue entre dos roles:

USER
ADMIN

Reglas principales:

Ruta Permiso
GET /api/users Solo ADMIN
POST /api/users Solo ADMIN
GET /api/users/me Usuario autenticado
GET /api/users/:id ADMIN o el propio usuario
PATCH /api/users/:id ADMIN o el propio usuario
DELETE /api/users/:id Solo ADMIN

Middlewares creados:

requireRole
requireSelfOrAdmin

Códigos importantes:

401 → No autenticado
403 → Autenticado, pero sin permiso

Arquitectura proyecto

src/
├── controllers/          # Gestión de peticiones HTTP y respuestas
│   ├── auth.controller.ts
│   ├── health.controller.ts
│   └── user.controller.ts
├── errors/               # Manejo centralizado de excepciones
│   └── AppError.ts
├── middlewares/          # Interceptores de autenticación y autorización
│   ├── auth.middleware.ts
│   └── role.middleware.ts
├── repositories/         # Acceso a base de datos con Prisma
│   └── user.repository.ts
├── routes/               # Enrutamiento de endpoints
│   ├── auth.routes.ts
│   ├── health.routes.ts
│   └── user.routes.ts
├── services/             # Lógica de negocio y validaciones
│   ├── auth.service.ts
│   └── user.service.ts
├── types/                # Tipos e interfaces globales
│   └── auth.types.ts
├── utils/                # Funciones auxiliares y helpers
│   ├── jwt.utils.ts
│   ├── parse.utils.ts
│   ├── password.utils.ts
│   └── string.utils.ts
├── prisma.ts             # Cliente de conexión Prisma
└── server.ts             # Servidor Express principal

Documentación del reto

About

API RESTful desarrollada con TypeScript, Express y Prisma ORM para gestión completa de usuarios, autenticación mediante JWT, contraseñas con Bcrypt y persistencia en PostgreSQL con Docker Compose.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages