DEV Community

Peace Melodi
Peace Melodi

Posted on

A Bank Wanted to Use AI to Approve Loans Faster, Here Is What I Would Build Around It in NestJS

A loan officer at a small digital bank was excited about the new model the data team had built. It could score a loan application in under a second, based on income, spending patterns, and repayment history, and approve or reject it automatically. The first week it ran, it worked beautifully. The second week, the model provider had an outage in the middle of the afternoon. Every loan application submitted during that window just hung, waiting on a response that was never coming. Some customers refreshed the page and submitted twice. A few applications that should have been rejected outright ended up approved, because a fallback path someone had written months earlier, and never tested properly, defaulted to approving anything it could not score.

Nobody on the data team had done anything wrong with the model itself. The model was accurate. What was missing was everything around it, the part that decides what happens when the model is slow, wrong, unavailable, or simply not something you should trust blindly with a decision this big.

The real problem is never the model

A bank does not really have an AI problem when it adopts a model for something like loan approval or fraud scoring. It has an integration problem. The model is a service you call, and like any service, it can be slow, it can fail, and it can be confidently wrong. The question that matters is not whether the model is smart. It is what the backend does the moment the model does not behave the way everyone assumed it would.

Enforcing a timeout so a slow model cannot freeze the whole flow

The first guardrail is making sure a call to the model can never hang indefinitely. If the model provider is slow or down, the request should fail fast and fall back to a safe default, not leave the customer staring at a loading screen.

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom, timeout, catchError } from 'rxjs';

@Injectable()
export class LoanScoringService {
  constructor(private readonly httpService: HttpService) {}

  async scoreApplication(applicationId: string, payload: Record<string, unknown>) {
    return firstValueFrom(
      this.httpService.post('https://ai-provider.example.com/score', payload).pipe(
        timeout(2000),
        catchError(() => {
          throw new HttpException(
            'Scoring service unavailable',
            HttpStatus.SERVICE_UNAVAILABLE,
          );
        }),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that the fallback here is an explicit error, not a silent approval. Whatever happens next, defaulting to approving a loan because a service call failed is never an acceptable behavior on its own.

Deciding what fail safe actually means for a loan decision

Once a timeout or failure is caught, something has to happen next, and for a decision this significant, that something should never be an automatic approval. A sensible fail safe path is to route the application to a manual review queue instead.

async handleScoringFailure(applicationId: string) {
  await this.reviewQueueService.enqueue({
    applicationId,
    reason: 'scoring_service_unavailable',
    requiresManualReview: true,
  });

  return {
    status: 'pending_review',
    message: 'Your application is being reviewed manually.',
  };
}
Enter fullscreen mode Exit fullscreen mode

This keeps the customer informed honestly, and it makes sure a technical failure never quietly turns into a financial decision nobody actually made on purpose.

Logging every decision so it can be explained later

A bank will eventually need to explain why a specific customer was approved, rejected, or flagged, sometimes months later, sometimes to a regulator. That means every scoring decision, along with the model's output and the data it was based on, needs to be recorded, not just acted on and discarded.

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('loan_decisions')
export class LoanDecision {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  applicationId: string;

  @Column({ type: 'jsonb' })
  modelInput: Record<string, unknown>;

  @Column({ type: 'jsonb' })
  modelOutput: Record<string, unknown>;

  @Column()
  finalDecision: string;

  @Column({ default: false })
  requiredManualReview: boolean;

  @CreateDateColumn()
  createdAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

With this in place, a decision made in under a second can still be fully reconstructed and explained later, which matters far more in banking than in almost any other kind of application.

Keeping a human in the loop for the decisions that matter most

Not every decision needs a person reviewing it, but the ones with real consequences, a large loan amount, a borderline score, a customer disputing a rejection, should never be finalized by the model alone. NestJS gives you a clean place to enforce that gate, a check that routes certain outcomes to a review queue instead of directly to the customer, based on rules the bank actually agrees with, rather than whatever the model happened to output.

The bigger picture

NestJS does not make an AI model more accurate, and it never should try to. What it gives you is the structure around the model, a place to enforce timeouts, a place to decide what fail safe actually means, a place to log every decision so it can be explained later, and a place to insist a person reviews the decisions that deserve one. That structure is what actually makes an AI model safe enough to use for something as consequential as approving a loan.

If your team is bringing AI into a process that touches real money or real customers, I would be glad to talk through how to build the guardrails around it properly.

I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.

LinkedIn: https://www.linkedin.com/in/melodi-peace-406494368
GitHub: https://github.com/PeaceMelodi

Top comments (0)