FleetKeep Migration Plan: NestJS/MongoDB to .NET Core/PostgreSQL

Table of Contents

  1. Overview
  2. Technology Stack
  3. Solution Architecture
  4. Phase 1: Project Setup & Core Infrastructure
  5. Phase 2: Domain Layer & Database
  6. Phase 3: Authentication & Authorization
  7. Phase 4: Core Business Modules
  8. Phase 5: Supporting Features
  9. Phase 6: Background Jobs & Scheduling
  10. Phase 7: External Integrations
  11. Phase 8: Testing & Quality Assurance
  12. Phase 9: Deployment & Migration
  13. Appendix

1. Overview

1.1 What We’re Doing

We are migrating the FleetKeep backend from:

1.2 Why This Migration

1.3 Key Principles

  1. Feature parity first - Every existing feature must work in the new system
  2. Keep it simple - Don’t over-engineer; junior devs should understand the code
  3. Test everything - No feature ships without tests
  4. Incremental progress - Small, reviewable pull requests

1.4 What’s Being Migrated

CategoryCount
API Endpoints~200
Database Tables60+
Background Jobs11 cron tasks + 2 queue processors
Email Templates23 types
External Integrations3 (Stripe, AWS, LeeTrans)

2. Technology Stack

2.1 Core Framework

PurposeTechnologyWhy
Framework.NET 8Latest LTS, best performance
APIASP.NET Core Web APIIndustry standard
ORMEntity Framework Core 8Best .NET ORM, great tooling
DatabasePostgreSQL 15+Robust, supports LTREE for hierarchies

2.2 NuGet Packages to Install

<!-- Core -->
<PackageReference Include="MediatR" Version="12.*" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.*" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.*" />

<!-- Database -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.*" />
<PackageReference Include="EFCore.NamingConventions" Version="8.*" />

<!-- Authentication -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.*" />
<PackageReference Include="BCrypt.Net-Next" Version="4.*" />

<!-- Background Jobs -->
<PackageReference Include="Hangfire.AspNetCore" Version="1.*" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.*" />

<!-- AWS & Stripe -->
<PackageReference Include="AWSSDK.S3" Version="3.*" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.*" />
<PackageReference Include="Stripe.net" Version="43.*" />

<!-- Utilities -->
<PackageReference Include="StackExchange.Redis" Version="2.*" />
<PackageReference Include="Serilog.AspNetCore" Version="8.*" />
<PackageReference Include="Sentry.AspNetCore" Version="4.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
<PackageReference Include="ClosedXML" Version="0.102.*" />
<PackageReference Include="CsvHelper" Version="31.*" />

<!-- Testing -->
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="Moq" Version="4.*" />
<PackageReference Include="FluentAssertions" Version="6.*" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.*" />

3. Solution Architecture

3.1 Project Structure

FleetKeep/
├── src/
│   ├── FleetKeep.Api/              # Controllers, Middleware
│   ├── FleetKeep.Application/      # Business Logic, CQRS
│   ├── FleetKeep.Domain/           # Entities, Enums
│   ├── FleetKeep.Infrastructure/   # DB, External Services
│   └── FleetKeep.Shared/           # Utilities
├── tests/
│   ├── FleetKeep.UnitTests/
│   └── FleetKeep.IntegrationTests/
└── docker/

3.2 Layer Responsibilities

FleetKeep.Domain

Pure C# classes representing the business domain. No dependencies.

FleetKeep.Application

Business logic, CQRS Handlers, DTOs. Depends on Domain.

FleetKeep.Infrastructure

Database, File Storage, Email, Payments. Depends on Application & Domain.

FleetKeep.Api

Controllers, Middleware. Thin layer. Depends on Application & Infrastructure.


4. Phase 1: Project Setup & Core Infrastructure

4.1 Create Solution

dotnet new sln -n FleetKeep
# (Create projects commands omitted for brevity, standard .NET CLI)

4.2 Application Settings

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=fleetkeep;Username=postgres;Password=pw",
    "Redis": "localhost:6379"
  },
  "JwtSettings": { "Secret": "super-secret-key-min-32-chars", "Issuer": "FleetKeep" }
}

