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.sendFile and res.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.

  1. A parent asks for the fee receipt. The clerk hands over a copy.
  2. A student submits an assignment. The clerk checks it is the right subject and not too thick.
  3. Accepted papers go into a locked cupboard, under a number, not the student's own label.
  4. 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:

  1. How to send files with res.sendFile.
  2. How to offer downloads with res.download and res.attachment.
  3. Why large files should be streamed.
  4. How to accept uploads with Multer.
  5. How to check uploaded files.
  6. 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.
  • /missing uses 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-Disposition in both answers.

Simple lesson: res.download sends a file to be saved, with a friendly name.

Large Files and Streaming

  • fs.readFileSync loads 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.sendFile and res.download already 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() and express.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.files becomes 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 %PDF for PDFs. Harder to fake.
  • fileFilter = a Multer option that accepts or rejects each file.
  • virus.exe fails the extension check, even though it claims to be a PDF.
  • fake.pdf passes the name check, but fails the signature check.
  • big.pdf is 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 root option, or path.basename(name) to strip folders.
  • Never allow uploads that can run, like .exe, .sh or .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.js sends your source code and the password.
  • /safe?file=../server.js is 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.code says 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.file is undefined. 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

MistakeWrong ideaCorrect ideaWhat goes wrong
res.sendFile("menu.pdf")Relative paths workUse an absolute path or rootTypeError: path must be absolute
path.join(dir, req.query.file)path.join is safeIt follows ../Source code and secrets leak
Form without enctype="multipart/form-data"Files upload with any formUploads need multipartreq.file is undefined
Trusting file.mimetypeThe browser checks typesThe client sets it freelyHarmful files get in
Saving with file.originalnameUsers pick sensible namesNames can hold ../ or clashOverwrites and traversal
Uploads inside public/It is easy to serve themPrivate files need a login checkAnyone can download anyone's files
readFileSync for big downloadsFiles are smallStream big filesHigh memory, crashes

Check Yourself

Exercises

What to Remember

  • res.sendFile with the root option sends files safely.
  • res.download and res.attachment make the browser save the file.
  • Stream large files, and always handle stream errors.
  • Multer reads uploads: single to req.file, array and fields to req.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.