DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

22-Nodejs Course 2023: UploadedFile

We have made such an impressive progress in our request handler, now let's continue doing our amazing work.

UploadedFile

We will start by creating a new file in the src/core/http folder called UploadedFile.ts

// src/core/http/UploadedFile.ts

export default class UploadedFile {
  constructor(
    private readonly fileData: any,
  ) {}
}
Enter fullscreen mode Exit fullscreen mode

We created a simple class which will receive the file object (that contains the file data).

Now what should we do with this class? well let's list its features

Uploaded File Features

  • Get file name.
  • Get file size.
  • Get file mimetype.
  • Get file extension.
  • Save file to a specific path.
  • Save file to a specific path with a specific name.
  • Save file to a specific path with random generated name.

Pretty cool right? let's start implementing them.

Request File

Now let's update our Request's method parseInputValue to be something like this

// src/core/http/Request.ts

  /**
   * Parse the given data
   */
  private parseInputValue(data: any) {
    if (data.file) {
      return data;
    }

    // data.value appears only in the multipart form data
    // if it json, then just return the data
    if (data.value !== undefined) return data.value;

    return data;
  }
Enter fullscreen mode Exit fullscreen mode

Here we w'll make a check if there is a file object in the data, then we'll return the data as it is, otherwise we'll return the data.value which is the value of the input.

The last return statement will be used with json data (not multipart form data).

Now let's create file method.

// src/core/http/request.ts
  /**
   * Get Uploaded file instance
   */
  public file(key: string): UploadedFile | null {
    const file = this.input(key);

    return file ? new UploadedFile(file) : null;
  }
Enter fullscreen mode Exit fullscreen mode

Here we got the normal file content from the input method, if exists, then return it in a new instance of UploadedFile class, otherwise return null.

The data that is being taken from the multipart are too many, but we need only the file name, mime type and the file buffer content, these what we'll take from the file object.

// src/core/http/UploadedFile.ts
import path from "path";

export default class UploadedFile {
  /**
   * Constructor
   */
  constructor(private readonly fileData: any) {}

  /**
   * Get File name
   */
  public get name(): string {
    return this.fileData.filename;
  }

  /**
   * Get File mime type
   */
  public get mimeType(): string {
    return this.fileData.mimetype;
  }

  /**
   * Get file extension
   */
  public get extension(): string {
    return path.extname(this.name).replace(".", "");
  }
}
Enter fullscreen mode Exit fullscreen mode

The only thing that i can point to here is the extension method, we used path.extname to get the extension of the file, then we removed the dot from the extension.

But let's make it in lower case anyways to make sure nothing fishy happens.

// src/core/http/UploadedFile.ts
  /**
   * Get file extension
   */
  public get extension(): string {
    return path.extname(this.name).replace(".", "").toLowerCase();
  }
Enter fullscreen mode Exit fullscreen mode

Saving File

Now let's go to the important part, which is saving the file, let's create a method called saveTo in our UploadedFile class, this method will receive the directory that we'll store inside it our file.

import { ensureDirectory } from "@mongez/fs";
import { writeFileSync } from "fs";
import path from "path";

export default class UploadedFile {
  /**
   * Constructor
   */
  constructor(private readonly fileData: any) {}

  /**
   * Get File name
   */
  public get name(): string {
    return this.fileData.filename;
  }

  /**
   * Get File mime type
   */
  public get mimeType(): string {
    return this.fileData.mimetype;
  }

  /**
   * Get file extension
   */
  public get extension(): string {
    return path.extname(this.name).replace(".", "").toLowerCase();
  }

  /**
   * Save the file to the given path
   */
  public async saveTo(path: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + this.name, file);
  }

  /**
   * Get file buffer
   */
  public buffer() {
    return this.fileData.toBuffer();
  }
}
Enter fullscreen mode Exit fullscreen mode

We used ensureDirectory to make sure that the directory exists, then we used writeFileSync to write the file to the given path.

Save File With Specific Name

Now let's create a method called saveAs which will receive the path and the name of the file.

// src/core/http/UploadedFile.ts
  /**
   * Save the file to the given path with the given name
   */
  public async saveAs(path: string, name: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + name, file);
  }
Enter fullscreen mode Exit fullscreen mode

Now let's go to our create-user.ts file and give it a try.

// src/app/users/controllers/create-user.ts
import { rootPath } from "@mongez/node";
import { Request } from "core/http/request";

export default async function createUser(request: Request) {
  const image = request.file("image");

  if (image) {
    await image.saveTo(rootPath("storage"));
  }

  return {
    done: true,
  };
}
Enter fullscreen mode Exit fullscreen mode

