Sling Academy
Home/Node.js/NestJS: How to Delete a File after Response is Sent to Client

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

Last updated: December 31, 2023

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.

Next Article: Solving NestJS TypeError: Converting circular structure to JSON

Previous Article: How to Bulk Redirect URLs in NestJS

Series: Nest.js Tutorials: From Basics to Advanced

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates