{"id":51578341,"url":"https://github.com/yolgesanchez/task_manager","last_synced_at":"2026-07-11T03:01:48.333Z","repository":{"id":368292589,"uuid":"1283439296","full_name":"YolgeSanchez/task_manager","owner":"YolgeSanchez","description":"Over-engineered task manager API built to learn Clean Architecture, JWT auth with RS256, Prisma ORM, and professional testing strategies in Node.js + TypeScript.","archived":false,"fork":false,"pushed_at":"2026-07-07T02:44:55.000Z","size":393,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-07T04:20:48.900Z","etag":null,"topics":["clean-architecture","docker","express","jwt","nodejs","postgresql","prisma","rest-api","typescript","vitest"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/YolgeSanchez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-28T23:40:37.000Z","updated_at":"2026-07-07T02:47:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/YolgeSanchez/task_manager","commit_stats":null,"previous_names":["yolgesanchez/task_manager"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/YolgeSanchez/task_manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YolgeSanchez%2Ftask_manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YolgeSanchez%2Ftask_manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YolgeSanchez%2Ftask_manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YolgeSanchez%2Ftask_manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/YolgeSanchez","download_url":"https://codeload.github.com/YolgeSanchez/task_manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YolgeSanchez%2Ftask_manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35349264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-architecture","docker","express","jwt","nodejs","postgresql","prisma","rest-api","typescript","vitest"],"created_at":"2026-07-11T03:01:46.689Z","updated_at":"2026-07-11T03:01:48.324Z","avatar_url":"https://github.com/YolgeSanchez.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Task Manager API\n\nUna API REST deliberadamente sobre-diseñada con fines de aprendizaje, implementando los principios de **Clean Architecture** en un backend con Node.js + TypeScript. El objetivo nunca fue construir un gestor de tareas simple — fue entender cómo los profesionales estructuran, prueban y mantienen backends de nivel empresarial.\n\n---\n\n## ¿Por qué sobre-diseñado?\n\nLa mayoría de los tutoriales te muestran _qué_ construir. Este proyecto fue construido para entender _cómo_ lo construyen los profesionales. Cada decisión — desde la estructura de carpetas hasta el manejo de errores y la estrategia de tests — refleja lo que encontrarías en el codebase de una institución financiera.\n\n---\n\n## Stack tecnológico\n\n| Capa                   | Tecnología                                         |\n| ---------------------- | -------------------------------------------------- |\n| Runtime                | Node.js 25 (`--env-file` nativo, `--watch` nativo) |\n| Lenguaje               | TypeScript                                         |\n| Framework              | Express                                            |\n| ORM                    | Prisma                                             |\n| Base de datos          | PostgreSQL (via Docker)                            |\n| Autenticación          | JWT con RS256 (via `jose`)                         |\n| Hashing de contraseñas | bcrypt                                             |\n| Validación             | Zod                                                |\n| Testing                | Vitest                                             |\n| Linting                | ESLint v9 (flat config)                            |\n| Formateo               | Prettier                                           |\n\n---\n\n## Clean Architecture\n\nTodo el codebase sigue Clean Architecture — un patrón donde el código se organiza en capas concéntricas y las **dependencias solo apuntan hacia adentro**. Las capas internas no saben nada de las externas.\n\n```\n┌─────────────────────────────────────────┐\n│         Frameworks \u0026 Drivers            │  ← Express, PostgreSQL, Prisma, bcrypt\n│  ┌───────────────────────────────────┐  │\n│  │      Interface Adapters           │  │  ← Controllers, Repositories, Middlewares\n│  │  ┌─────────────────────────────┐  │  │\n│  │  │         Use Cases           │  │  │  ← Reglas de negocio de la aplicación\n│  │  │  ┌───────────────────────┐  │  │  │\n│  │  │  │       Entities        │  │  │  │  ← Reglas de negocio empresariales\n│  │  │  └───────────────────────┘  │  │  │\n│  │  └─────────────────────────────┘  │  │\n│  └───────────────────────────────────┘  │\n└─────────────────────────────────────────┘\n           ↑ regla de dependencia: solo apunta hacia adentro\n```\n\n### Qué significa esto en la práctica\n\n- Las entidades `Task`, `User` y `Project` tienen **cero dependencias externas** — sin Express, sin Prisma, sin librerías\n- Los use cases solo dependen de **interfaces** (ports), nunca de implementaciones concretas\n- PostgreSQL puede ser reemplazado por MongoDB sin tocar una sola línea de lógica de negocio\n- Cada use case es completamente testeable **sin base de datos**\n\n---\n\n## Estructura del proyecto\n\n```\nsrc/\n├── domain/                     ← El corazón de la aplicación\n│   ├── entities/               ← Objetos de negocio con sus reglas\n│   │   ├── Task.ts\n│   │   ├── User.ts\n│   │   └── Project.ts\n│   ├── repositories/           ← Interfaces (ports) — sin implementaciones\n│   │   ├── TaskRepository.ts\n│   │   ├── UserRepository.ts\n│   │   └── ProjectRepository.ts\n│   ├── services/               ← Interfaces de servicios de dominio\n│   │   ├── PasswordHasher.ts\n│   │   └── TokenService.ts\n│   ├── errors/                 ← Errores de dominio con códigos HTTP\n│   └── types/\n│       └── types.ts\n│\n├── application/                ← Un use case = un archivo = una responsabilidad\n│   ├── dtos/                   ← Tipos de entrada/salida compartidos\n│   ├── errors/                 ← Errores de capa de aplicación\n│   └── use-cases/\n│       ├── auth/               ← SignInUseCase, SignUpUseCase\n│       ├── task/               ← CRUD + FindAllByUserId + FindAllByProjectId\n│       ├── user/               ← CRUD\n│       └── project/            ← CRUD + gestión de miembros + gestión de tareas\n│\n├── infrastructure/             ← Todo lo externo\n│   ├── repositories/           ← Implementaciones Prisma de las interfaces de dominio\n│   ├── services/               ← BcryptPasswordHasher, JoseTokenService\n│   ├── http/\n│   │   ├── controllers/        ← Traducen HTTP ↔ use cases\n│   │   ├── middlewares/        ← Auth, error handler, validadores\n│   │   ├── routes/             ← Definición de rutas\n│   │   ├── schemas/            ← Schemas de validación con Zod\n│   │   └── containers/         ← Composition root — ensambla todo\n│   └── libs/\n│       └── prisma.ts           ← Singleton del cliente Prisma\n│\n├── app.ts                      ← Configuración de Express\n└── server.ts                   ← Punto de entrada\n```\n\n---\n\n## Modelo de dominio\n\n### Entidades y sus reglas\n\n**Task**\n\n- No puede tener nombre vacío\n- El deadline debe ser posterior a la fecha de creación\n- Las transiciones de estado se validan internamente\n\n**User**\n\n- Username: alfanumérico + guión bajo, mínimo 3 caracteres\n- Contraseña: mínimo 8 caracteres\n- Email: formato válido requerido\n- Soft delete — los usuarios nunca se eliminan físicamente de la base de datos\n\n**Project**\n\n- Nombre mínimo 3 caracteres\n- El dueño no puede ser eliminado como miembro\n- Mantiene internamente los IDs de miembros e IDs de tareas\n- Soft delete — igual que los usuarios\n\n---\n\n## Autenticación\n\nAutenticación JWT usando **RS256** (claves asimétricas) — el estándar de la industria para aplicaciones financieras.\n\n- La clave privada firma el token (solo en el servidor)\n- La clave pública lo verifica (puede compartirse con otros servicios)\n- Tokens almacenados en **cookies HttpOnly** — no accesibles desde JavaScript, protegidos contra XSS\n- `sameSite: strict` — protegido contra CSRF\n\n---\n\n## Rutas de la API\n\n### Autenticación\n\n```\nPOST   /api/auth              Registro\nPOST   /api/auth/signin       Inicio de sesión\n```\n\n### Usuarios (requiere autenticación)\n\n```\nGET    /api/users             Obtener todos los usuarios\nGET    /api/users/:id         Obtener usuario por id\nPATCH  /api/users/:id         Actualizar usuario\nDELETE /api/users/:id         Eliminar usuario (soft delete)\n```\n\n### Tareas (requiere autenticación)\n\n```\nGET    /api/tasks                          Obtener todas las tareas del usuario autenticado\nGET    /api/tasks/:id                      Obtener tarea por id\nGET    /api/projects/:projectId/tasks      Obtener todas las tareas de un proyecto\nPOST   /api/tasks                          Crear tarea\nPATCH  /api/tasks/:id                      Actualizar tarea\nDELETE /api/tasks/:id                      Eliminar tarea\n```\n\n### Proyectos (requiere autenticación)\n\n```\nGET    /api/projects                              Obtener todos los proyectos del usuario autenticado\nGET    /api/projects/:id                          Obtener proyecto por id\nPOST   /api/projects                              Crear proyecto\nPATCH  /api/projects/:id                          Actualizar nombre del proyecto\nDELETE /api/projects/:id                          Eliminar proyecto (soft delete)\nPOST   /api/projects/:id/members/:memberId        Agregar miembro\nDELETE /api/projects/:id/members/:memberId        Eliminar miembro\nPOST   /api/projects/:id/tasks/:taskId            Agregar tarea al proyecto\nDELETE /api/projects/:id/tasks/:taskId            Quitar tarea del proyecto\n```\n\n---\n\n## Estrategia de testing\n\nLos tests están divididos en dos categorías con comandos separados para que los tests unitarios nunca esperen a la base de datos.\n\n```\ntests/\n├── domain/                     ← Reglas de entidades y lógica de negocio\n│   ├── User.test.ts\n│   ├── Task.test.ts\n│   └── Project.test.ts\n├── application/                ← Use cases con repositorios falsos en memoria (sin DB)\n│   ├── auth.use-cases.test.ts\n│   ├── task.use-cases.test.ts\n│   ├── user.use-cases.test.ts\n│   └── project.use-cases.test.ts\n└── integration/                ← Base de datos PostgreSQL real\n    ├── setup.ts\n    ├── helpers/\n    │   └── cleanDatabase.ts\n    ├── repositories/\n    │   ├── PrismaUserRepository.test.ts\n    │   ├── PrismaTaskRepository.test.ts\n    │   └── PrismaProjectRepository.test.ts\n    └── api/\n        └── TaskRoutes.test.ts\n```\n\n```bash\nnpm run test:unit         # Tests de dominio + aplicación (rápido, sin DB)\nnpm run test:integration  # Tests de repositorios + API (requiere Docker)\nnpm run test:run          # Todos los tests una vez\nnpm test                  # Modo watch\n```\n\nLos tests unitarios usan **repositorios falsos en memoria** que implementan las mismas interfaces que los reales — los use cases no tienen idea de que no están hablando con una base de datos real. Esto es el patrón Ports \u0026 Adapters en acción.\n\n---\n\n## Cómo correrlo localmente\n\n### Requisitos\n\n- Node.js 25+\n- Docker Desktop\n\n### Configuración\n\n```bash\n# Instalar dependencias\nnpm install\n\n# Levantar bases de datos\ndocker compose up -d\n\n# Generar cliente Prisma\nnpx prisma generate\n\n# Correr migraciones\nnpx prisma migrate deploy\nDATABASE_URL=\"Aqui va tu TEST_URL de el .env\" npx prisma migrate deploy\n\n# Generar claves RS256\nnode scripts/generate-keys.ts\n# Copiar el output a .env PRIVATE_KEY y .env PUBLIC_KEY\n```\n\n# Iniciar servidor de desarrollo\n\nnpm run dev\n\n### Variables de entorno (`.env`)\n\n```\n\nPRIVATE_KEY=\"Key content here\"\nPUBLIC_KEY=\"Key content here\"\nPORT=3000\nDATABASE_URL=\"postgresql://root:root@localhost:5432/taskdb\"\nDATABASE_TEST_URL=\"postgresql://root:root@localhost:5433/taskdb_test\"\nNODE_ENV=\"development\"\n\n```\n\n---\n\n## Patrones clave utilizados\n\n**Ports \u0026 Adapters** — el dominio define interfaces, la infraestructura las implementa. El dominio nunca importa desde la infraestructura.\n\n**Responsabilidad única por Use Case** — `CreateTaskUseCase` hace una sola cosa. `DeleteTaskUseCase` hace una sola cosa. Sin clases service con 15 métodos.\n\n**Defense in Depth** — cada controller verifica la autenticación de forma independiente, incluso detrás del middleware de auth. Un middleware faltante nunca causa una falla de seguridad silenciosa.\n\n**Prevención de enumeración de usuarios** — `SignInUseCase` retorna el mismo `UserNotFoundError` ya sea que el username no exista o que la contraseña sea incorrecta. Un atacante no puede determinar qué usuarios están registrados.\n\n**Soft Delete** — usuarios y proyectos nunca se eliminan físicamente. Se setea `deletedAt` en su lugar, preservando registros de auditoría — un requisito estricto en sistemas financieros.\n\n**DTOs en los límites** — las entidades nunca salen del dominio sin procesar. `toJSON()` controla exactamente qué datos se exponen en las respuestas, manteniendo los detalles de implementación internos.\n\n---\n\n## Deuda técnica conocida\n\n- `findByUsername` y `findByEmail` en `PrismaUserRepository` no filtran por `deletedAt: null` — un usuario eliminado con username o email único bloquea el re-registro con esa credencial. Diferido intencionalmente.\n- `Task` no implementa soft delete — solo hard delete.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyolgesanchez%2Ftask_manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyolgesanchez%2Ftask_manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyolgesanchez%2Ftask_manager/lists"}