Scenario and definition
A Social Media Platform needs an endpoint accepting a user's profile picture upload, validating file type and size limits before processing, streaming the file directly to cloud object storage rather than buffering the entire file in application memory. Node.js (NestJS) handles this via Multer-based interceptors. Spring Boot handles this via MultipartFile parameters backed by the servlet container's multipart resolver.
Keywords
Code comparison
@Post('profile-picture')
@UseInterceptors(FileInterceptor('file', {
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
cb(null, ['image/jpeg', 'image/png'].includes(file.mimetype));
}
}))
async uploadProfilePicture(@UploadedFile() file: Express.Multer.File): Promise<{ url: string }> {
if (!file) throw new BadRequestException('Invalid file type or no file provided');
const url = await this.storageService.streamUpload(file.buffer, file.originalname);
return { url };
}@PostMapping("/profile-picture")
public Map<String, String> uploadProfilePicture(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty() || !List.of("image/jpeg", "image/png").contains(file.getContentType())) {
throw new BadRequestException("Invalid file type or no file provided");
}
String url = storageService.streamUpload(file.getInputStream(), file.getOriginalFilename());
return Map.of("url", url);
}Code explanation
node
FileInterceptor('file', options) wires Multer into NestJS's interceptor pipeline, with fileFilter rejecting disallowed MIME types BEFORE the file is fully buffered, and limits.fileSize enforcing the size cap at the parsing layer. file.buffer holds the file in memory (memoryStorage) — for genuinely large files, production setups typically configure diskStorage or a streaming-direct-to-S3 approach instead.
springboot
MultipartFile is Spring's abstraction over the underlying servlet container's multipart-parsing, with size limits enforced at the container level before Spring's controller code even runs. file.getInputStream() exposes the upload as a true Java InputStream rather than a fully-buffered byte array, making genuine streaming to cloud storage more naturally achievable than Node's buffer-centric default.
Backend integration
node
storageService.streamUpload typically wraps an AWS S3 SDK call, with the returned public URL constructed from the bucket's CDN domain plus the generated object key.
springboot
storageService.streamUpload similarly wraps the AWS SDK for Java's S3 client, passing the InputStream directly to a PutObjectRequest, with true streaming access making it more straightforward to avoid intermediate memory buffering.
Interview questions
Why does Spring's MultipartFile.getInputStream() offer a more natural path to true streaming than NestJS's default Multer memoryStorage configuration?
Multer's memoryStorage option buffers the ENTIRE uploaded file into a Node.js Buffer in application memory before the controller method even runs — for larger allowed file sizes this doesn't scale, requiring different Multer configuration or a raw stream-parsing library to get an actual Readable stream. Spring's MultipartFile.getInputStream() exposes a true InputStream by design, allowing bytes to be forwarded to cloud storage incrementally without ever holding the complete file in heap memory at once.
Why does Spring Boot reject oversized files at the servlet container level before the controller method runs, while NestJS's size limit is enforced inside the interceptor?
Spring Boot's multipart resolver is configured as part of the underlying servlet container's request-handling setup, meaning an oversized request can be rejected during the container's own parsing, before Spring's DispatcherServlet even routes to a controller — an earlier rejection point. NestJS's FileInterceptor enforces its size limit as Multer parses the multipart stream within the Node.js application process itself, still relatively early but at a layer above the container.