Tmp File Upload Plugin
Stream large file uploads into temporary files instead of memory, so requests far larger than available memory are parsed safely.
Installation
npm install @orpc/node@betapnpm add @orpc/node@betayarn add @orpc/node@betabun add @orpc/node@betaSetup
Use TmpFileUploadHandlerPlugin to parse file uploads into temporary files. Bodies the standard parser would buffer into an in-memory File, and multipart/form-data file parts, stream to disk instead. Every other body is left to the standard parser.
import { TmpFileUploadHandlerPlugin } from '@orpc/node'
import { RPCHandler } from '@orpc/server/node'
const handler = new RPCHandler(router, {
plugins: [
new TmpFileUploadHandlerPlugin({
/**
* The directory temporary files are created under. Each request that
* spools an upload gets its own subdirectory inside it, removed when
* the request finishes.
*
* @default os.tmpdir()
*/
tmpDir: './uploads',
}),
],
})
Working with Uploaded Files
Procedures receive ordinary File instances and read them lazily from disk, in constant memory. Each one is a TmpFile exposing the path of its backing file, so an upload can be kept with a cheap rename instead of a copy:
import { TmpFile } from '@orpc/node'
import { rename } from 'node:fs/promises'
const uploadVideo = os
.input(z.object({ video: z.file() }))
.handler(async ({ input }) => {
if (input.video instanceof TmpFile) {
await rename(input.video.path, `./videos/${crypto.randomUUID()}`)
}
})
Limiting Body Sizes
Use maxBodySize to limit each kind of request body by what it actually costs. All three kinds are required together, so none is left unbounded by accident; set a kind to Number.POSITIVE_INFINITY to deliberately leave it unlimited. A body over its limit rejects with PAYLOAD_TOO_LARGE.
const handler = new RPCHandler(router, {
plugins: [
new TmpFileUploadHandlerPlugin({
maxBodySize: {
/**
* Content parsed into memory: JSON, URL-encoded forms, and the
* plain fields of a multipart body. Usually the lowest limit.
*/
memory: 1024 * 1024,
/**
* Content streamed into temporary files: file bodies and the
* file parts of a multipart body combined.
*/
file: 10 * 1024 * 1024 * 1024,
/**
* Content consumed as a stream: event streams and raw binary
* streams, enforced while the stream is consumed. Usually the
* highest limit.
*/
stream: Number.POSITIVE_INFINITY,
},
}),
],
})
A multipart body splits across the first two limits, fields against memory and file parts against file, and as a whole, framing included, it is bounded by the sum of both. A declared Content-Length over the applicable limit rejects immediately, and enforcement continues while the body streams in, so a lying length cannot bypass it.
With all three limits configured, the plugin subsumes the Request Limit Plugin. When the Request Compression Plugin is present, limits apply to the decompressed payload rather than the compressed wire size.
Learn More
For implementation details, see the source code.