4.4 Base Classes

BaseEntity.cs

public abstract class BaseEntity {
    public Guid Id { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

4.5 Deliverables Checklist


5. Phase 2: Domain Layer & Database

5.1 & 5.2 Key Entities

Implement entities like Company, User, Asset, Inspection, etc.

public class Asset : BaseEntity, ITenantEntity, IAuditable, ISoftDeletable {
    public Guid CompanyId { get; set; }
    public string Name { get; set; }
    public AssetStatus Status { get; set; }
    // ... Navigation properties
}

5.5 ApplicationDbContext

public class ApplicationDbContext : DbContext {
    // DB Sets...
    public DbSet<Asset> Assets => Set<Asset>();

    protected override void OnModelCreating(ModelBuilder builder) {
        // Global Query Filters for Multi-tenancy & Soft Delete
        builder.Entity<Asset>().HasQueryFilter(e => 
            e.CompanyId == _tenantService.CompanyId && !e.IsDeleted);
    }
}

5.7 Deliverables Checklist


6. Phase 3: Authentication & Authorization

6.2 Current User Service

Implements ICurrentUserService to retrieve claims (UserId, CompanyId) from HttpContext.

6.4 Permission Filter Service

This replicates the complex NestJS constructPermissionFilter() logic.

public async Task<PermissionFilterDto> ConstructPermissionFilterAsync(Guid userId, Guid companyId) {
    // Logic to build allowed/excluded asset types and IDs
}

6.5 Query Filter Extensions

public static IQueryable<Asset> ApplyAuthorizationFilters(this IQueryable<Asset> query, ICurrentUserService user) {
    return query
        .ApplyLocationFilter(user)
        .ApplyPermissionFilter(user.PermissionFilter);
}

6.8 Deliverables Checklist


7. Phase 4: Core Business Modules

7.1 CQRS Pattern

Each feature (e.g., Assets) has Commands (Create, Update) and Queries (Get, List).

7.2 Example: Assets Module

GetAssetsQueryHandler.cs

public async Task<PaginatedList<AssetDto>> Handle(GetAssetsQuery request, CancellationToken ct) {
    var query = _context.Assets
        .ApplyAuthorizationFilters(_currentUser) // Critical!
        .AsQueryable();
    
    // Apply search/sort...
    return await query.ToPaginatedListAsync(...);
}

7.5 Deliverables Checklist


8. Phase 5: Supporting Features

Features


9. Phase 6: Background Jobs & Scheduling

9.1 Hangfire

Replace NestJS Bull queues with Hangfire.

RecurringJob.AddOrUpdate<ScheduledJobsService>(
    "send-overdue-notifications",
    job => job.SendOverdueNotificationsAsync(),
    Cron.Hourly);

9.5 Deliverables Checklist


10. Phase 7: External Integrations

Stripe & API Keys


11. Phase 8: Testing & Quality Assurance

CategoryWhat to TestTools
Unit TestsHandlers, ValidatorsxUnit, Moq
Integration TestsAPI Endpoints, DBTestcontainers

12. Phase 9: Deployment & Migration

12.1 Docker & Migration

Use Docker Compose for the stack. Create a script to migrate data from MongoDB JSON exports to PostgreSQL.

12.3 Deployment Checklist


13. Appendix

13.3 Useful Commands

# Migrations
dotnet ef migrations add InitialCreate -s src/FleetKeep.Api
dotnet ef database update -s src/FleetKeep.Api

# Run
dotnet run --project src/FleetKeep.Api

Summary

This migration plan provides a complete roadmap for transitioning FleetKeep from NestJS/MongoDB to .NET Core/PostgreSQL. The key points are:

  1. Clean Architecture with clear layer separation
  2. CQRS pattern with MediatR for organized business logic
  3. EF Core Global Query Filters for automatic multi-tenancy and soft delete
  4. Extension methods for authorization filtering (location, asset type, permissions)
  5. Hangfire for background job processing
  6. Comprehensive testing at unit and integration levels

Follow the phases in order, and ensure each phase’s deliverables are complete before moving to the next. This will ensure a smooth migration with minimal risk.