# File Uploads in Express

# Where Do Uploaded Files Actually Go? A Beginner's Guide to File Uploads in Express

You've got a form. The user picks a file, hits submit, and... something happens on the server. But where does the file *actually go*? What's the path? Can you open it in a browser? And how do you make sure someone can't just upload a script and break your server?

These are the questions no one tells you to ask when you're just trying to get Multer working. Let's go through them one by one.

---

## 01 — Where Uploaded Files Are Stored

When a user uploads a file through your Express app, the file doesn't go to some magical cloud by default. It lands on your server's hard drive — right there in your project folder.

If you're using `multer` (the most common library for handling file uploads), you tell it exactly where to save files using `diskStorage`:

```javascript
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')   // save to the "uploads" folder
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + '-' + file.originalname)
  }
})

const upload = multer({ storage })
```

After this runs, a file called `photo.jpg` uploaded at 3:05 PM might end up at:

```
uploads/1718270700000-photo.jpg
```

The timestamp prefix is a common trick to avoid overwriting files with the same name. More on naming strategies in a bit.

### What your project folder looks like

```
my-express-app/
├── server.js
├── package.json
├── node_modules/
└── uploads/              ← files land here
    ├── images/
    │   ├── 1718270700000-photo.jpg
    │   └── 1718270800000-logo.png
    └── documents/
        ├── 1718270900000-cv.pdf
        └── 1718271000000-report.pdf
```

Organising into subfolders like `uploads/images/` and `uploads/documents/` is optional, but it keeps things tidy once your project grows and you're handling multiple file types.

---

## 02 — Local Storage vs External Storage

Storing files directly on your server (local storage) is the simplest approach, and it works great while you're building and learning. But it's worth knowing the trade-offs early so you're not caught off guard later.

### Local Storage

Files go straight to a folder on the same machine running your Express app. Easy to set up, zero extra cost, works immediately.

The downside? If you ever run your app on multiple servers, each server has its own copy of the uploads folder — and they don't talk to each other. A file uploaded on Server A won't be visible from Server B.

> ✅ **Good for:** Personal projects, portfolios, internal tools, anything you're deploying to a single server. It's also the right starting point while you learn — don't overcomplicate it.

### External Storage (like AWS S3 or Cloudinary)

Instead of saving to a local folder, you upload the file to a cloud service. Your app just stores the URL that points to the file. Multiple servers, no problem — they all reference the same external URL.

```
// Local storage
file → saved to ./uploads/photo.jpg on your server

// External storage (e.g. S3)
file → uploaded to S3 bucket
your DB stores → "https://s3.amazonaws.com/mybucket/photo.jpg"
```

> 💡 **Rule of thumb:** Start local. Move to external storage when you're ready to deploy seriously or when you expect heavy traffic. Don't let the choice block you from building.

---

## 03 — Serving Static Files in Express

Saving a file to `uploads/` is only half the job. By default, Express doesn't let anyone access files in that folder through the browser. You have to explicitly tell it: *"hey, make this folder publicly available."*

That's what `express.static()` is for:

```javascript
const express = require('express')
const app = express()

// Make the "uploads" folder publicly accessible
app.use('/uploads', express.static('uploads'))
```

Now, any file inside the `uploads/` folder can be reached at:

```
http://yoursite.com/uploads/filename.jpg
```

### How the flow works

```
Browser                   Express                    Your Server
  │                          │                            │
  │  GET /uploads/photo.jpg  │                            │
  │ ─────────────────────►   │                            │
  │                          │   looks up file on disk    │
  │                          │ ─────────────────────────► │
  │                          │                            │
  │                          │   ./uploads/photo.jpg ✓    │
  │                          │ ◄───────────────────────── │
  │   file sent as response  │                            │
  │ ◄──────────────────────  │                            │
```

Think of `express.static()` as pointing a URL path to a physical folder on your server. Anything inside that folder becomes accessible over HTTP — images load in the browser, PDFs open in a new tab.

