Skip to main content

Command Palette

Search for a command to run...

Storing Uploaded Files and Serving Them in Express

Updated
3 min readView as Markdown

Where uploaded files are stored

When a user uploads a file via an API (e.g., using multipart/form-data), the backend needs to decide where to persist it.

Typical flow:

  1. Client uploads file → API endpoint

  2. Middleware (like multer) processes the file

  3. File is stored either:

    • Locally (disk)

    • Remotely (cloud storage)

Example using multer (local storage):

import multer from "multer";

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "uploads/");
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + "-" + file.originalname);
  },
});

const upload = multer({ storage });

This stores files in a local uploads/ directory.

Local storage vs external storage concept

Choosing storage is an architectural decision, not just an implementation detail.

Local Storage (Disk)

Pros:

  • Simple setup

  • Fast access (low latency)

  • No external dependencies

Cons:

  • Not scalable across multiple servers

  • Risk of data loss on server crash

  • Difficult to manage backups

External Storage (Cloud)

Popular services:

  • Amazon S3

  • Cloudinary

  • Google Cloud Storage

Pros:

  • Highly scalable

  • Durable and reliable

  • CDN support for fast delivery

Cons:

  • Requires setup and credentials

  • Slightly higher latency

  • Costs involved

Serving static files in Express

Once files are stored locally, you need to serve them over HTTP.

Basic setup:

import express from "express";

const app = express();

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

What this does:

  • Maps /uploads URL path → uploads/ folder

  • Any file inside uploads/ becomes publicly accessible

Accessing uploaded files via URL

If a file is stored as:

uploads/1714738293-profile.png

You can access it at:

http://localhost:3000/uploads/1714738293-profile.png

Example API response:

{
  "imageUrl": "http://localhost:3000/uploads/1714738293-profile.png"
}

This URL can be used directly in frontend apps (<img src=... />).

Security considerations for uploads

  • Maps /uploads URL path → uploads/ folder

  • Any file inside uploads/ becomes publicly accessible


🌐 Accessing Uploaded Files via URL

If a file is stored as:

uploads/1714738293-profile.png

You can access it at:

http://localhost:3000/uploads/1714738293-profile.png

Example API response:

{
  "imageUrl": "http://localhost:3000/uploads/1714738293-profile.png"
}

This URL can be used directly in frontend apps (<img src=... />).

Security Considerations for File Uploads

This is where most developers make mistakes. File uploads are a major attack vector.

Validate File Types

Never trust file.mimetype blindly.

const allowedTypes = ["image/png", "image/jpeg"];

if (!allowedTypes.includes(file.mimetype)) {
  throw new Error("Invalid file type");
}

Limit File Size

const upload = multer({
  storage,
  limits: { fileSize: 2 * 1024 * 1024 }, // 2MB
});

Prevents DoS via large uploads.

Avoid Executable Files

Never allow:

  • .js

  • .exe

  • .sh

These can be executed on your server if misconfigured.

Rename Files Safely

Avoid using original filenames directly.

✔️ Good:

timestamp-randomstring.png

❌ Bad:

../../../etc/passwd

Use Separate Storage Server (Production)

Serving files directly from your API server:

  • Increases load

  • Reduces scalability

Instead:

  • Upload → Cloud (e.g., S3)

  • Serve via CDN

Authentication (if needed)

Not all files should be public.

Options:

  • Signed URLs (S3)

  • Auth middleware before serving files