Skip to main content
Madhukar
All Articles

Storing Uploaded Files and Serving Them in Express

July 28, 20265 min read
Express.jsNode.jsMulterBackend
Storing Uploaded Files and Serving Them in Express

Where does a file actually go after a user uploads it?

A user picks a profile picture, hits upload, and a moment later, it shows up on their profile. Simple from the outside — but behind that one interaction, a real decision has to be made: where does that file physically live, how does the server find it again later, and how does a browser end up being able to display it?

This guide walks through that full picture for an Express application — from choosing where uploaded files are stored, to serving them back out safely over a URL.

1. Where Uploaded Files Are Stored

When a file is uploaded to an Express server, it has to be saved somewhere on the server’s file system (or elsewhere, covered in the next section) before it can be accessed again later.

A common, simple approach is a dedicated folder — often named uploads — sitting alongside the rest of the project:

project-root/
uploads/
avatars/
user-42.png
user-91.png
documents/
invoice-108.pdf
src/
app.js

Organizing this folder by file type or purpose (avatars/, documents/) keeps uploads manageable and predictable as the number of files grows, rather than dumping everything into one flat, undifferentiated folder.

2. Local Storage vs External Storage Concept

Local storage

Saving files directly onto the same server’s disk (as shown above) is simple to set up and works well for small projects, local development, or applications with modest, predictable upload volume.

External (cloud) storage

For production applications, uploaded files are very often stored in a dedicated external storage service (like Amazon S3, Google Cloud Storage, or similar object storage platforms) instead of the application server’s own disk.

Why the distinction matters

  • Server disks are limited and often ephemeral — many modern hosting platforms redeploy or replace server instances, meaning anything saved only to local disk can simply disappear on the next deploy
  • Scaling horizontally (running multiple server instances) means a file saved to one server’s disk wouldn’t be visible to the others — external storage gives every instance access to the same files
  • External storage services are typically built specifically for durability, backups, and serving files efficiently at scale — jobs a general-purpose application server isn’t optimized for

For learning, prototyping, or genuinely small applications, local storage remains a perfectly reasonable starting point — the concepts in this article (organizing folders, serving files, securing uploads) apply either way, and external storage can be introduced later as the application’s needs grow.

3. Serving Static Files in Express

Once a file is saved, Express needs a way to actually deliver it back to a browser when requested. Express includes a built-in feature for exactly this: static file serving.

const express = require("express");
const app = express();

app.use("/uploads", express.static("uploads"));

This single line tells Express: “any request starting with /uploads should be matched directly against files inside the local uploads folder, and served automatically — no custom route handler needed for each individual file."

This is a meaningfully different pattern from a typical API route — you’re not writing logic to fetch and return data; you’re telling Express to expose an entire folder’s contents directly, matched by file path.

4. Accessing Uploaded Files via URL

With static serving configured as above, any file saved into the uploads folder automatically becomes reachable at a predictable URL, mirroring its folder path:

File on disk:     uploads/avatars/user-42.png
Accessible at: https://yourapp.com/uploads/avatars/user-42.png

This means a frontend can simply reference that URL directly — in an <img> tag, for instance — without needing a special API endpoint just to fetch the raw file:

<img src="https://yourapp.com/uploads/avatars/user-42.png" alt="User avatar" />

Whatever path structure you save uploads under becomes the exact same path structure used to access them — which is exactly why a sensible, organized folder structure (Section 1) makes URLs predictable and easy to reason about too.

5. Security Considerations for Uploads

File uploads are a common target for abuse, so a few precautions matter well beyond just “does the upload work”:

Validate file types

Check the file’s actual type (not just its filename extension, which can be easily faked) before accepting it — restricting uploads to expected types (like images only, for an avatar upload) reduces the risk of something malicious being uploaded and later served to other users.

Limit file size

Set a maximum upload size to prevent excessively large files from consuming disk space or bandwidth, whether by accident or deliberate abuse.

Never trust the original filename directly

User-supplied filenames can contain unexpected characters or path traversal attempts (like ../../config.js) designed to write files outside the intended upload folder. Generate a new, safe filename (such as a random ID) on the server instead of using the uploaded filename as-is.

Keep uploads out of sensitive directories

Store uploads in a folder that’s clearly separate from your application’s source code and configuration — and make sure your static-serving setup exposes only that specific uploads folder, not your entire project directory.

Consider where uploads are served from

Serving user-uploaded content from the same domain as your main application can, in some cases, introduce security considerations (like certain cross-site scripting risks) that don’t apply to statically-known application files — a reason some production systems intentionally serve uploads from external storage or a separate subdomain.

Final Takeaway

Handling uploads well in Express comes down to answering a few honest questions in order: where should this file actually live — local disk for simplicity, or external storage for durability and scale? How will it be found again — through a predictable, organized folder structure? How does a browser actually retrieve it — through Express’s static file serving, mapped cleanly to a URL? And finally, what precautions keep that upload from becoming a security liability — validated types, safe filenames, and a tightly scoped serving folder. Get those four questions right, and the rest of a file-upload feature tends to fall into place naturally.

Frequently Asked Questions

Should I always use external storage like S3 instead of local disk?

> Not necessarily for small or learning projects — local disk is simpler to set up and perfectly fine at a small scale. External storage becomes important once you’re deploying to platforms with ephemeral file systems, or scaling to multiple server instances that all need access to the same files.

Does express.static() require any additional packages?

> No — express.static is built directly into Express itself. Handling the upload itself (parsing incoming file data from a form) typically does use an additional package like Multer, but serving already-saved files back out doesn't require one.

Is it safe to use the file’s original uploaded name?

> It’s safer not to. Generating a new, random filename server-side avoids issues with unexpected characters, duplicate filenames overwriting each other, and path traversal attempts hidden in a crafted filename.

Can I restrict who can access an uploaded file?

> Yes, though express.static by itself serves files openly to anyone who knows the URL. Restricting access (for private files) typically means moving away from blanket static serving toward a dedicated route that checks authentication or authorization before streaming the file back.

Originally published by Mr Madhukar

Read the complete article on Medium with full formatting & reader responses.