Design file uploads that enforce server side limits

An avatar form rejects files larger than two megabytes. A caller bypasses the form and sends a much larger body directly to the endpoint. Another caller names a file photo.png even though its bytes are not an image. The browser's checks improved the normal experience, but the server treated them as proof. Upload limits must hold at every path that accepts or publishes bytes.
This example assumes a Node upload service and object storage. Exact multipart parser and provider APIs depend on installed versions, so the implementation discussion uses explicit stages rather than an invented universal upload function. The desired result is an owned, validated image that becomes public only after successful verification.
Define limits before parsing
Specify maximum request size, file size, file count, allowed media types, and expected dimensions for the feature. An avatar and a document attachment need different policies. Also define account quotas and upload frequency so many individually valid files cannot exhaust storage or provider costs.
Enforce an ingress limit where infrastructure supports it, then enforce application limits while reading the actual stream. A declared Content-Length can be missing or misleading. A parser that buffers the entire body before checking size can already consume the memory you intended to protect.
Choose deliberate error behavior. Oversized requests should receive an appropriate limit response, and the client should know whether retrying with a smaller file is useful. Do not present a timeout as successful upload simply because the progress bar reached the end of the browser's transfer.
Validate identity and destination
Authenticate before issuing upload capability where possible. Check whether the actor may replace this avatar or attach a file to the requested resource. Use server-generated object keys and associate the upload with an owner and purpose. Never use a caller's filename as a trusted storage path.
A direct-to-storage flow moves byte transfer away from the application server, but it does not remove authorization or validation. A signed upload grant should be narrow in destination, operation, lifetime, and provider-supported constraints. The finalization endpoint must confirm the object belongs to the authorized operation.
Do not accept a public URL supplied by the browser as proof that a valid object was uploaded. A caller may point to another account's object or an unrelated host. Use the provider's trusted metadata and your own upload record to establish the relationship.
Inspect actual content
Check byte signatures and parse the expected format with a maintained library. Filename extension and client MIME type are hints, not authority. Image dimensions, decoded pixel limits, and malformed content matter in addition to compressed byte size. A small compressed file can still require excessive processing.
For image features, re-encoding into an allowed output format can reduce unexpected metadata and content behavior, provided the processing library and limits are appropriate. Do not claim that re-encoding makes every file safe. SVG and document formats need their own policies because they can contain active or external content.
Store initially in a private or quarantined state when scanning or validation is asynchronous. A public object should not become accessible before the checks that justify publishing it. The application record can move from pending to validated to published, with failed uploads remaining inaccessible.
Implement a staged operation
A bounded upload sequence is: authorize, create a pending upload record, accept or grant transfer, validate bytes and metadata, persist the verified object reference, and publish the intended association. Each stage has an explicit result and cleanup behavior.
If the provider succeeds but the database write fails, an orphan object may remain. If the database succeeds but the provider fails, the application must not display a broken object as ready. Record enough state to reconcile either case. A background cleanup can remove old unclaimed objects, but it should verify ownership and age before deletion.
For avatar replacement, keep the previous valid avatar until the new one is fully ready. Deleting the old object at the beginning creates a needless failure window. After successful replacement, schedule cleanup of the previous object according to retention and cache behavior.
Verify the bypass paths
Test the endpoint directly with no browser form. Send an oversized stream, missing length header, too many files, misleading MIME type, invalid image bytes, and an unsupported format. Use synthetic files in an isolated environment. Verify rejected uploads do not become publicly reachable.
Test unauthorized destination changes and attempts to finalize another actor's upload. For direct-to-storage, inspect the actual grant constraints and exercise provider rejection where feasible. A mocked signing function cannot prove the storage service enforces the requested size or destination restriction.
Inject provider failure before transfer, during transfer, and after object creation but before finalization. Assert the user sees pending or failed state accurately and that cleanup can recover orphaned objects. Check memory behavior under a bounded oversized test; avoid a test that endangers shared infrastructure.
Review AI suggestions against real APIs
An assistant may propose a parser option such as a size limit that your installed library does not support or applies at a different stage. Verify the actual package version and documented option. Ask it to identify when bytes are buffered and when rejection occurs.
Provide sanitized upload metadata and small synthetic fixtures instead of real documents. Ask the assistant to trace object ownership from grant issuance to finalization. A useful finding names an untrusted URL, missing owner predicate, or early publication state. A generic claim that signed URLs are secure is incomplete.
Review generated cleanup code carefully. Broad deletion by prefix can affect unrelated objects if the prefix is caller-controlled or reused. Prefer tracked object identifiers and a narrow state predicate. Cleanup is a write operation with its own authorization and consistency requirements.
Balance validation and latency
Synchronous validation can provide quick final feedback for small avatars. Larger attachments may need asynchronous scanning and a pending UI. The user should know whether transfer completed, validation is running, or the file is ready. Those are different milestones.
Direct uploads can reduce application memory and bandwidth pressure but create an additional finalization contract. Proxy uploads can simplify ownership and processing but require robust streaming limits. Choose based on file sizes, infrastructure, and validation needs rather than a blanket performance claim.
Third-party failure handling covers partial completion, while authorization tracing covers destination ownership. The upload is safe enough to publish when trusted server evidence establishes both the bytes and the actor's right to associate them.
Sources
About the Author

Kamruz
Full Stack Developer
A passionate developer who loves creating modern web applications and sharing knowledge with the community.
More About Me