NestJS: How to Delete a File after Response is Sent to Client

Updated: December 31, 2023 By: Guest Contributor Post a comment

Introduction

When building applications with NestJS, you may encounter scenarios where a file needs to be served to a client and then deleted from the server. This tutorial guides you through implementing this pattern effectively in your NestJS app using the latest TypeScript syntax.

Using Middleware and Interceptors

One approach to deleting a file after the response is by using middleware and interceptors. You can create a custom interceptor to handle post-response actions.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class FileCleanupInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    return next.handle().pipe(
      tap(() => {
        const response = context.switchToHttp().getResponse();
        const filename = response.locals?.fileToDelete;

        if (filename) {
          const fs = require('fs');
          fs.unlink(filename, (err) => {
            if (err) console.error('Error deleting file:', err);
            else console.log('File deleted:', filename);
          });
        }
      })
    );
  }
}

Serving and Deleting the File in the Controller

Next, you can serve the file in your controller and utilize the `FileCleanupInterceptor` to schedule deletion post-response.

import { Controller, Get, Res, UseInterceptors } from '@nestjs/common';
import { Response } from 'express';
import { FileCleanupInterceptor } from './file-cleanup.interceptor';

@Controller('file')
export class FileController {
  @Get('download')
  @UseInterceptors(FileCleanupInterceptor)
  downloadFile(@Res() res: Response) {
    const file = './temp/file-to-send.pdf';
    res.locals.fileToDelete = file;
    res.download(file, (err) => {
      if (err) {
        res.locals.fileToDelete = null;
        console.error('File download error:', err);
      }
    });
  }
}

Advanced: Using Streams

For more efficiency, especially with larger files, you could leverage Node.js streams and pipe the file to the response object. Once the stream is closed, you can then delete the file.

import { Readable } from 'stream';
import { createReadStream, unlink } from 'fs';
import { join } from 'path';

const filePath = join(__dirname, 'temp', 'file-to-send.pdf');
const fileStream = createReadStream(filePath);

fileStream.on('end', () => {
  unlink(filePath, (err) => {
    if (err) throw err;
    console.log(`${filePath} was deleted`);
  });
});

fileStream.pipe(res);

Cleanup on Application Exit

In a more advanced scenario, you may decide to cleanup files when the application exits instead. Below is an illustration of handling this with a global process event listener.

import { unlink } from 'fs';

const tempFiles = new Set();

function cleanupOnExit(file: string) {
  tempFiles.add(file);

  const removeFile = () => {
    unlink(file, (err) => {
      if (err) throw err;
      console.log(`${file} was deleted`);
      tempFiles.delete(file);
    });
  };

  process.on('exit', removeFile);
  process.on('SIGINT', removeFile);
  process.on('SIGTERM', removeFile);
  process.on('uncaughtException', removeFile);
}

Conclusion

You now have various options at your disposal for deleting files after they’ve been sent to the client using NestJS. From employing custom interceptors and middleware to handling streams efficiently, you can ensure your application maintains optimal performance by cleaning up resources properly. Remember to always safeguard against potential errors and use the strategies best suited to your use-case scenarios.