Storage & Parsingv0.1.8
@tekir/bodyparser
Body parsing: JSON, form, multipart, raw. File uploads with validation.
Installation
$
bun add @tekir/bodyparserFeatures
- JSON, URL-encoded form, and raw body parsing
- Multipart file uploads with parseMultipart()
- UploadedFile with size and strict whitelist content validation
- AdonisJS-style accessors: `ctx.file(name)`, `ctx.files(name)`, `ctx.allFiles()`
- Always-present helpers: non-multipart requests get a no-op fallback so handlers do not need optional-chains
- Configurable size limits per content type
- BodyParserProvider for automatic registration
Quick Example
TypeScript
import { bodyParser } from '@tekir/bodyparser'
app.router.use(bodyParser())
app.router.post('/upload', async (ctx) => {
const avatar = ctx.file('avatar', { size: '2mb', extnames: ['jpg', 'png'] })
if (avatar) await avatar.moveTo('./uploads')
// Multi-file field
const photos = ctx.files('photos')
for (const p of photos) await p.moveTo('./uploads/photos')
// Or every uploaded file across all fields
const all = ctx.allFiles()
})Changelog
v0.1.8LatestSeptember 16, 2026
- Spilled uploads now load through Node-compatible ESM imports instead of a CommonJS-only runtime require.
- Published output now uses the shared Node-targeted ESM bundle pipeline with external dependencies and generated TypeScript declarations, while Bun consumers keep the native source export.
v0.1.7July 23, 2026
- Multipart parsing now enforces a configurable
maxPartsceiling in both streaming and fallback paths, rejecting excessive field-and-file payloads with413before they can exhaust parser resources.
v0.1.5June 13, 2026
- Multipart parsing is now streaming with limits applied as the body is read.
maxFileSize, totallimit,maxFiles(default 20),maxFields(default 1000), andmaxParts(default 1000) are enforced during the read and abort early with a413once exceeded, so a large upload is no longer buffered fully into memory. Parts overspillThreshold(default 1 MB) stream to a temp file to keep memory bounded. - JSON, form, and raw bodies are also size-limited during the read (with a
Content-Lengthpre-check) and cancel as soon as the limit is exceeded. - JSON parsing now strips
__proto__,constructor, andprototypekeys recursively, consistent with the urlencoded path, closing a prototype pollution vector. - SVG content detection is hardened so that crafted XML/HTML no longer passes as an image; the docs note that SVG is an active document and should not be added to an
extnameswhitelist without sanitization. Method spoofing is now opt-in viamethodSpoofing: trueand only upgrades real POST requests (a GET can never be mutated). - Found and fixed with Fable.
v0.1.4May 17, 2026
- Multipart uploads now reject oversize requests before parsing. When the incoming
Content-Lengthexceeds the configuredlimit,parseMultipart()throws a newPayloadTooLargeError(HTTP 413) instead of buffering the whole payload into the runtime'sFormDataparser first. Apps that catch framework errors get a clean 413 path; apps that don't were previously paying memory cost for the rejection. - A user-supplied
tmpFileName()callback can no longer write outsidetmpDir. The returned name isbasename()-stripped and the final path is verified to stay under the configured directory; paths like../../etc/passwdare rejected and surface as atmpFileNamevalidation error on the affected upload rather than escaping containment. PayloadTooLargeErroris exported from@tekir/bodyparserfor callers that want to branch on it or attach a custom error handler.
v0.1.3May 4, 2026
- **Security**:
UploadedFile.validateContent()now treats unrecognized magic bytes as outside theextnameswhitelist instead of silently allowing them. Previously a renamedmalware.exe→malware.jpgslipped through becausedetectExtname()returnednulland the mismatch check was guarded behindif (detected && ...), leavinghasErrorsfalseso the file reached storage. Strict whitelist semantics now: empty buffer →rule: 'content', message: 'Empty file'; magic bytes don't match any known signature →rule: 'content', message: 'Unrecognized file content'; detected format is not in the whitelist →rule: 'content', message: 'File content (.X) is not in allowed types: ...'; declared extension does not match the detected format →rule: 'extname'. The non-strict path (noextnamesoption) is unchanged.
v0.1.2May 4, 2026
- **Breaking**:
ctx.filesis now a method, not aMultipartFilescollection. Multi-file fields are read withctx.files(name)(returnsUploadedFile[]) instead ofctx.files.files(name). Matches AdonisJS' single-method-per-shape pattern (ctx.file()/ctx.files()/ctx.allFiles()) and removes the awkwardfiles.filesdouble-dot. TheMultipartFilesclass is still exported for advanced use; the parser still produces it internally. - All three accessors are installed on
ctxfor every request, including non-multipart ones, with a no-op fallback (ctx.file()→undefined,ctx.files()→[],ctx.allFiles()→[]). Removes the optional-chain dance from controllers, soconst avatar = ctx.file('avatar')works in any handler regardless of content-type. ctx.file(name)now returnsUploadedFile | undefined(was... | null) so the entire surface lines up onundefinedfor the absent case.