Building LandlordOS: A Modern Property Management System
I recently launched LandlordOS, a property management system designed for small-to-medium landlords. Here's the story behind it and the technical decisions that went into building it.
The Problem
After talking to several landlords managing 5-50 rental units, I noticed a common pain point: they were stuck between two extremes:
- Enterprise software ($200+/month) with features they'd never use
- Google Sheets that become unwieldy and error-prone
They needed something in between - simple enough to use daily, but robust enough to handle real business operations.
Tech Stack
I chose modern, production-ready tools:
- Next.js 16 with App Router for server-side rendering
- TypeScript for type safety across the entire codebase
- PostgreSQL (Neon) for reliable data storage
- Drizzle ORM for type-safe database queries
- NextAuth.js v5 for authentication
- Vercel for deployment and edge runtime
Core Features
Property & Unit Management
- Track multiple properties with detailed information
- Manage individual units within properties
- Monitor occupancy status and availability
Tenant Management
- Store tenant information and contact details
- Track lease agreements with start/end dates
- Link tenants to specific units
Payment Tracking
- Record payments with amount, date, and method
- View payment history per tenant
- Track outstanding balances
Maintenance Requests
- Submit and track maintenance issues
- Priority levels (Low/Medium/High/Critical)
- Status tracking (Pending/In Progress/Completed/Cancelled)
Security Measures
Security was a top priority. I implemented:
1. CSRF Protection
Double-submit cookie pattern that works in Edge Runtime:
\`typescript
// lib/csrf.ts
export function generateCsrfToken(): string {
return randomBytes(32).toString('hex');
}
export function validateCsrfToken(token: string, cookieToken: string): boolean {
return token === cookieToken;
}
`\
2. Rate Limiting
5 attempts per 15 minutes on authentication endpoints:
\`typescript
// lib/rate-limit.ts
const authLimiter = rateLimit({
interval: 15 * 60 * 1000, // 15 minutes
uniqueTokenPerInterval: 500,
});
await authLimiter.check(5, identifier); // 5 requests
`\
3. Input Sanitization
XSS and SQL injection prevention:
\typescript
// lib/sanitize.ts
export function sanitizeHtml(input: string): string {
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
\\
4. Secure Middleware
Public routes bypass authentication checks to prevent database calls in Edge Runtime:
\`typescript
// middleware.ts
const publicRoutes = ['/', '/pricing', '/features', '/about'];
const isPublicRoute = publicRoutes.includes(request.nextUrl.pathname);
if (isPublicRoute) {
return NextResponse.next(); // Skip auth entirely
}
`\
Testing Strategy
Quality was ensured through comprehensive testing:
Integration Tests (Vitest)
31 tests covering:
- User authentication (signup, login, password validation)
- Property management (validation, creation)
- Tenant & unit management
- Payment recording
- Maintenance requests
- Security (XSS prevention, SQL injection protection)
Result: 100% pass rate
E2E Tests (Playwright)
8 tests on the live site:
- Page load verification
- User signup flow
- Login functionality
- Navigation
- Core feature testing
- Stripe checkout integration
Result: 100% pass rate on production
Deployment Process
The deployment was interesting - I initially tried Cloudflare Pages, but Next.js App Router with Server Components isn't compatible with static deployments. After also trying Netlify with the same issue, I went with Vercel which natively supports Next.js.
Environment variables configured:
-
DATABASE_URL- Neon PostgreSQL connection -
NEXTAUTH_SECRET- Session encryption -
NEXTAUTH_URL- Auth callback URL -
STRIPE_SECRET_KEY- Payment processing -
NEXT_PUBLIC_GA_MEASUREMENT_ID- Analytics
Lessons Learned
1. Edge Runtime Limitations
Middleware runs in Edge Runtime, which doesn't support all Node.js APIs. I had to be careful about what code ran where.
2. Middleware Auth Pitfall
Initially, my middleware was calling auth() for ALL routes, including public ones. This caused crashes because database drivers can't run in Edge Runtime. The fix was to add an early return for public routes.
3. Type Safety is Worth It
TypeScript caught numerous bugs during development. The upfront cost of typing everything paid off immediately.
4. Test Coverage Matters
The 100% test pass rate requirement forced me to think through edge cases I would have otherwise missed.
What's Next
Future enhancements:
- Document upload for leases/agreements
- Automated rent reminders via email
- Financial reporting and analytics
- Mobile app (React Native)
- Multi-language support
Try It Out
Live demo: https://landlordos.vercel.app
I'd love to hear feedback from:
- Property managers using it in real scenarios
- Developers interested in the architecture
- Anyone building similar SaaS products
What would you add to a property management system?
Built as part of the Genesis website pipeline project - an automated system for building and launching SaaS applications.
Top comments (0)