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,
) {}
}
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;
}
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;
}
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(".", "");
}
}
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();
}
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();
}
}
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);
}
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,
};
}
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());
}
}
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
// 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());
}
}
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(),
},
};
}
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
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Top comments (0)