Cursor Agent Mode Case Study: Fintech Startup Refactors Express.js to NestJS in 3 Days
How a Fintech Startup Used Cursor Agent Mode to Migrate a Legacy Express.js API to NestJS in 72 Hours
When PayStream Labs — a Series A fintech startup processing 50,000 daily transactions — decided their monolithic Express.js API was bottlenecking feature delivery, they faced a familiar dilemma: rebuild from scratch or incrementally refactor. Their engineering team of four chose a third path — leveraging Cursor Agent Mode to orchestrate a full migration to NestJS in just three working days. This case study breaks down their exact workflow, tooling configuration, and results.
The Challenge: 47 Route Handlers, Zero Type Safety
PayStream’s legacy API consisted of 47 Express route handlers spread across 23 files, with no TypeScript, minimal test coverage (12%), and hand-rolled middleware for authentication, rate limiting, and request validation. The team needed to migrate to NestJS to gain dependency injection, modular architecture, and built-in validation — without halting feature development.
Key Constraints
- Three-day migration window before a compliance audit sprint- All existing integration tests must continue to pass- Zero downtime: the new API must be backward-compatible- Only four engineers available, two of whom had never used NestJS
Day 1: Project Scaffolding and Docs Indexing
Step 1 — Initialize the NestJS Project Alongside the Legacy Code
npx @nestjs/cli new paystream-api —strict —skip-git —package-manager pnpm
cp -r ./legacy-express/src/models ./paystream-api/src/shared/models
Step 2 — Configure Cursor Docs Indexing for Codebase-Aware Context
The team indexed both the legacy codebase and NestJS documentation directly in Cursor to give Agent Mode full context.
- Open Cursor Settings → Features → Docs- Click **Add new doc** and enter https://docs.nestjs.com- Add a second entry: https://docs.nestjs.com/openapi/introduction- Wait for indexing to complete (status shown in the Docs panel)With docs indexed, Agent Mode can reference NestJS decorators, module patterns, and Swagger integration without hallucinating outdated APIs.
Step 3 — Use Agent Mode for Multi-File Module Generation
The team opened Cursor’s Agent Mode (Ctrl+I → select Agent) and issued structured prompts referencing the legacy code:
@legacy-express/src/routes/transactions.js @legacy-express/src/middleware/auth.js
Refactor this Express transaction router into a NestJS module with:
- TransactionsController with all route handlers preserved
- TransactionsService extracting business logic
- DTOs with class-validator decorators matching the existing req.body shapes
- AuthGuard replacing the auth middleware
Preserve all HTTP status codes and response shapes exactlyAgent Mode generated five files simultaneously:
transactions.module.ts,transactions.controller.ts,transactions.service.ts,create-transaction.dto.ts, andauth.guard.ts— all correctly wired with NestJS decorators and imports.
Day 2: Automated Test Generation and Bulk Migration
Step 4 — Generate Tests with Agent Mode
For each migrated module, the team prompted Agent Mode to produce tests referencing the existing integration test patterns:
@paystream-api/src/transactions/transactions.controller.ts
@legacy-express/tests/transactions.test.js
Generate NestJS unit tests and e2e tests for TransactionsController.
Use Jest and @nestjs/testing. Mirror every assertion from the legacy test file.
Mock TransactionsService. Include edge cases for invalid DTOs.
Agent Mode produced both transactions.controller.spec.ts and transactions.e2e-spec.ts, referencing the Test.createTestingModule pattern from the indexed NestJS docs.
Step 5 — Batch-Migrate Remaining Modules
Using Cursor’s multi-file edit capability, the team migrated all 23 files in batches of 4-5 route files per Agent Mode session. The key technique was including the already-migrated modules as context so Agent Mode maintained consistent patterns:
@paystream-api/src/transactions/transactions.module.ts
@legacy-express/src/routes/accounts.js
@legacy-express/src/routes/webhooks.js
@legacy-express/src/routes/compliance.js
Migrate these three Express routers to NestJS modules following
the exact same patterns as the transactions module. Maintain
consistent error handling, guard usage, and DTO validation.
Step 6 — Run the Full Test Suite
cd paystream-api
pnpm test -- --coverage
pnpm test:e2eResult: 94% test coverage on the migrated codebase, up from 12% on the legacy API.
Day 3: Integration, Validation, and Deployment
Step 7 — Wire Up the App Module and Verify Backward Compatibility
// src/app.module.ts
import { Module } from ‘@nestjs/common’;
import { TransactionsModule } from ’./transactions/transactions.module’;
import { AccountsModule } from ’./accounts/accounts.module’;
import { WebhooksModule } from ’./webhooks/webhooks.module’;
import { ComplianceModule } from ’./compliance/compliance.module’;
import { AuthModule } from ’./auth/auth.module’;
@Module({
imports: [
AuthModule,
TransactionsModule,
AccountsModule,
WebhooksModule,
ComplianceModule,
],
})
export class AppModule {}
Step 8 — Generate Swagger Documentation via Agent Mode
@paystream-api/src/transactions/transactions.controller.ts
Add Swagger decorators to all endpoints using @nestjs/swagger.
Use @ApiTags, @ApiOperation, @ApiResponse, and @ApiBody.
Reference the indexed NestJS OpenAPI docs for correct decorator usage.
Results Summary
| Metric | Before (Express.js) | After (NestJS) |
|---|---|---|
| Route handlers | 47 (untyped JS) | 47 (typed TS with DTOs) |
| Test coverage | 12% | 94% |
| Files manually written | — | ~15% of total output |
| Migration duration | — | 3 days (4 engineers) |
| API documentation | None | Full Swagger/OpenAPI |
| Post-deploy incidents | — | 0 in first 30 days |
.cursorrules file in your project root with instructions like Always use constructor injection. Never use any decorators. Prefer readonly properties in services. Agent Mode reads this file automatically.- **Index internal docs:** If your team has an internal API design guide or Notion page, add it to Cursor's Docs index for context-aware generation that matches your team's conventions.- **Chunk large migrations:** Never migrate more than 5 route files per Agent Mode session. Context quality degrades with overly large prompts.- **Review diffs, not files:** After Agent Mode generates code, use Cursor's inline diff view (visible in the Agent Mode panel) to accept or reject changes file by file.
## Troubleshooting Common Issues
| Problem | Cause | Solution |
|---|---|---|
| Agent Mode generates Express-style code instead of NestJS | Legacy files dominating context window | Reduce legacy file references; pin a NestJS example module as primary context |
| DTO validation not triggering at runtime | Missing global validation pipe | Add app.useGlobalPipes(new ValidationPipe()) in main.ts |
| Docs indexing stuck or incomplete | URL returns JS-rendered content | Use direct documentation URLs that return static HTML; retry indexing |
| Circular dependency errors after migration | Agent Mode wired modules bidirectionally | Use forwardRef(() => ModuleName) or restructure shared logic into a common module |
| Tests fail with "Cannot determine a GraphQL input type" | Missing class-transformer reflect metadata | Ensure reflect-metadata is imported at the top of your test setup file |
Can Cursor Agent Mode handle migrations for codebases larger than 50 files?
Yes, but the key is chunking. Agent Mode works best when given 3-7 files of context per session. For large codebases, establish a reference pattern with your first module migration, then batch the remaining modules in groups. Teams have reported successful migrations of 200+ file codebases using this incremental approach over 1-2 weeks.
Does Cursor’s Docs Indexing support private or internal documentation?
Cursor’s Docs Indexing supports any URL that returns accessible HTML content. For private documentation behind authentication, you can copy relevant sections into a local markdown file in your project and reference it with the @filename syntax in Agent Mode prompts. The .cursorrules file is another excellent place to encode internal conventions that Agent Mode should follow.
How does Agent Mode differ from Cursor’s standard Cmd+K inline editing?
Cmd+K (inline edit) operates on a single file selection and is ideal for targeted, localized changes. Agent Mode operates across your entire project context — it can create, modify, and delete multiple files in a single operation, run terminal commands, and iteratively fix errors. For migrations and refactors that touch many files simultaneously, Agent Mode is significantly more effective because it maintains architectural coherence across all generated files.