DEV Community

Maneet Goyal for Manufac Analytics Private Limited

Posted on • Edited on

2

Implementing Multer Storage Engine in TypeScript

Multer is pretty popular ExpressJS middleware, primarily used to handle file uploads from client to server. However, a lot of other options are available too depending on what fits your use case.

Multer plays along really well with ExpressJS and have out-of-the-box support for storing file in-disk and in-memory, on the server side. Interestingly, Multer can also be used for file streaming applications where you don't need to store the incoming file on server side and directly redirect the incoming file stream to some cloud storage service instead (say, AWS S3). When you need to support the uploads of bulky/large files (say, tens or hundreds of MBs or anything beyond that), streaming can come across as the most practical solution.

The file streaming logic can vary depending on your cloud storage service provide but in this post I'll simply present the storage engine implementation logic in generalized way which you can expand upon based on your use cases. This logic is already presented by the maintainers of Multer here but it uses object prototypes and vanilla JS. For those of us who have gotten the taste of syntactic sugar by using JS classes might find it hard to comprehend that logic right away. Moreover, owing to the popularity of TypeScript, a TS implementation of that storage engine logic may also be handy for the web dev community.

So, here's TS Classes based approach for implementing Multer's Storage Engine:

import { createWriteStream, unlink } from "fs";
import type { PathLike } from "fs";
import type { Request } from "express";
type Maybe<T> = T | null | undefined;
type ResponseCallback<T> = (err: Error | null, payload?: Maybe<T>) => void;
type RequestHandler<T> = (req: Request, file: Express.Multer.File, responseCb: ResponseCallback<T>) => void;
/**
* Custom Multer Storage Engine for uploading file to AWS S3
* Ref: https://github.com/expressjs/multer/blob/master/StorageEngine.md
* Example Usage:
* ```ts
* const storageEngine = new MulterS3StorageEngine((_req, file, responseCb) => {
* responseCb(null, `/var/www/uploads/${file.originalname}`);
* });
* ```
*/
export class MulterS3StorageEngine {
/**
* Tells the engine where to save the file.
* @param _req
* @param _file
* @param responseCb
*/
getDestination: RequestHandler<PathLike> = (_req, _file, responseCb) => {
responseCb(null, "/dev/null");
};
/**
* Stores the file and returns information on how to access the file in the future.
* The information you provide in the callback will be merged with multer's file object, and then presented to the user via `req.files`.
* @param req
* @param file
* @param responseCb
*/
_handleFile: RequestHandler<unknown> = (
req: Request,
file: Express.Multer.File,
responseCb: ResponseCallback<unknown>
) => {
this.getDestination(req, file, (err, path) => {
if (err !== null) {
responseCb(err);
}
if (path !== null && path !== undefined) {
const outStream = createWriteStream(path);
file.stream.pipe(outStream);
outStream.on("error", responseCb);
outStream.on("finish", () => {
responseCb(null, { path, size: outStream.bytesWritten });
});
}
});
};
/**
* Removes the file if an error is encounter during file stream handling.
* Multer will decide which files to delete and when.
* @param _req
* @param file
* @param responseCb
*/
_removeFile: RequestHandler<unknown> = (
_req: Request,
file: Express.Multer.File,
responseCb: ResponseCallback<unknown>
) => {
unlink(file.path, responseCb);
};
constructor(destination?: RequestHandler<string>) {
this.getDestination = destination ?? this.getDestination;
}
}

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay