File handling on a Node API means accepting an uploaded file over multipart/form-data, validating it, and persisting its bytes to durable storage while recording a reference (a storage key) in the database — using Node’s fs/promises for disk I/O and Nest’s FileInterceptor to parse the upload.
In simpler words
The bytes of a file go to disk or object storage; the database only keeps a pointer (a path or key) plus metadata like name, type, and size.
A typical Nest upload slice ties these together: a controller accepts an upload with FileInterceptor, a service writes the bytes with fs/promises, and an attachment entity stores the storage_key and metadata.
Parse an upload with FileInterceptor and read it with @UploadedFile
Write bytes safely with fs/promises and store only a reference in the DB
Validate file type and size, and serve a file back as a download
Node fs basics: writing and reading bytes
Definition
Node’s fs/promises module exposes async file operations — mkdir with recursive, writeFile, readFile, and streams for large files — while the path module builds filesystem paths portably; a Buffer holds the raw bytes in memory before they are written.
In simpler words
You get a Buffer of bytes, make sure the target directory exists, then write the file — and for big files you stream instead of holding it all in memory.
mkdir(dir, { recursive: true }) is safe to call repeatedly; writeFile(path, buffer) persists the bytes; join(dir, name) builds the path without hardcoding separators.
writeFile/readFile load the whole file into memory — fine for small attachments, but for large files prefer createReadStream/createWriteStream so memory stays flat.
The bytes land on disk under UPLOAD_DIR; storageKey is the reference the database will remember.
Nest uploads: FileInterceptor and @UploadedFile
Definition
Nest parses multipart/form-data through FileInterceptor (backed by Multer), which populates the file argument exposed by the @UploadedFile decorator; memoryStorage keeps the bytes in a Buffer for the handler, while limits.fileSize rejects oversized uploads before they are fully read.
In simpler words
FileInterceptor turns the raw multipart body into a tidy file object with originalname, mimetype, size, and buffer that your handler receives.
A typical upload route uses FileInterceptor with memoryStorage and a 5MB fileSize limit, so the handler gets file.buffer directly and anything over 5MB is refused.
memoryStorage suits small files handed straight to writeFile; for large files, stream to disk or object storage instead of buffering the whole thing.
The form field name (file) must match the FileInterceptor argument, or @UploadedFile is undefined.
Validate, store a reference, and serve safely
Definition
Safe file handling validates the declared type and size, sanitizes or replaces the client filename so it cannot escape the storage directory, stores only a reference plus metadata in the database, and serves downloads through a controlled path rather than trusting client-supplied paths.
In simpler words
Never trust the uploaded filename or type blindly, never put the bytes in a DB column, and never build a download path straight from user input.
A safe implementation replaces the filename with a random-UUID prefix so a name like ../../etc/passwd cannot traverse out of UPLOAD_DIR, and stores storage_key, mime_type, and size on an attachment row — the blob stays on disk, not in Postgres.
mimetype from the client is a hint, not proof; for untrusted uploads verify by content and constrain accepted types. Serve files back with StreamableFile from a key you looked up, never from a raw client path.