One small thing worth noting: the URL path (`/uploads`) and the actual folder name (`'uploads'`) don't have to match. You could do:

```javascript
app.use('/files', express.static('uploads'))
// file is at: /files/photo.jpg in the URL
// but saved in: ./uploads/photo.jpg on disk
```

---

## 04 — Accessing Uploaded Files via URL

Once you've set up static serving, accessing a file is just a matter of building the right URL. After a successful upload, you can construct the file's URL in your route handler and store it in your database:

```javascript
app.post('/upload', upload.single('profilePic'), (req, res) => {
  const fileUrl = `/uploads/${req.file.filename}`

  // Save fileUrl to your database here
  // e.g. user.profilePic = fileUrl

  res.json({
    message: 'Upload successful',
    url: fileUrl
  })
})
```

If someone uploads `photo.jpg` and it gets saved as `1718270700000-photo.jpg`, the response might look like:

```json
{
  "message": "Upload successful",
  "url": "/uploads/1718270700000-photo.jpg"
}
```

On the frontend, you'd use this URL directly in an `<img>` tag or a download link — no extra setup needed.

```html
<img src="/uploads/1718270700000-photo.jpg" alt="Profile photo" />
```

---

## 05 — Security Considerations for Uploads

This is where a lot of beginners skip ahead, thinking "it's just a side project." But a few small habits now will save you a lot of headaches later. File uploads are one of the most common attack surfaces in web apps.

### Check the file type

Never trust the file extension alone. A file named `evil.jpg` could actually be a script. Always check the `mimetype` that multer gives you, and only allow what you actually need:

```javascript
fileFilter: (req, file, cb) => {
  const allowed = ['image/jpeg', 'image/png', 'image/webp']
  if (allowed.includes(file.mimetype)) {
    cb(null, true)
  } else {
    cb(new Error('Only JPEG, PNG and WebP are allowed'))
  }
}
```

### Limit the file size

Set a max file size so no one can crash your server by uploading a 4GB video. Multer makes this easy:

```javascript
limits: { fileSize: 5 * 1024 * 1024 }  // 5MB max
```

### Rename every file

Never save a file with its original name. Use a timestamp or random ID so attackers can't guess file paths or overwrite existing files:

```javascript
filename: (req, file, cb) => {
  const unique = Date.now() + '-' + Math.round(Math.random() * 1E9)
  cb(null, unique + path.extname(file.originalname))
}
```

### The full, production-ready upload config

Putting it all together:

```javascript
const upload = multer({
  storage: multer.diskStorage({
    destination: (req, file, cb) => cb(null, 'uploads/'),
    filename: (req, file, cb) => {
      const unique = Date.now() + '-' + Math.round(Math.random() * 1E9)
      cb(null, unique + path.extname(file.originalname))
    }
  }),
  limits: { fileSize: 5 * 1024 * 1024 },   // 5MB max
  fileFilter: (req, file, cb) => {
    const allowed = ['image/jpeg', 'image/png', 'image/webp']
    if (allowed.includes(file.mimetype)) {
      cb(null, true)
    } else {
      cb(new Error('Only JPEG, PNG and WebP are allowed'))
    }
  }
})
```

### Two more things before you ship

> ⚠️ **Add `uploads/` to your `.gitignore`.** You almost certainly don't want to commit user-uploaded files to your repository — especially if they could contain personal data.

> 🚫 **Never serve uploaded HTML or JS files directly.** If someone uploads a `.html` file and you serve it via `express.static()`, the browser will execute it. Restrict uploads to only the file types your app genuinely needs.

---

## Quick Recap

| Topic | What to remember |
|---|---|
| Where files go | A local folder on your server, configured in `diskStorage` |
| Local vs external | Local is fine to start; move to S3/Cloudinary for production scale |
| Static serving | `express.static()` maps a URL to a folder so files are accessible via HTTP |
| File URLs | Build the URL after upload, store it in your DB, use it like any `src` or `href` |
| Security | Validate mimetype, set size limits, rename files, add `uploads/` to `.gitignore` |

---

