A full-stack property management application built with Clean Architecture principles, featuring maintenance tracking, expense management, and vendor coordination across a portfolio of rental properties.
This project follows Clean Architecture with strict separation of concerns:
apps/backend/src/
├── core/ # Pure domain models (no external dependencies)
├── domain/ # Abstract interfaces (repositories, services)
├── application/ # Use cases (pure business logic, fully testable)
├── infrastructure/ # Concrete implementations (Prisma, Bcrypt, JWT)
├── presentation/ # HTTP layer (controllers, middleware, routes)
├── container.ts # Dependency injection (inversify)
└── server.ts # Entry point (imports reflect-metadata FIRST)
Key Principles:
- Domain entities never import from infrastructure or presentation
- Use cases depend only on domain interfaces, never concrete implementations
- All dependencies injected via inversify container
- Changing from Prisma to MongoDB requires zero changes to domain/application layers
- Node.js + Express - REST API
- TypeScript - Type safety
- Prisma - Database ORM (local dev)
- Flyway - Migration management (production)
- PostgreSQL - Primary database
- inversify - Dependency injection
- bcrypt - Password hashing
- JWT - Authentication
- Jest - Testing
- Vue 3 - UI framework
- Vite - Build tool
- Pinia - State management
- Vue Router - Client-side routing
- Axios - HTTP client with JWT interceptors
- TypeScript - Type safety
- Docker - Containerization
- Railway - Hosting platform
- GitHub Actions - CI/CD
- Redis - Caching (planned)
- Node.js 18+
- npm 9+
- Docker & Docker Compose (for containerized setup)
- PostgreSQL (for local non-Docker setup)
-
Clone the repository
git clone <repository-url> cd upkeep-io
-
Install dependencies
npm install
-
Set up environment variables
# Backend cp apps/backend/.env.example apps/backend/.env # Edit apps/backend/.env with your database credentials # Frontend cp apps/frontend/.env.example apps/frontend/.env
-
Run database migrations
npm run migrate:dev --workspace=apps/backend
Run all services with a single command:
docker-compose up- Backend API: http://localhost:3000
- Frontend: http://localhost:5173
- PostgreSQL: localhost:5432
- Redis: localhost:6379
Run backend and frontend separately:
# Terminal 1 - Backend
npm run dev:backend
# Terminal 2 - Frontend
npm run dev:frontend# Run all tests
npm test
# Run unit tests only (use cases with mocked repositories)
npm run test:unit
# Run integration tests
npm run test:integration
# Watch mode
npm run test:watch --workspace=apps/backend# Build all workspaces
npm run build
# Build backend only
npm run build:backend
# Build frontend only
npm run build:frontendupkeep-io/
├── apps/
│ ├── backend/ # Express API
│ │ ├── src/
│ │ │ ├── application/ # Use cases (CreateUser, Login, CreateProperty)
│ │ │ ├── domain/ # Interfaces (IUserRepository, IPasswordHasher)
│ │ │ ├── infrastructure/ # Implementations (PrismaUserRepository, BcryptPasswordHasher)
│ │ │ ├── presentation/ # Controllers, routes, middleware
│ │ │ ├── container.ts # DI configuration
│ │ │ └── server.ts # Entry point
│ │ ├── prisma/ # Database schema
│ │ ├── Dockerfile
│ │ └── package.json
│ └── frontend/ # Vue 3 app
│ ├── src/
│ │ ├── api/ # Axios client with JWT interceptor
│ │ ├── stores/ # Pinia stores (auth)
│ │ ├── router/ # Vue Router with protected routes
│ │ ├── views/ # Pages (Login, Signup, Dashboard)
│ │ ├── App.vue
│ │ └── main.ts
│ ├── Dockerfile
│ ├── nginx.conf
│ └── package.json
├── libs/
│ ├── domain/ # Shared entities (User, Property) and errors
│ ├── validators/ # Zod schemas for input validation
│ └── auth/ # JWT utilities
├── migrations/ # Flyway SQL migrations for production
├── .github/workflows/ # CI/CD pipelines
├── docker-compose.yml
├── package.json # Workspace root
└── tsconfig.json # Root TypeScript config with path aliases
- User - System users (property owners)
- Property - Rental properties with addresses and metadata
- MaintenanceWork - Central aggregate for tracking work, costs, and travel (planned)
- WorkPerformer - Tracks who did work (owner, family, vendor) and time (planned)
- Vendor - Reusable vendors (HVAC, plumbers, etc.) (planned)
- Receipt - Material purchases for tax deduction tracking (planned)
- TravelActivity - Mileage tracking for IRS deductions (planned)
- RecurringService - Scheduled vendor services (planned)
See property-management-domain-model.md for detailed specifications.
Interactive API Documentation: http://localhost:3000/api-docs
Full Swagger/OpenAPI 3.0 documentation is available at /api-docs when running the backend server. The interactive UI allows you to:
- Browse all endpoints with detailed descriptions
- View request/response schemas auto-generated from Zod validators
- Test endpoints directly with "Try it out" functionality
- Authenticate with JWT tokens for protected endpoints
POST /api/auth/signup- Create new user accountPOST /api/auth/login- Login and receive JWT token
POST /api/properties- Create a new propertyGET /api/properties- List all properties for authenticated userGET /api/properties/:id- Get property by IDPUT /api/properties/:id- Update propertyDELETE /api/properties/:id- Delete property
POST /api/leases- Create a new lease with lessees and occupantsGET /api/leases- List all leasesGET /api/leases/:id- Get lease by ID with detailsPUT /api/leases/:id- Update leaseDELETE /api/leases/:id- Delete lease (soft delete)GET /api/leases/property/:propertyId- List leases for a propertyPOST /api/leases/:id/lessees- Add lessee to leaseDELETE /api/leases/:id/lessees/:personId- Remove lessee from leasePOST /api/leases/:id/occupants- Add occupant to leaseDELETE /api/leases/:id/occupants/:occupantId- Remove occupant from lease
For complete details, examples, and to test the API interactively, visit /api-docs
DATABASE_URL=postgresql://user:password@localhost:5432/upkeep_dev
JWT_SECRET=your-secret-key-change-this-in-production
JWT_EXPIRES_IN=7d
PORT=3000
NODE_ENV=development
CORS_ORIGIN=http://localhost:5173VITE_API_URL=http://localhost:3000/api-
Create Railway Project with services:
- PostgreSQL database
- Backend (Node.js)
- Frontend (Static)
- Redis (optional)
-
Configure Environment Variables in Railway dashboard
-
Set up GitHub Secrets:
RAILWAY_TOKENRAILWAY_DATABASE_URLRAILWAY_DATABASE_USERRAILWAY_DATABASE_PASSWORD
-
Push to main branch - GitHub Actions will:
- Run tests
- Apply Flyway migrations
- Deploy backend and frontend
# Create and apply migration
npm run migrate:dev --workspace=apps/backend
# Check migration status
npm run migrate:status --workspace=apps/backend
# Regenerate Prisma client
npm run generate --workspace=apps/backend- Update
apps/backend/prisma/schema.prisma - Run
npm run migrate:devto generate Prisma migration - Copy generated SQL to
migrations/VXXX__description.sql - Commit and push - GitHub Actions runs Flyway
- Unit Tests - Test use cases with mocked repositories (no database, no Express)
- Integration Tests - Test full request flow with real database
- Use cases in
application/layer should have 100% coverage - Controllers in
presentation/layer should be thin
Example testable use case:
// Fully testable without database
const mockRepo = { findById: jest.fn(), save: jest.fn() };
const useCase = new CreateUserUseCase(mockRepo, mockHasher, mockTokenGen);
const result = await useCase.execute({ email, password, name });Target: $100/month
- Railway services: ~$35-45/month
- PostgreSQL, Redis, Backend, Frontend
- Leaves $55-65 for scaling
- Create feature branch from
main - Implement changes following Clean Architecture
- Write unit tests for use cases
- Ensure all tests pass:
npm test - Create pull request
Proprietary - All rights reserved