Skip to main content
← Back to articles

Legacy PHP to NestJS Migration Story

Strangling admin APIs first, shared database phase, and team retraining.

Nestlancer Editorial

Share

Strangling admin APIs first, shared database phase, and team retraining. The team prioritized reversible migrations while migrating on a Nestlancer-style stack—gateway at the edge, domain NestJS services, Prisma on Postgres, async work through RabbitMQ, and media on Backblaze B2.

Where we started

The team inherited freelancer marketplace core still on Laravel endpoints. The triggering incident was clear: typed mobile clients could not trust inconsistent error shapes. Leadership funded the refactor when customer-visible latency and support load rose together—not when the diagram looked messy.

Architecture before

  • Single deploy artifact coupling unrelated domains
  • Synchronous cross-module HTTP with partial timeouts
  • Mixed read/write traffic on one database primary
  • Ad-hoc file storage complicating virus scan and CDN caching

Migration timeline

Discovery

Mapped freelancer marketplace core still on Laravel endpoints and inventoried which routes could move behind the gateway without user-visible changes.

Strangler cutover

Routed new traffic through NestJS handlers while legacy paths drained over 5 weeks.

Reliability hardening

Added gateway, Prisma, outbox with explicit SLOs owned by a 8-engineer platform squad.

Stabilize and learn

Ran game days for DLQ replay, replica lag failover, and presigned upload expiry edge cases.

Patterns we applied

  • API gateway centralized auth, OpenAPI, and aggregation for mobile clients.
  • Prisma migrations shipped expand/contract to avoid hard downtime windows.
  • Transactional outbox kept Postgres commits and RabbitMQ publishes consistent.

Code sketch from the cutover

@Injectable()
export class PostsRepository {
  constructor(
    @Inject('PRISMA_WRITE') private write: PrismaClient,
    @Inject('PRISMA_READ') private read: PrismaClient,
  ) {}

  listPublished() {
    return this.read.post.findMany({ where: { status: 'PUBLISHED' } });
  }
}

Results

  • migrated listing APIs module-by-module behind strangler routes
  • On-call pages for queue backlog fell after outbox lag dashboards went live
  • Product teams could ship blog and portfolio changes without redeploying payments
  • Support tickets citing 'stale listings' dropped once read paths moved to replicas

Retrospective checklist

  • Game day DLQ replay documented with ordering notes
  • Replica lag runbook tested in staging monthly
  • Presigned upload TTL aligned with mobile retry policy
  • Gateway error envelope consistent across all domain services
  • Post-incident templates link to dashboards—not screenshots

Lesson

Microservices did not arrive on day one. The team earned splits by proving operational ownership per domain—not by copying a reference diagram. The Nestlancer patterns above were adopted only after metrics justified the coordination cost.

Comments

Loading comments…

Related posts