File Handling in Express.js
By Syncfusion®, Inc. · Last updated
Sending files with res.sendFile, downloads with res.download and res.attachment, streaming large files, uploads with Multer, memory and disk storage, checking uploads, path traversal and other file security, and upload error handling.
What you will learn
- Send files safely with res.sendFile and the root option
- Offer downloads with res.download
- Stream large files
- Accept uploads with Multer
- Check and store uploads safely
File Handling Introduction
Servers often send files and receive files.
- Sending: a menu PDF, a report card, a photo.
- Downloading: the browser saves the file instead of showing it.
- Uploading: a student sends a photo or an assignment.
- Express can send files by itself with
res.sendFileandres.download. - Uploads need a package. The usual one is Multer.
Simple lesson: Express sends files. Multer receives them.
A Real-World Example
The school office handles papers all day.
- A parent asks for the fee receipt. The clerk hands over a copy.
- A student submits an assignment. The clerk checks it is the right subject and not too thick.
- Accepted papers go into a locked cupboard, under a number, not the student's own label.
- Nobody may walk in and pick any file from the cupboard.
Handing over is sending. Checking and storing is uploading. The locked cupboard is file security.
In this lesson, you will learn:
- How to send files with
res.sendFile. - How to offer downloads with
res.downloadandres.attachment. - Why large files should be streamed.
- How to accept uploads with Multer.
- How to check uploaded files.
- How to keep files and folders safe.
Sending Files with res.sendFile
- res.sendFile(path) sends a file and sets its
Content-Type. - The path must be absolute, such as
path.join(import.meta.dirname, "files", "menu.pdf"). - Or give a relative name plus the root option:
res.sendFile("menu.pdf", { root: dir }). - The root option also blocks
..tricks, so it is the safer choice. - A missing file is an error. Handle it in a callback or let error middleware do it.
- Each file gets the right
Content-Type. Check Headers. /missinguses the callback to send a friendly 404.
Simple lesson: Use res.sendFile with the root option.
Offering Downloads
- res.download(path, name) = send the file as a download.
- The browser saves it instead of showing it.
- The second argument is the file name the user sees.
- It sets the Content-Disposition header:
attachment; filename="...". - res.attachment(name) sets only that header. Use it when you send the body yourself.
- Open Headers and find
Content-Dispositionin both answers.
Simple lesson: res.download sends a file to be saved, with a friendly name.
Large Files and Streaming
fs.readFileSyncloads the whole file into memory first.- A 2 GB video read this way could crash the server.
- Streaming = sending the file in small pieces as it is read.
fs.createReadStream(path).pipe(res)streams a file.- Backpressure = the stream pauses when the client reads slowly, so memory stays low.
res.sendFileandres.downloadalready stream for you.
Always listen for error on a stream. An unhandled stream error stops the whole Node.js process.
Simple lesson: Stream big files. Never load them fully into memory.
Uploading Files
An HTML form that uploads files must use enctype="multipart/form-data".
<form action="/photo" method="post" enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="photo">
<button>Upload</button>
</form>
- multipart/form-data = a body split into parts, one per field.
- File fields carry a file name, a type and the bytes.
- Text fields carry plain values, like
name. express.json()andexpress.urlencoded()cannot read this format.- API clients, like Postman or
curl -F, send the same format.
Multer
- Install:
npm install multer. - Create an upload handler:
const upload = multer({ ... }). - upload.single("photo") = one file. It lands in req.file.
- upload.array("photos", 3) = up to 3 files with one name. They land in req.files.
- upload.fields([...]) = files under several names.
req.filesbecomes an object. - Text fields from the same form land in req.body.
- The quick requests send real multipart bodies, like a browser form.
Simple lesson: upload.single for one file, upload.array for many, both before your handler.
Where Uploads Are Stored
- Memory storage (the default) = the file stays in
req.file.buffer. Good for small files you pass on, such as to cloud storage. - Disk storage = Multer writes the file to a folder.
multer({ dest: "uploads/" })saves with a random name and no extension.multer.diskStorage({ destination, filename })lets you choose the folder and the name.- Custom storage engines send files straight to places like Amazon S3.
- Temporary files should be deleted when you are done with them.
- The saved name is a random UUID plus
.pdf. The user's odd name is never used as a path.
Simple lesson: Store uploads under names you generate, in a folder you choose.
Checking Uploaded Files
Treat every upload as possibly wrong or harmful.
- File size limit:
limits: { fileSize: 2 * 1024 * 1024 }for 2 MB. - File count limit:
limits: { files: 3 }. - Allowed extensions: an allowlist, such as
.pdf,.png,.jpg. - Declared MIME type:
file.mimetype. The client chooses it, so it can lie. - File signature: the first bytes of the file, such as
%PDFfor PDFs. Harder to fake. - fileFilter = a Multer option that accepts or rejects each file.
virus.exefails the extension check, even though it claims to be a PDF.fake.pdfpasses the name check, but fails the signature check.big.pdfis stopped by the size limit with 413.
Checking the first bytes is a good extra check, but it is not perfect. Real apps may also scan files for viruses, and never run or open uploaded files on the server.
Simple lesson: Check size, extension, type and signature. Trust none of them alone.
File Security
- Path traversal = using
../in a name to reach files outside a folder. - Never build a path from user input with plain
path.join. - Use the
rootoption, orpath.basename(name)to strip folders. - Never allow uploads that can run, like
.exe,.shor.html. - Keep private uploads outside
public/. Send them through a route that checks login. - Use generated names, and delete temporary files when done.
/unsafe?file=../server.jssends your source code and the password./safe?file=../server.jsis refused with 403.
Path traversal is one of the most common file bugs. Every file name from a user is an attack until proven safe.
Simple lesson: User input never decides a full file path.
Handling Upload Errors
- Multer passes problems to
next(err)as a MulterError. err.codesays what went wrong.- LIMIT_FILE_SIZE: the file is too big. Answer 413.
- LIMIT_FILE_COUNT: too many files. Answer 400.
- LIMIT_UNEXPECTED_FILE: a file arrived under the wrong field name. Answer 400.
- Missing file:
req.fileisundefined. Check it in your handler. - Answer every upload error with the same JSON shape.
Simple lesson: Turn Multer errors into clear, consistent JSON answers.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
res.sendFile("menu.pdf") | Relative paths work | Use an absolute path or root | TypeError: path must be absolute |
path.join(dir, req.query.file) | path.join is safe | It follows ../ | Source code and secrets leak |
Form without enctype="multipart/form-data" | Files upload with any form | Uploads need multipart | req.file is undefined |
Trusting file.mimetype | The browser checks types | The client sets it freely | Harmful files get in |
Saving with file.originalname | Users pick sensible names | Names can hold ../ or clash | Overwrites and traversal |
Uploads inside public/ | It is easy to serve them | Private files need a login check | Anyone can download anyone's files |
readFileSync for big downloads | Files are small | Stream big files | High memory, crashes |
Check Yourself
Exercises
What to Remember
res.sendFilewith therootoption sends files safely.res.downloadandres.attachmentmake the browser save the file.- Stream large files, and always handle stream errors.
- Multer reads uploads:
singletoreq.file,arrayandfieldstoreq.files. - Check size, extension, MIME type and signature. Use generated file names.
- Never build paths from user input, and keep private files out of
public/.
Next: Working with a Database. You will store canteen data in SQLite and query it safely.