The rootPath is a function that gives you the path of the root directory of the project, we can pass to it any internal path to generate a full path, we used it to get the path of the storage folder.

Now open postman and make a post request to http://localhost:3000/users and select body type as form-data and add a key called image and select a file, then send the request.

File Size

Now let's add a method called size which will return the size of the file in bytes.

Please note that this method will be a async method as we'll call the buffer method which is also an async method.

So we're going to use the buffer in two scenarios, one is to get the size of the file, and the other is to save the file, so we'll use the buffer to get the size of the file and save it in a variable, then we'll use this variable to save the file.

Let's make a variable called fileBuffer and assign it to the buffer of the file.

// src/core/http/UploadedFile.ts
import { ensureDirectory } from "@mongez/fs";
import { writeFileSync } from "fs";
import path from "path";

export default class UploadedFile {
  /**
   * File Buffer
   */
  private fileBuffer: any;

  /**
   * Constructor
   */
  constructor(private readonly fileData: any) {}

  /**
   * Get File name
   */
  public get name(): string {
    return this.fileData.filename;
  }

  /**
   * Get File mime type
   */
  public get mimeType(): string {
    return this.fileData.mimetype;
  }

  /**
   * Get file extension
   */
  public get extension(): string {
    return path.extname(this.name).replace(".", "").toLowerCase();
  }

  /**
   * Save the file to the given path
   */
  public async saveTo(path: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + this.name, file);
  }

  /**
   * Save the file to the given path with the given name
   */
  public async saveAs(path: string, name: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + name, file);
  }

  /**
   * Get file size in bytes
   */
  public async size() {
    const fileBuffer = await this.buffer();

    return fileBuffer.toString().length;
  }

  /**
   * Get file buffer
   */
  public async buffer() {
    if (this.fileBuffer) {
      return this.fileBuffer;
    }

    return (this.fileBuffer = await this.fileData.toBuffer());
  }
}
Enter fullscreen mode Exit fullscreen mode

Random generated file name

Now let's add our last method which is save, this method will generate a random name for the file and save it to the given path.

Now let's install another small package which we'll use later a lot, Reinforcements.

yarn add @mongez/reinforcements
Enter fullscreen mode Exit fullscreen mode
// src/core/http/UploadedFile.ts
import { ensureDirectory } from "@mongez/fs";
import { Random } from "@mongez/reinforcements";
import { writeFileSync } from "fs";
import path from "path";

export default class UploadedFile {
  /**
   * File Buffer
   */
  private fileBuffer: any;

  /**
   * Constructor
   */
  constructor(private readonly fileData: any) {}

  /**
   * Get File name
   */
  public get name(): string {
    return this.fileData.filename;
  }

  /**
   * Get File mime type
   */
  public get mimeType(): string {
    return this.fileData.mimetype;
  }

  /**
   * Get file extension
   */
  public get extension(): string {
    return path.extname(this.name).replace(".", "").toLowerCase();
  }

  /**
   * Save the file to the given path
   */
  public async saveTo(path: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + this.name, file);
  }

  /**
   * Save the file to the given path with the given name
   */
  public async saveAs(path: string, name: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    writeFileSync(path + "/" + name, file);
  }

  /**
   * Save the file to the given path with random name
   */
  public async save(path: string) {
    const file = await this.buffer();

    ensureDirectory(path);

    const name = Random.string(64) + "." + this.extension;

    writeFileSync(path + "/" + name, file);

    return name;
  }

  /**
   * Get file size in bytes
   */
  public async size() {
    const fileBuffer = await this.buffer();

    return fileBuffer.toString().length;
  }

  /**
   * Get file buffer
   */
  public async buffer() {
    if (this.fileBuffer) {
      return this.fileBuffer;
    }

    return (this.fileBuffer = await this.fileData.toBuffer());
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's give it another test in our controller

// src/app/users/controllers/create-user.ts
import { rootPath } from "@mongez/node";
import { Request } from "core/http/request";

export default async function createUser(request: Request) {
  const image = request.file("image");

  let name = "";

  if (image) {
    name = await image.save(rootPath("storage"));
  }

  return {
    image: {
      name,
      size: await image?.size(),
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

And that's it!

Conclusion

In this tutorial, we've learned how to upload files using multer and how to save them to the disk, we've also learned how to get the file size and how to generate a random name for the file.

In our next article, we'll make it more configurable.

🎨 Project Repository

You can find the latest updates of this project on Github

😍 Join our community

Join our community on Discord to get help and support (Node Js 2023 Channel).

🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.

💰 Bonus Content 💰

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)