Tutorial

How to Choose Multer Storage

Pick between Multer memory and disk storage based on file size, so small files stay in memory for fast processing and large files stream to disk.

On this page

  1. Introduction
  2. Weigh memory vs disk
  3. Use memory for small
  4. Use disk for large
  5. Set limits on both
  6. Clean up temp files
  7. Test both paths
  8. Common pitfalls
  9. Conclusion

Introduction

The hardest part of pick memory vs disk is not writing the code — it is deciding what the code is allowed to reject. A Multer setup that accepts everything is easy to ship and expensive to operate, because every malformed input becomes a production surprise. The discipline is to enumerate the shapes the system actually receives, encode them in Multer, and let the library reject the rest loudly. This keeps the happy path fast and the failure path visible.

Choosing Multer for pick memory vs disk is a trade, not a winner. You give up some flexibility to get a hard boundary, and the exchange pays off when the boundary catches a class of bugs that would otherwise surface at 3 a.m. Document the trade at the call site so the next reader does not undo the guard thinking it is a bug. The reader who does not know why the guard exists is the reader who removes it.

The sections below walk through the concrete decisions: where to put the multer check, how to keep it fast under load, what to do when the contract changes, and how to test the failure paths so they degrade on purpose instead of by accident.

Weigh memory vs disk

Weigh memory vs disk is the first decision because it determines what the rest of the Multer pipeline can assume. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Weigh memory vs disk is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

import multer from 'multer';
const small = multer({ storage: multer.memoryStorage(), limits: { fileSize: 2e6 } });
const large = multer({ storage: multer.diskStorage({ destination: '/tmp' }) });

The javascript above is the shape to ship for pick memory vs disk. It is small enough to audit and specific enough that the failure behavior is obvious. Adapt the constants to your traffic and latency budget, and resist the urge to handle cases the system has not seen yet.

Use memory for small

The mechanism behind Use memory for small is straightforward in Multer, but the consequences ripple. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Use memory for small is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Use disk for large

Use disk for large is where most Multer implementations leak performance. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Use disk for large is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

test('memory storage for small', () => {
  expect(small._storage.constructor.name).toBe('MemoryStorage');
});

Pin the behavior with this test. A Multer setup that passes this test degrades on purpose instead of by accident, which is the difference between a managed failure and an incident.

Set limits on both

The hard part of Set limits on both is not the happy path — it is what happens when multer fails halfway. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Set limits on both is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Clean up temp files

Clean up temp files should be observable without a debugger. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Clean up temp files is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Test both paths

Test both paths is a trade, not a winner. A wrong choice here turns into pick memory vs disk under load, and the invariant you thought Multer guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Test both paths is the one most teams skip: drive multer to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Common pitfalls

The common pitfalls in pick memory vs disk all reduce to one mistake: treating Multer as a formality instead of a contract. The contract is the thing the rest of the system relies on, and a weak contract propagates weakness everywhere.

  • Skipping the multer boundary so pick memory vs disk enters unvalidated.
  • Assuming Multer is fast without measuring the tail latency under load.
  • Letting pick memory vs disk fail silently instead of failing loudly with a metric.
  • Testing only the happy path so the multer failure surfaces in production.
  • Sharing mutable state across requests so one bad input poisons the next.

Conclusion

Do not conflate the conclusion with the implementation. They look similar from the outside but the failure modes differ: Multer fails fast and the caller knows, while an unchecked path fails slow and the caller guesses. Choose the one whose failure mode your system can absorb, and document the choice at the call site.

Self-check: write a test that drives multer to the failing condition and assert the system degrades gracefully. Then pick memory vs disk is a property you maintain, not a feature you hope for.

Sources: Multer, Express file upload

FAQ

When use disk storage?
When files exceed memory limits or you need to stream to another service.
How clean up temp files?
Delete in a finally block or use a cron job for orphaned files.

Related Examples