### Low-level BodyParser Middleware Instantiation Source: https://context7.com/adonisjs/bodyparser/llms.txt Demonstrates manual instantiation of BodyParserMiddleware for testing or custom server setups, allowing specific configuration overrides. ```typescript // ---- Low-level instantiation (e.g. in tests or custom servers) ---- import { createServer } from 'node:http' import { BodyParserMiddlewareFactory } from '@adonisjs/bodyparser/factories' import { RequestFactory, ResponseFactory, HttpContextFactory } from '@adonisjs/http-server/factories' const server = createServer(async (req, res) => { const request = new RequestFactory().merge({ req, res }).create() const response = new ResponseFactory().merge({ req, res }).create() const ctx = new HttpContextFactory().merge({ request, response }).create() const middleware = new BodyParserMiddlewareFactory() .merge({ json: { limit: '2mb' } }) .create() await middleware.handle(ctx, async () => { // ctx.request.bodyType === 'json' // ctx.request.all() === { username: 'virk' } res.writeHead(200, { 'content-type': 'application/json' }) res.end(JSON.stringify({ body: ctx.request.all(), bodyType: ctx.request.bodyType, })) }) }) // POST / with Content-Type: application/json {"username":"virk"} // → { body: { username: 'virk' }, bodyType: 'json' } ``` -------------------------------- ### BodyParserMiddlewareFactory Source: https://context7.com/adonisjs/bodyparser/llms.txt A testing factory for `BodyParserMiddleware`. It starts with the default `defineConfig({})` configuration and allows for deep-merging of configuration overrides before creating middleware instances. This is useful for testing middleware behavior with specific configurations. ```APIDOC ## `BodyParserMiddlewareFactory` — Create middleware instances for tests Testing factory for `BodyParserMiddleware`. Starts from the default `defineConfig({})` baseline and lets you deep-merge configuration overrides before calling `create()`. ```ts import { test } from '@japa/runner' import { createServer } from 'node:http' import supertest from 'supertest' import { BodyParserMiddlewareFactory } from '@adonisjs/bodyparser/factories' import { RequestFactory, ResponseFactory, HttpContextFactory } from '@adonisjs/http-server/factories' test('enforces per-parser size limits', async ({ assert }) => { const server = createServer(async (req, res) => { const ctx = new HttpContextFactory() .merge({ request: new RequestFactory().merge({ req, res }).create(), response: new ResponseFactory().merge({ req, res }).create(), }) .create() const middleware = new BodyParserMiddlewareFactory() .merge({ json: { limit: 100 }, // 100 bytes form: { limit: 100 }, multipart: { limit: '1mb' }, }) .create() try { await middleware.handle(ctx, async () => { res.writeHead(200) res.end() }) } catch (err) { res.writeHead(err.status ?? 500) res.end(err.message) } }) const { text } = await supertest(server) .post('/') .type('json') .send({ payload: 'a'.repeat(200) }) .expect(413) assert.equal(text, 'request entity too large') }) ``` ``` -------------------------------- ### Retrieve all uploaded files as a keyed object Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `request.allFiles()` to get a raw map of all uploaded files, where keys are field names and values are either a single `MultipartFile` or an array of `MultipartFile`. Files retrieved this way are not yet validated; manual validation using `file.validate()` is required if not using `request.file()` or `request.files()`. ```typescript import type { HttpContext } from '@adonisjs/http-server' import type { MultipartFile } from '@adonisjs/bodyparser' export default class UploadsController { async inspect({ request, response }: HttpContext) { const all = request.allFiles() // { avatar: MultipartFile, docs: [MultipartFile, MultipartFile] } const summary = Object.entries(all).map(([field, fileOrFiles]) => { const files = Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles] return files.map((f: MultipartFile) => ({ field, clientName: f.clientName, size: f.size, extname: f.extname, type: f.type, // e.g. 'image' subtype: f.subtype, // e.g. 'png' state: f.state, // 'idle' | 'streaming' | 'consumed' | 'moved' })) }).flat() return response.ok(summary) } } ``` -------------------------------- ### Retrieve a single uploaded file with validation Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `request.file()` to get a single uploaded file for a given field name. It applies validation constraints like size and allowed extensions. Returns `null` if the file is not present. ```typescript // app/controllers/avatars_controller.ts import type { HttpContext } from '@adonisjs/http-server' export default class AvatarsController { async store({ request, response }: HttpContext) { const avatar = request.file('avatar', { size: '2mb', extnames: ['jpg', 'jpeg', 'png', 'webp'], }) if (!avatar) { return response.badRequest({ error: 'No file uploaded' }) } if (!avatar.isValid) { return response.unprocessableEntity({ errors: avatar.errors }) // errors[0] example: // { fieldName: 'avatar', clientName: 'photo.bmp', // message: 'Invalid file extension bmp. Only jpg, jpeg, png, webp are allowed', // type: 'extname' } } await avatar.move('/var/www/uploads', { name: `${Date.now()}.${avatar.extname}`, overwrite: true, }) return response.ok({ path: avatar.filePath, size: avatar.size }) } } ``` -------------------------------- ### Accumulate Form Fields with Normalization Source: https://context7.com/adonisjs/bodyparser/llms.txt Use FormFields to manually build flat and nested form data. An optional normalizer function can process each value, for example, to trim whitespace or convert empty strings to null. Useful for custom parsers or tests. ```typescript import { FormFields } from '@adonisjs/bodyparser/src/form_fields' // Build form data manually (useful in custom parsers / tests) const fields = new FormFields((value) => value.trim() === '' ? null : value.trim()) fields.add('username', 'virk') fields.add('tags[]', 'adonisjs') fields.add('tags[]', 'nodejs') fields.add('address[city]', 'Mumbai') fields.add('emptyField', '') console.log(fields.get()) // { // username: 'virk', // tags: ['adonisjs', 'nodejs'], // address: { city: 'Mumbai' }, // emptyField: null // } ``` -------------------------------- ### request.allFiles() Source: https://context7.com/adonisjs/bodyparser/llms.txt Retrieves every uploaded file as a keyed object. This method returns the raw `__raw_files` map, which is a `Record`. Files returned by this method have not yet been validated; manual validation using `file.validate()` is required, or use `request.file()` / `request.files()` for automatic validation. ```APIDOC ## `request.allFiles()` — Retrieve every uploaded file as a keyed object Returns the raw `__raw_files` map: `Record`. Files returned here have **not** been validated yet; call `file.validate()` manually or use `request.file()` / `request.files()` which validate automatically. ### Example Usage ```ts import type { HttpContext } from '@adonisjs/http-server' import type { MultipartFile } from '@adonisjs/bodyparser' export default class UploadsController { async inspect({ request, response }: HttpContext) { const all = request.allFiles() // { avatar: MultipartFile, docs: [MultipartFile, MultipartFile] } const summary = Object.entries(all).map(([field, fileOrFiles]) => { const files = Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles] return files.map((f: MultipartFile) => ({ field, clientName: f.clientName, size: f.size, extname: f.extname, type: f.type, // e.g. 'image' subtype: f.subtype, // e.g. 'png' state: f.state, // 'idle' | 'streaming' | 'consumed' | 'moved' })) }).flat() return response.ok(summary) } } ``` ``` -------------------------------- ### defineConfig - Configuration Helper Source: https://context7.com/adonisjs/bodyparser/llms.txt The defineConfig helper is used to create a fully typed BodyParserConfig object by merging user-supplied overrides with sensible defaults for various parsers. ```APIDOC ## defineConfig(config) Merges user-supplied partial overrides with defaults for all four parsers and returns a complete `BodyParserConfig` object. This is the single entry-point for configuring the middleware. ```ts // config/bodyparser.ts import { defineConfig } from '@adonisjs/bodyparser' export default defineConfig({ // Only parse bodies for these HTTP methods (default: POST, PUT, PATCH, DELETE) allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], json: { encoding: 'utf-8', limit: '4mb', // string or bytes number strict: true, // reject non-array/object JSON convertEmptyStringsToNull: true, trimWhitespaces: true, types: [ 'application/json', 'application/json-patch+json', 'application/vnd.api+json', ], }, form: { encoding: 'utf-8', limit: '2mb', convertEmptyStringsToNull: true, trimWhitespaces: true, queryString: { depth: 5, allowDots: true, comma: true, }, types: ['application/x-www-form-urlencoded'], }, raw: { encoding: 'utf-8', limit: '1mb', types: ['text/*'], }, multipart: { autoProcess: true, // auto-save files to OS tmpdir processManually: [], // route patterns to skip auto-processing maxFields: 1000, limit: '50mb', // total bytes across all files fieldsLimit: '2mb', // total bytes of non-file fields convertEmptyStringsToNull: true, trimWhitespaces: true, tmpFileName() { // optional: customise temp file name return `upload-${Date.now()}.tmp` }, }, }) ``` ``` -------------------------------- ### Configure BodyParser Middleware Source: https://context7.com/adonisjs/bodyparser/llms.txt Use defineConfig to merge custom configurations with defaults for JSON, form, raw, and multipart parsers. Customize limits, encodings, and file upload behavior. ```typescript import { defineConfig } from '@adonisjs/bodyparser' export default defineConfig({ // Only parse bodies for these HTTP methods (default: POST, PUT, PATCH, DELETE) allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], json: { encoding: 'utf-8', limit: '4mb', strict: true, convertEmptyStringsToNull: true, trimWhitespaces: true, types: [ 'application/json', 'application/json-patch+json', 'application/vnd.api+json', ], }, form: { encoding: 'utf-8', limit: '2mb', convertEmptyStringsToNull: true, trimWhitespaces: true, queryString: { depth: 5, allowDots: true, comma: true, }, types: ['application/x-www-form-urlencoded'], }, raw: { encoding: 'utf-8', limit: '1mb', types: ['text/*'], }, multipart: { autoProcess: true, processManually: [], maxFields: 1000, limit: '50mb', fieldsLimit: '2mb', convertEmptyStringsToNull: true, trimWhitespaces: true, tmpFileName() { return `upload-${Date.now()}.tmp` }, }, }) ``` -------------------------------- ### Create BodyParserMiddleware instances with BodyParserMiddlewareFactory Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `BodyParserMiddlewareFactory` from `@adonisjs/bodyparser/factories` to create middleware instances for testing. This factory allows deep-merging configuration overrides before creating the middleware, useful for testing per-parser size limits. ```typescript import { test } from '@japa/runner' import { createServer } from 'node:http' import supertest from 'supertest' import { BodyParserMiddlewareFactory } from '@adonisjs/bodyparser/factories' import { RequestFactory, ResponseFactory, HttpContextFactory } from '@adonisjs/http-server/factories' test('enforces per-parser size limits', async ({ assert }) => { const server = createServer(async (req, res) => { const ctx = new HttpContextFactory() .merge({ request: new RequestFactory().merge({ req, res }).create(), response: new ResponseFactory().merge({ req, res }).create(), }) .create() const middleware = new BodyParserMiddlewareFactory() .merge({ json: { limit: 100 }, // 100 bytes form: { limit: 100 }, multipart: { limit: '1mb' }, }) .create() try { await middleware.handle(ctx, async () => { res.writeHead(200) res.end() }) } catch (err) { res.writeHead(err.status ?? 500) res.end(err.message) } }) const { text } = await supertest(server) .post('/') .type('json') .send({ payload: 'a'.repeat(200) }) .expect(413) assert.equal(text, 'request entity too large') }) ``` -------------------------------- ### BodyParserMiddleware - Core Middleware Source: https://context7.com/adonisjs/bodyparser/llms.txt The BodyParserMiddleware reads the Content-Type, selects the matching parser, and populates ctx.request with parsed body data and bodyType. It must be registered as global middleware. ```APIDOC ## BodyParserMiddleware Reads `Content-Type`, selects the matching parser, and populates `ctx.request` with parsed body data and `bodyType`. Must be registered as global middleware in the AdonisJS HTTP kernel. After parsing, `ctx.request.bodyType` is one of `'json' | 'urlencoded' | 'multipart' | 'raw' | 'unknown'`. ```ts // start/kernel.ts (AdonisJS application bootstrap) import { defineConfig } from '@adonisjs/bodyparser' import { BodyParserMiddleware } from '@adonisjs/bodyparser/bodyparser_middleware' // Typically wired through the IoC container: router.use([ () => import('@adonisjs/bodyparser/bodyparser_middleware'), ]) // ---- Low-level instantiation (e.g. in tests or custom servers) ---- import { createServer } from 'node:http' import { BodyParserMiddlewareFactory } from '@adonisjs/bodyparser/factories' import { RequestFactory, ResponseFactory, HttpContextFactory } from '@adonisjs/http-server/factories' const server = createServer(async (req, res) => { const request = new RequestFactory().merge({ req, res }).create() const response = new ResponseFactory().merge({ req, res }).create() const ctx = new HttpContextFactory().merge({ request, response }).create() const middleware = new BodyParserMiddlewareFactory() .merge({ json: { limit: '2mb' } }) .create() await middleware.handle(ctx, async () => { // ctx.request.bodyType === 'json' // ctx.request.all() === { username: 'virk' } res.writeHead(200, { 'content-type': 'application/json' }) res.end(JSON.stringify({ body: ctx.request.all(), bodyType: ctx.request.bodyType, })) }) }) // POST / with Content-Type: application/json {"username":"virk"} // → { body: { username: 'virk' }, bodyType: 'json' } ``` ``` -------------------------------- ### MultipartFile.move() Source: https://context7.com/adonisjs/bodyparser/llms.txt Moves an uploaded file from its temporary location to a permanent one. It can create directories recursively and handles cross-device moves. It throws an error if overwrite is false and the destination file already exists. ```APIDOC ## `MultipartFile.move(location, options?)` — Persist an uploaded file Moves the file from its OS temp path to a permanent location. Creates the destination directory recursively if needed. Handles cross-device moves (EXDEV) transparently by falling back to copy + delete. Throws if `overwrite` is `false` and the destination already exists. ### Parameters #### Path Parameters - **location** (string) - Required - The destination path to move the file to. #### Options - **name** (string) - Optional - The new name for the file. If not provided, the original filename is used. - **overwrite** (boolean) - Optional - Whether to overwrite the file if it already exists at the destination. Defaults to `false`. ### Request Example ```ts import { cuid } from '@adonisjs/core/helpers' import type { HttpContext } from '@adonisjs/http-server' export default class ProfileController { async update({ request, response }: HttpContext) { const photo = request.file('photo', { size: '5mb', extnames: ['jpg', 'png'] }) if (!photo || !photo.isValid) { return response.unprocessableEntity({ errors: photo?.errors ?? ['No file'] }) } await photo.move('/var/www/public/uploads', { name: `${cuid()}.${photo.extname}`, overwrite: false, }) return response.ok(photo.toJSON()) } } ``` ### Response #### Success Response (200) - **filePath** (string) - The final path of the moved file. - **fileName** (string) - The final name of the moved file. - **state** (string) - The state of the file, which will be 'moved'. #### Response Example ```json { "fieldName": "photo", "clientName": "selfie.jpg", "size": 204800, "filePath": "/var/www/public/uploads/clx1234.jpg", "fileName": "clx1234.jpg", "type": "image", "extname": "jpg", "subtype": "jpeg", "state": "moved", "isValid": true, "validated": true, "errors": [], "meta": {} } ``` ``` -------------------------------- ### Persist Uploaded File with MultipartFile.move() Source: https://context7.com/adonisjs/bodyparser/llms.txt Moves an uploaded file from its temporary location to a permanent destination. The destination directory is created if it doesn't exist. Handles cross-device moves and throws an error if `overwrite` is false and the file already exists. ```typescript import { cuid } from '@adonisjs/core/helpers' import type { HttpContext } from '@adonisjs/http-server' export default class ProfileController { async update({ request, response }: HttpContext) { const photo = request.file('photo', { size: '5mb', extnames: ['jpg', 'png'] }) if (!photo || !photo.isValid) { return response.unprocessableEntity({ errors: photo?.errors ?? ['No file'] }) } // Move to /public/uploads/. await photo.move('/var/www/public/uploads', { name: `${cuid()}.${photo.extname}`, overwrite: false, // throws if file already exists }) // After move(): // photo.state === 'moved' // photo.filePath === '/var/www/public/uploads/clx1234.jpg' // photo.fileName === 'clx1234.jpg' return response.ok(photo.toJSON()) // { // fieldName: 'photo', // clientName: 'selfie.jpg', // size: 204800, // filePath: '/var/www/public/uploads/clx1234.jpg', // fileName: 'clx1234.jpg', // type: 'image', // extname: 'jpg', // subtype: 'jpeg', // state: 'moved', // isValid: true, // validated: true, // errors: [], // meta: {} // } } } ``` -------------------------------- ### request.files(key, options?) Source: https://context7.com/adonisjs/bodyparser/llms.txt Retrieves multiple uploaded files for a given field name. This method is similar to `request.file()` but always returns an array of `MultipartFile` objects. It applies the same validation constraints to every file in the array. ```APIDOC ## `request.files(key, options?)` — Retrieve multiple uploaded files Same as `request.file` but always returns a `MultipartFile[]`. Applies the same validation constraints to every file in the array. ### Example Usage ```ts // app/controllers/documents_controller.ts import type { HttpContext } from '@adonisjs/http-server' export default class DocumentsController { async upload({ request, response }: HttpContext) { const docs = request.files('documents', { size: '10mb', extnames: ['pdf', 'doc', 'docx'], }) const results = await Promise.all( docs.map(async (doc) => { if (!doc.isValid) { return { ok: false, errors: doc.errors } } await doc.move('/var/www/documents') return { ok: true, filePath: doc.filePath, size: doc.size } }) ) return response.ok({ results }) } } // Supports array notation in form fields: // // ``` ``` -------------------------------- ### Create fake MultipartFile instances with MultipartFileFactory Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `MultipartFileFactory` from `@adonisjs/bodyparser/factories` to create fake `MultipartFile` objects for unit testing controllers or validators in isolation. Configure file properties like size, type, and name. ```typescript import { test } from '@japa/runner' import { MultipartFileFactory } from '@adonisjs/bodyparser/factories' test.group('Avatar validation', () => { test('rejects files over 2 MB', ({ assert }) => { const file = new MultipartFileFactory() .merge({ fieldName: 'avatar', clientName: 'photo.jpg', extname: 'jpg', type: 'image', subtype: 'jpeg', size: 3_000_000, // 3 MB — over the 2 MB limit }) .create({ size: '2mb', extnames: ['jpg', 'png'] }) assert.isFalse(file.isValid) assert.equal(file.errors[0].type, 'size') assert.match(file.errors[0].message, /less than 2MB/) }) test('accepts valid image', ({ assert }) => { const file = new MultipartFileFactory() .merge({ fieldName: 'avatar', clientName: 'avatar.png', extname: 'png', type: 'image', subtype: 'png', size: 512_000, // 500 KB }) .create({ size: '2mb', extnames: ['jpg', 'png'] }) assert.true(file.isValid) assert.equal(file.state, 'consumed') assert.equal(file.extname, 'png') }) }) ``` -------------------------------- ### request.file(key, options?) Source: https://context7.com/adonisjs/bodyparser/llms.txt Retrieves a single uploaded file for a given field name. It applies optional validation constraints such as size and allowed file extensions, and returns the file object or null if not present. If the field contains an array of files, the first file is returned. ```APIDOC ## `request.file(key, options?)` — Retrieve a single uploaded file Extension macro added to `HttpRequest`. Locates the `MultipartFile` for the given field name, applies optional validation constraints (`size`, `extnames`), runs validation, and returns the file or `null` when not present. If the field holds an array, the first element is returned. ### Example Usage ```ts // app/controllers/avatars_controller.ts import type { HttpContext } from '@adonisjs/http-server' export default class AvatarsController { async store({ request, response }: HttpContext) { const avatar = request.file('avatar', { size: '2mb', extnames: ['jpg', 'jpeg', 'png', 'webp'], }) if (!avatar) { return response.badRequest({ error: 'No file uploaded' }) } if (!avatar.isValid) { return response.unprocessableEntity({ errors: avatar.errors }) // errors[0] example: // { fieldName: 'avatar', clientName: 'photo.bmp', // message: 'Invalid file extension bmp. Only jpg, jpeg, png, webp are allowed', // type: 'extname' } } await avatar.move('/var/www/uploads', { name: `${Date.now()}.${avatar.extname}`, overwrite: true, }) return response.ok({ path: avatar.filePath, size: avatar.size }) } } ``` ``` -------------------------------- ### Low-Level Streaming with Multipart Class Source: https://context7.com/adonisjs/bodyparser/llms.txt Processes multipart streams directly, allowing for custom per-field handlers before files are saved to disk. Use this when `autoProcess` is false or routes are configured for manual processing. Files are accessed via `request.file()` after `multipart.process()` completes. ```typescript // Route configured with processManually: ['/upload/s3'] import { createWriteStream } from 'node:fs' import { pipeline } from 'node:stream/promises' import type { HttpContext } from '@adonisjs/http-server' import { streamFile } from '@adonisjs/bodyparser' // re-exported helper export default class S3UploadController { async store({ request, response }: HttpContext) { const multipart = request.multipart // Wildcard handler — called for every file field multipart.onFile('*', { size: '100mb', extnames: ['mp4', 'mov'] }, async (part, reporter) => { const tmpPath = `/tmp/${part.filename}` // streamFile pipes the readable to disk and calls reporter per chunk await streamFile(part, tmpPath, reporter) // Return metadata consumed by MultipartFile internals return { tmpPath } }) // Named handler takes priority over wildcard multipart.onFile('thumbnail', { size: '2mb', extnames: ['jpg', 'png'] }, async (part, reporter) => { const tmpPath = `/tmp/thumb-${part.filename}` await streamFile(part, tmpPath, reporter) return { tmpPath } }) try { await multipart.process() } catch (err) { // E_INVALID_MULTIPART_REQUEST, E_REQUEST_ENTITY_TOO_LARGE, etc. return response.badRequest({ error: err.message }) } // After process(), files are available on request const video = request.file('video') const thumbnail = request.file('thumbnail') return response.ok({ video: video?.toJSON(), thumbnail: thumbnail?.toJSON(), }) } } ``` -------------------------------- ### MultipartFileFactory Source: https://context7.com/adonisjs/bodyparser/llms.txt A testing factory exported from `@adonisjs/bodyparser/factories` that creates fake `MultipartFile` instances. It allows for the production of fully configured `MultipartFile` objects without an actual HTTP upload, making it ideal for unit-testing controllers or validators in isolation. ```APIDOC ## `MultipartFileFactory` — Create fake `MultipartFile` instances for tests Testing factory (exported from `@adonisjs/bodyparser/factories`) that produces fully configured `MultipartFile` objects without an actual HTTP upload. Useful for unit-testing controllers or validators in isolation. ```ts import { test } from '@japa/runner' import { MultipartFileFactory } from '@adonisjs/bodyparser/factories' test.group('Avatar validation', () => { test('rejects files over 2 MB', ({ assert }) => { const file = new MultipartFileFactory() .merge({ fieldName: 'avatar', clientName: 'photo.jpg', extname: 'jpg', type: 'image', subtype: 'jpeg', size: 3_000_000, // 3 MB — over the 2 MB limit }) .create({ size: '2mb', extnames: ['jpg', 'png'] }) assert.isFalse(file.isValid) assert.equal(file.errors[0].type, 'size') assert.match(file.errors[0].message, /less than 2MB/) }) test('accepts valid image', ({ assert }) => { const file = new MultipartFileFactory() .merge({ fieldName: 'avatar', clientName: 'avatar.png', extname: 'png', type: 'image', subtype: 'png', size: 512_000, // 500 KB }) .create({ size: '2mb', extnames: ['jpg', 'png'] }) assert.isTrue(file.isValid) assert.equal(file.state, 'consumed') assert.equal(file.extname, 'png') }) }) ``` ``` -------------------------------- ### Manually Validate File with MultipartFile.validate() Source: https://context7.com/adonisjs/bodyparser/llms.txt Manually triggers size and extension validators for a file. This is useful when you need to set constraints after retrieving the file. The validators are idempotent and only run if `validated` is false. ```typescript import type { HttpContext } from '@adonisjs/http-server' export default class ReportsController { async store({ request, response }: HttpContext) { // Get file without auto-validation const raw = request.allFiles()['report'] as import('@adonisjs/bodyparser').MultipartFile // Set constraints and validate raw.sizeLimit = '20mb' raw.allowedExtensions = ['pdf', 'xlsx', 'csv'] raw.validate() if (!raw.isValid) { return response.unprocessableEntity({ // errors example: // [{ fieldName: 'report', clientName: 'data.zip', // message: 'Invalid file extension zip. Only pdf, xlsx, csv are allowed', // type: 'extname' }] errors: raw.errors, }) } await raw.move('/var/www/reports') return response.ok({ saved: raw.filePath }) } } ``` -------------------------------- ### Multipart class Source: https://context7.com/adonisjs/bodyparser/llms.txt Provides low-level access to the multipart stream for processing files before they are saved to disk. It allows registering handlers for specific file fields or a wildcard handler for all files. ```APIDOC ## `Multipart` class — Low-level streaming multipart processor Provides direct access to the multipart stream before files touch the disk. Use when `autoProcess` is `false` or when the route is listed in `processManually`. Register per-field handlers with `onFile()`, then call `process()` to consume the stream. ### Methods #### `onFile(fieldName, options, handler)` Registers a handler for a specific file field or a wildcard. - **fieldName** (string) - The name of the file field, or '*' for a wildcard. - **options** (object) - Validation options like `size` and `extnames`. - **handler** (function) - An async function that receives the file part and a reporter. #### `process()` Consumes the multipart stream and processes all registered file handlers. ### Request Example ```ts // Route configured with processManually: ['/upload/s3'] import { createWriteStream } from 'node:fs' import { pipeline } from 'node:stream/promises' import type { HttpContext } from '@adonisjs/http-server' import { streamFile } from '@adonisjs/bodyparser' export default class S3UploadController { async store({ request, response }: HttpContext) { const multipart = request.multipart multipart.onFile('*', { size: '100mb', extnames: ['mp4', 'mov'] }, async (part, reporter) => { const tmpPath = `/tmp/${part.filename}` await streamFile(part, tmpPath, reporter) return { tmpPath } // Metadata for MultipartFile internals }) multipart.onFile('thumbnail', { size: '2mb', extnames: ['jpg', 'png'] }, async (part, reporter) => { const tmpPath = `/tmp/thumb-${part.filename}` await streamFile(part, tmpPath, reporter) return { tmpPath } }) try { await multipart.process() } catch (err) { return response.badRequest({ error: err.message }) } const video = request.file('video') const thumbnail = request.file('thumbnail') return response.ok({ video: video?.toJSON(), thumbnail: thumbnail?.toJSON(), }) } } ``` ### Response #### Success Response (200) - **video** (object | null) - JSON representation of the video file if uploaded. - **thumbnail** (object | null) - JSON representation of the thumbnail file if uploaded. #### Response Example ```json { "video": { "fieldName": "video", "clientName": "my_video.mp4", "size": 104857600, "filePath": "/tmp/my_video.mp4", "fileName": "my_video.mp4", "type": "video", "extname": "mp4", "state": "moved", "isValid": true, "validated": true, "errors": [], "meta": { "tmpPath": "/tmp/my_video.mp4" } }, "thumbnail": { "fieldName": "thumbnail", "clientName": "thumb.jpg", "size": 2097152, "filePath": "/tmp/thumb-thumb.jpg", "fileName": "thumb.jpg", "type": "image", "extname": "jpg", "state": "moved", "isValid": true, "validated": true, "errors": [], "meta": { "tmpPath": "/tmp/thumb-thumb.jpg" } } } ``` #### Error Response - **error** (string) - A message describing the error encountered during multipart processing (e.g., `E_INVALID_MULTIPART_REQUEST`, `E_REQUEST_ENTITY_TOO_LARGE`). ``` -------------------------------- ### Stream Readable to File with Progress Tracking Source: https://context7.com/adonisjs/bodyparser/llms.txt The streamFile utility pipes a Node.js Readable stream to a file on disk. It can optionally track upload progress by firing a data listener for each chunk received. Partially written files are cleaned up automatically on error. ```typescript import { createReadStream } from 'node:fs' import { streamFile } from '@adonisjs/bodyparser' // re-exported via index.ts // Stream a readable to a temp path while counting bytes let totalBytes = 0 await streamFile( createReadStream('/source/large-video.mp4'), '/tmp/upload-123.mp4', (chunk) => { totalBytes += chunk.length process.stdout.write(`\rReceived ${(totalBytes / 1024 / 1024).toFixed(1)} MB`) } ) console.log('\nDone!') ``` -------------------------------- ### MultipartFile.validate() Source: https://context7.com/adonisjs/bodyparser/llms.txt Manually triggers size and extension validators for an uploaded file. This is typically called automatically by `request.file()` and `request.files()`, but can be invoked explicitly after setting `sizeLimit` or `allowedExtensions`. ```APIDOC ## `MultipartFile.validate()` — Run size and extension validators manually Triggers both the `SizeValidator` and `ExtensionValidator` on the file. Called automatically by `request.file()` and `request.files()`, but can be invoked manually after setting `sizeLimit` / `allowedExtensions`. Each validator is idempotent once `validated === true`. ### Parameters This method does not accept any parameters. ### Request Example ```ts import type { HttpContext } from '@adonisjs/http-server' export default class ReportsController { async store({ request, response }: HttpContext) { const raw = request.allFiles()['report'] as import('@adonisjs/bodyparser').MultipartFile raw.sizeLimit = '20mb' raw.allowedExtensions = ['pdf', 'xlsx', 'csv'] raw.validate() if (!raw.isValid) { return response.unprocessableEntity({ errors: raw.errors, }) } await raw.move('/var/www/reports') return response.ok({ saved: raw.filePath }) } } ``` ### Response #### Success Response (200) - **filePath** (string) - The path where the file was saved after validation and moving. #### Response Example ```json { "saved": "/var/www/reports/your-file.pdf" } ``` #### Error Response - **errors** (array) - An array of error objects detailing validation failures. Example error object: ```json { "fieldName": "report", "clientName": "data.zip", "message": "Invalid file extension zip. Only pdf, xlsx, csv are allowed", "type": "extname" } ``` ``` -------------------------------- ### Abort multipart processing with Multipart.abort() Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `multipart.abort()` to immediately stop all part processing and reject the `process()` promise. This is useful for early termination in custom handlers, such as detecting path traversal. ```typescript import type { HttpContext } from '@adonisjs/http-server' export default class ControlledUploadController { async store({ request, response }: HttpContext) { const multipart = request.multipart let rejected = false multipart.onFile('*', {}, async (part, reporter) => { // Reject any file whose reported name looks suspicious if (part.filename.includes('..')) { multipart.abort(new Error('Path traversal detected')) rejected = true return } part.resume() // consume and discard the stream }) try { await multipart.process() } catch (err) { if (rejected) { return response.forbidden({ error: err.message }) } throw err } return response.ok({ status: 'accepted' }) } } ``` -------------------------------- ### streamFile Function Source: https://context7.com/adonisjs/bodyparser/llms.txt The streamFile function pipes any Node.js Readable stream to a file on the filesystem. It can optionally accept a dataListener callback that is invoked for each Buffer chunk received, allowing for tracking of upload progress or byte counts. Partially written files are automatically cleaned up in case of an error. ```APIDOC ## `streamFile(readStream, location, dataListener?)` — Stream a readable to disk Pipes any Node.js `Readable` to a file on the filesystem. Optionally fires `dataListener` for each `Buffer` chunk received — this is how the `BodyParserMiddleware` and the `Multipart` class track bytes for the upload limit. Cleans up the partially-written file automatically on error. ```ts import { createReadStream } from 'node:fs' import { streamFile } from '@adonisjs/bodyparser' // re-exported via index.ts // Stream a readable to a temp path while counting bytes let totalBytes = 0 await streamFile( createReadStream('/source/large-video.mp4'), '/tmp/upload-123.mp4', (chunk) => { totalBytes += chunk.length process.stdout.write(`\rReceived ${(totalBytes / 1024 / 1024).toFixed(1)} MB`) } ) console.log('\nDone!') ``` ``` -------------------------------- ### Detect File Type from Buffer Source: https://context7.com/adonisjs/bodyparser/llms.txt Use getFileType to determine a file's true extension and MIME type by reading its magic bytes. This is independent of the filename and is used internally by the multipart part handler. Returns null if the type cannot be identified. ```typescript import { readFile } from 'node:fs/promises' import { getFileType } from '@adonisjs/bodyparser/src/utils' const buffer = await readFile('/tmp/upload-123') const result = await getFileType(buffer) console.log(result) // { ext: 'png', type: 'image', subtype: 'png' } // or null if unrecognised ``` -------------------------------- ### Register BodyParser Middleware in Kernel Source: https://context7.com/adonisjs/bodyparser/llms.txt Register the BodyParserMiddleware globally in your AdonisJS HTTP kernel to automatically parse incoming request bodies. ```typescript // start/kernel.ts (AdonisJS application bootstrap) import { defineConfig } from '@adonisjs/bodyparser' import { BodyParserMiddleware } from '@adonisjs/bodyparser/bodyparser_middleware' // Typically wired through the IoC container: router.use([ () => import('@adonisjs/bodyparser/bodyparser_middleware'), ]) ``` -------------------------------- ### Error Codes Reference Source: https://context7.com/adonisjs/bodyparser/llms.txt Reference for error codes that the BodyParserMiddleware maps to typed AdonisJS exceptions. These can be caught and handled in the global exception handler to provide specific responses for different parsing errors. ```APIDOC ## Error codes reference The middleware maps raw-body parse errors to typed AdonisJS exceptions. The following error codes can be caught and handled in the global exception handler: ```ts // app/exceptions/handler.ts import { HttpExceptionHandler } from '@adonisjs/core/http' import type { HttpContext } from '@adonisjs/http-server' export default class ExceptionHandler extends HttpExceptionHandler { async handle(error: any, ctx: HttpContext) { switch (error.code) { case 'E_REQUEST_ENTITY_TOO_LARGE': // HTTP 413 — body or file exceeds the configured limit return ctx.response.status(413).send({ error: 'Payload too large' }) case 'E_ENCODING_UNSUPPORTED': // HTTP 415 — the charset in the Content-Type is not supported return ctx.response.status(415).send({ error: 'Unsupported encoding' }) case 'E_REQUEST_ABORTED': // HTTP 400 — client disconnected before sending full body return ctx.response.status(400).send({ error: 'Request aborted' }) case 'E_INVALID_MULTIPART_REQUEST': // HTTP 400 — malformed multipart body return ctx.response.status(400).send({ error: 'Invalid multipart request' }) case 'E_MISSING_FILE_TMP_PATH': // HTTP 500 — move() called before tmpPath was set return ctx.response.status(500).send({ error: 'Internal error' }) default: return super.handle(error, ctx) } } } ``` ``` -------------------------------- ### getFileType Function Source: https://context7.com/adonisjs/bodyparser/llms.txt The getFileType function detects a file's type, extension, and MIME type by reading its magic bytes (binary signature). This is independent of the filename provided by the client and is used internally by the multipart part handler. It returns null if the file type cannot be determined. ```APIDOC ## `getFileType(buffer)` — Detect file type from magic bytes Reads the binary signature of a file buffer to determine the true extension and MIME type, independent of the filename the client supplied. Used internally by the multipart part handler after streaming. Returns `null` when the type cannot be determined. ```ts import { readFile } from 'node:fs/promises' import { getFileType } from '@adonisjs/bodyparser/src/utils' const buffer = await readFile('/tmp/upload-123') const result = await getFileType(buffer) console.log(result) // { ext: 'png', type: 'image', subtype: 'png' } // or null if unrecognised ``` ``` -------------------------------- ### Multipart.abort(error) Source: https://context7.com/adonisjs/bodyparser/llms.txt Aborts multipart processing by emitting an error event on the underlying form stream. This immediately stops all part processing and rejects the `process()` promise, which is useful for early termination in custom handlers. ```APIDOC ## `Multipart.abort(error)` — Abort multipart processing Emits an error event on the underlying form stream, immediately stopping all part processing and rejecting the `process()` promise. Useful for early termination in custom handlers. ```ts import type { HttpContext } from '@adonisjs/http-server' export default class ControlledUploadController { async store({ request, response }: HttpContext) { const multipart = request.multipart let rejected = false multipart.onFile('*', {}, async (part, reporter) => { // Reject any file whose reported name looks suspicious if (part.filename.includes('..')) { multipart.abort(new Error('Path traversal detected')) rejected = true return } part.resume() // consume and discard the stream }) try { await multipart.process() } catch (err) { if (rejected) { return response.forbidden({ error: err.message }) } throw err } return response.ok({ status: 'accepted' }) } } ``` ``` -------------------------------- ### Retrieve multiple uploaded files with validation Source: https://context7.com/adonisjs/bodyparser/llms.txt Use `request.files()` to retrieve an array of uploaded files for a given field name. It applies the same validation constraints to each file in the array. Ensure your HTML form fields support array notation (e.g., `name='documents[]'`). ```typescript // app/controllers/documents_controller.ts import type { HttpContext } from '@adonisjs/http-server' export default class DocumentsController { async upload({ request, response }: HttpContext) { const docs = request.files('documents', { size: '10mb', extnames: ['pdf', 'doc', 'docx'], }) const results = await Promise.all( docs.map(async (doc) => { if (!doc.isValid) { return { ok: false, errors: doc.errors } } await doc.move('/var/www/documents') return { ok: true, filePath: doc.filePath, size: doc.size } }) ) return response.ok({ results }) } } // Supports array notation in form fields: // // ```