How to Implement File Uploads in Topcoat: Complete Multipart Handling Guide
Topcoat provides a first-class Multipart extractor in the router crate that handles multipart/form-data requests by streaming fields through the multer library and enforcing configured body limits.
The tokio-rs/topcoat framework simplifies HTTP file upload handling through its integrated multipart support. To implement file uploads in Topcoat, you use the Multipart type which automatically parses request bodies and provides streaming access to uploaded fields and form data. This extractor lives in crates/topcoat-router/src/content/multipart.rs and integrates directly with the router's request processing pipeline.
Understanding the Multipart Extractor
Core Implementation and Dependencies
According to the Topcoat source code, the Multipart extractor implements FromRequest to automatically handle incoming requests. When a route handler accepts a Multipart parameter, Topcoat validates the Content-Type header, extracts the multipart boundary string, and delegates parsing to the multer library. The extractor respects the router's BodyLimit configuration, returning a 413 Content Too Large response when uploads exceed the configured limit.
Field Access Methods
Each field yielded by next_field() exposes metadata through methods defined in the Field struct:
name()returns the form field name from theContent-Dispositionheaderfile_name()provides the original filename for file uploadscontent_type()reveals the MIME type of the fieldbytes()andtext()load the entire payload into memory asBytesorStringchunk()and theStreamimplementation allow incremental processing for large files
Processing File Uploads in Route Handlers
To handle uploads, define a route using the #[route(POST "...")] attribute and accept mut multipart: Multipart as a handler parameter. Iterate through fields using while let Some(field) = multipart.next_field().await? to process each part individually.
use topcoat::{
Result,
router::{content::multipart::Multipart, route},
Cx,
};
#[route(POST "/api/upload")]
async fn upload(mut multipart: Multipart) -> Result<&'static str> {
// Process each part of the multipart request
while let Some(field) = multipart.next_field().await? {
// Identify the field by its name attribute
let name = field.name().unwrap_or("<unknown>");
// If the field has a filename, treat it as a file upload
if let Some(filename) = field.file_name() {
// Read the whole file into memory (or stream with `chunk()`)
let data = field.bytes().await?;
// Here you could write `data` to disk, S3, etc.
println!("Uploaded file `{filename}` ({:?} bytes) in field `{name}`", data.len());
} else {
// Non‑file field – treat as plain text
let text = field.text().await?;
println!("Form field `{name}`: {text}");
}
}
Ok("upload processed")
}
This pattern, demonstrated in examples/request-response/src/main.rs, distinguishes between file fields (which have a filename) and regular form fields, processing each appropriately while providing access to metadata like original filenames and content types.
Configuring Upload Size Limits
By default, the extractor reads through the router's body limit layer, which restricts maximum payload sizes. For upload routes requiring larger limits, apply the BodyLimit layer to specific paths:
use topcoat::router::BodyLimit;
router
.route(upload)
.layer(BodyLimit::max(64 * 1024 * 1024).at("/api/upload"));
This configuration permits uploads up to 64 MB on the /api/upload endpoint while maintaining stricter default limits on other routes. The .at() method ensures the limit applies only to the specified path.
Error Handling and HTTP Status Codes
The Multipart extractor produces specific HTTP responses for error conditions encountered during parsing:
- 400 Bad Request (
invalid_boundary) when theContent-Typeheader lacksmultipart/form-dataor contains a malformed boundary - 413 Content Too Large when the payload exceeds the configured
BodyLimitbefore or during streaming - 400 Bad Request for malformed multipart data encountered while reading fields through the multer library
Summary
- The
Multipartextractor incrates/topcoat-router/src/content/multipart.rsprovides native support formultipart/form-datauploads without external middleware - Access uploaded files through
next_field()and inspect metadata usingname(),file_name(), andcontent_type() - Control memory usage by choosing between
bytes()(full load) andchunk()(streaming) when reading field data - Configure route-specific size limits using the
BodyLimitlayer to prevent 413 Content Too Large errors on upload endpoints - The extractor relies on the multer library for standards-compliant parsing while respecting Topcoat's body limit constraints
Frequently Asked Questions
What happens if the request Content-Type is not multipart/form-data?
The extractor returns a 400 Bad Request with the invalid_boundary error code. This occurs immediately during request processing because the FromRequest implementation validates the Content-Type header before attempting to parse the body.
How do I stream large files instead of loading them into memory?
Use the chunk() method or treat the Field as a Stream implementation instead of calling bytes(). This allows you to process data incrementally as it arrives from the client, avoiding memory exhaustion when handling large uploads.
Can I configure different size limits for different routes?
Yes. Apply the BodyLimit layer to specific routes using the .at() method as implemented in crates/topcoat-router/src/content/multipart.rs. This allows you to set higher limits for upload endpoints while maintaining stricter default restrictions on other routes.
What library does Topcoat use internally for parsing multipart data?
Topcoat delegates multipart parsing to the multer library, which handles boundary extraction, header parsing, and streaming field data while respecting the router's body limit constraints enforced through limited(cx, body).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →