A modern, scalable backend API for real estate transactions built with NestJS and PostgreSQL
- User Management - Registration, authentication, and profile management
- Property Listings - Create, manage, and search property listings
- Transaction Tracking - Record and track real estate transactions
- Tax Strategy Suggestions - Store informational, non-binding tax structuring suggestions for transactions
- Document Management - Store and manage property-related documents
- Role-Based Access Control - USER, AGENT, ADMIN roles with route protection
- Clean Architecture - Modular, testable, and maintainable code structure
- CI/CD Ready - Automated testing and deployment pipeline
The application implements comprehensive RBAC with three user roles:
- USER: Default role for registered users. Can create properties and manage their own data.
- AGENT: Can manage properties and assist with transactions.
- ADMIN: Full system access including user management, property administration, and system configuration.
Routes are protected using decorators:
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
@Get('admin/users')
getAllUsers() {
// Only admins can access
}New users are automatically assigned the USER role upon registration.
The application provides secure password reset functionality via email:
- Request Reset: User submits email address
- Token Generation: Secure reset token created (expires in 1 hour)
- Email Delivery: Reset link sent to user's email
- Token Validation: Token verified on password reset
- Password Update: New password hashed and stored
# Request password reset
POST /auth/password-reset/request
{
"email": "[email protected]"
}
# Reset password with token
POST /auth/password-reset/reset
{
"token": "reset-token-here",
"newPassword": "NewSecurePassword123!"
}- Token Expiration: Reset tokens expire after 1 hour
- Single Use: Tokens can only be used once
- Password History: Prevents reuse of recent passwords
- Rate Limiting: Previous tokens invalidated on new request
- Blocked User Protection: No emails sent to blocked accounts
- Node.js >= 18.0.0
- PostgreSQL >= 14
- npm >= 8.0.0
# Install dependencies
npm install
# Copy environment file
cp .env.example .env
# Set up your database URL in .env fileA production Dockerfile is included and docker-compose.yml wires up the
full stack (app, postgres, pgbouncer, redis).
# Build and boot the full stack
docker compose up --build
# Verify container health (GET /healthz returns 200)
curl -fsSL http://localhost:3000/healthz
# Tear down (including volumes)
docker compose down -v- The
appcontainer applies pending Prisma migrations (prisma migrate deploy) viadocker-entrypoint.shbefore startingnode dist/main. - Health checks:
postgres/pgbouncer/redisuse their native probes; theappservice probesGET /healthz. - Override secrets via
.envvariables:POSTGRES_PASSWORD,JWT_SECRET,JWT_REFRESH_SECRET,REDIS_PASSWORD. The JWT secrets must each be at least 32 characters or the app will refuse to boot.
The application uses environment variables for configuration. Copy .env.example to .env and adjust the values as needed.
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Required |
PORT |
Server port | 3000 |
NODE_ENV |
Environment mode | development |
FRONTEND_URL |
Frontend application URL for email links | http://localhost:3000 |
JWT_SECRET |
JWT signing secret | Required |
JWT_REFRESH_SECRET |
JWT refresh token secret | Required |
JWT_ACCESS_EXPIRES_IN |
Access token expiration | 15m |
JWT_REFRESH_EXPIRES_IN |
Refresh token expiration | 7d |
BCRYPT_ROUNDS |
Password hashing rounds | 12 |
PASSWORD_HISTORY_LIMIT |
Password history limit | 5 |
PASSWORD_MIN_LENGTH |
Minimum password length | 8 |
PASSWORD_REQUIRE_UPPERCASE |
Require uppercase in password | true |
PASSWORD_REQUIRE_LOWERCASE |
Require lowercase in password | true |
PASSWORD_REQUIRE_DIGIT |
Require digit in password | true |
PASSWORD_REQUIRE_SPECIAL |
Require special char in password | true |
PASSWORD_SPECIAL_CHARS |
Allowed special characters | !@#$%^&*()_+-=... |
FRONTEND_URL |
Frontend application URL for email links | http://localhost:3000 |
RECAPTCHA_SECRET |
Google reCAPTCHA v3 private key | Required |
CAPTCHA_THRESHOLD |
Minimum reCAPTCHA score to pass | 0.5 |
BASE_URL |
Root URL of this API server | http://localhost:3000 |
API_URL |
Full API base URL for email links | http://localhost:3000/api |
AVATAR_UPLOAD_DIR |
Directory for user avatar uploads | ./uploads/avatars |
AVATAR_MAX_FILE_SIZE |
Max avatar file size in bytes | 5242880 |
CORS_ORIGINS |
Comma-separated allowed origins | http://localhost:3000 |
DEBUG_PII |
Enable PII debugging in auth logs | false |
EMAIL_VERIFICATION_EXPIRES_IN |
Email verification token TTL | 24h |
GOOGLE_CLIENT_ID |
Google OAuth2 client ID | โ |
GOOGLE_CLIENT_SECRET |
Google OAuth2 client secret | โ |
GOOGLE_CALLBACK_URL |
Google OAuth2 callback URL | /api/auth/google/callback |
BLOCKCHAIN_ENABLED |
Enable blockchain integration | true |
BLOCKCHAIN_NETWORK |
Ethereum network | sepolia |
BLOCKCHAIN_RPC_URL |
Ethereum RPC endpoint (validated at boot) | โ |
BLOCKCHAIN_CONTRACT_ADDRESS |
Smart contract address (EIP-55 checksum validated at boot) | โ |
BLOCKCHAIN_PRIVATE_KEY |
Wallet private key for signing (validated at boot) | โ |
BACKUP_STORAGE_PATH |
Directory for DB backup files | ./backups |
PG_DUMP_PATH |
Path to pg_dump binary | pg_dump |
PSQL_PATH |
Path to psql binary | psql |
PROPERTY_IMAGES_UPLOAD_DIR |
Directory for property images | ./uploads/properties |
PROPERTY_IMAGE_MAX_SIZE |
Max property image size in bytes | 10485760 |
PROPERTY_IMAGE_MAX_PER_PROPERTY |
Max images per property | 30 |
GEOCODING_PROVIDER |
Geocoding provider (nominatim/google) | nominatim |
NOMINATIM_BASE_URL |
Nominatim API base URL | https://nominatim.openstreetmap.org |
GEOCODING_USER_AGENT |
User agent for geocoding requests | PropChain-Backend/1.0 |
GEOCODING_TIMEOUT_MS |
Geocoding request timeout (ms) | 5000 |
GOOGLE_GEOCODING_API_KEY |
Google Geocoding API key (optional) | โ |
FRAUD_ALERT_RECIPIENTS |
Comma-separated fraud alert emails | โ |
CACHE_WARMING_ENABLED |
Enable cache warming on startup | false |
CACHE_WARMING_INTERVAL |
Cache warming interval (ms) | โ |
TEST_DATABASE_URL |
PostgreSQL URL for integration tests | โ |
# Generate Prisma Client
npm run db:generate
# Run migrations
npm run migrate
# (Optional) Seed database
npm run db:seed# Development mode
npm run start:dev
# Production mode
npm run build
npm run start:prod# Unit tests
npm test
# Test coverage
npm run test:cov
# Watch mode
npm run test:watchFor database-backed integration tests, set TEST_DATABASE_URL to a dedicated test database. Helper utilities are available in test/database/prisma-test-helpers.ts to clean fixtures and reset seeded state between suites.
src/
โโโ database/ # Database configuration and Prisma service
โโโ users/ # User management module
โโโ properties/ # Property listings module
โโโ app.module.ts # Main application module
โโโ app.controller.ts # App controller with health check
โโโ main.ts # Application entry point
prisma/
โโโ schema.prisma # Database schema
โโโ seed.ts # Database seeding
| Command | Description |
|---|---|
npm run build |
Build the application |
npm run start:dev |
Start in development mode with watch |
npm run start:prod |
Start in production mode |
npm run lint |
Run ESLint with auto-fix |
npm run format |
Format code with Prettier |
npm test |
Run tests |
npm run test:cov |
Run tests with coverage |
npm run migrate |
Run database migrations |
npm run migrate:deploy |
Deploy migrations to production |
npm run db:generate |
Generate Prisma Client |
npm run db:studio |
Open Prisma Studio |
- User - Platform users (buyers, sellers, agents, admins)
- Property - Real estate listings with detailed information
- Transaction - Property transactions with blockchain integration
- Document - Property-related documents and files
- Properties module: src/properties/README.md
- Transactions module: src/transactions/README.md
- Auth & Users: docs/Auth_and_User_APIs.md
Create a .env file based on .env.example:
DATABASE_URL=postgresql://user:password@localhost:5432/propchain
PORT=3000
JWT_SECRET=your-secret-keyThe CI/CD pipeline is configured in .github/workflows/ci.yml:
- Develop branch โ Deploys to staging
- Main branch โ Deploys to production
# Build for production
npm run build
# Run migrations
npm run migrate:deploy
# Start application
npm run start:prodGET /api/health- Application health status
POST /api/users- Create userGET /api/users- List all usersGET /api/users/:id- Get user by IDPUT /api/users/:id- Update userDELETE /api/users/:id- Delete user
POST /api/properties- Create propertyGET /api/properties- List all propertiesGET /api/properties/:id- Get property by IDPUT /api/properties/:id- Update propertyDELETE /api/properties/:id- Delete property
GET /api/transactions/:transactionId/tax-strategies- List tax strategy suggestions for a transactionPOST /api/transactions/:transactionId/tax-strategies- Create a tax strategy suggestionPATCH /api/transactions/:transactionId/tax-strategies/:strategyId- Update a tax strategy suggestion
Tax strategy suggestions are informational only and are not legal or tax advice. See docs/Tax_Strategy_Suggestions.md for usage details.
See CONTRIBUTING.md for contribution guidelines, branch naming conventions, PR expectations, and local test/lint instructions.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
For support, email [email protected] or join our Slack channel
- TypeScript strict mode: The project now enables
strictTypeScript checks. The base config is in tsconfig.json. - Key compiler flags enforced:
noImplicitAny,strictNullChecksand related strict checks are enabled for app builds via tsconfig.app.json. - ESLint rules:
@typescript-eslint/no-explicit-anyis set toerrorand explicit boundary/return types are encouraged via@typescript-eslint/explicit-module-boundary-typesand@typescript-eslint/explicit-function-return-type(set towarn). See .eslintrc.js.
Local checks before committing/pushing:
# Install
npm ci
# Run linter (auto-fixable issues)
npm run lint
# Build to verify TypeScript strict checks
npm run buildCI: A GitHub Actions workflow (.github/workflows/ci.yml) now runs npm run lint and npm run build on pushes and PRs to main to validate the stricter compilation and linting rules.