GitHub GitHub
docs / Body Parsing

Body Parsing

LiteNode includes built-in support for parsing request bodies — no middleware setup required. It automatically handles JSON, URL-encoded form data, and multipart/form-data.

Accessing the Body

Two equivalent styles are available. Use whichever you prefer:

// Via req.body
app.post("/users", (req, res) => {
    const data = req.body
    res.status(201).json({ created: data })
})

// Via the third handler parameter
app.post("/users", (req, res, data) => {
    res.status(201).json({ created: data })
})

Supported HTTP Methods

Body parsing is automatic for:

  • POST — creating resources
  • PUT — replacing resources
  • PATCH — partial updates
  • DELETE — when a payload is needed alongside deletion

Request Size Limit

Pass a number as the last argument to set a maximum request size in megabytes. The default is 1 MB.

// 512 KB limit
app.post("/upload", handleUpload, 0.5)

// 5 MB limit for larger payloads
app.patch("/update-image", handleImageUpdate, 5)

Supported Content Types

JSON

Send Content-Type: application/json from the client. The body is parsed into a JavaScript object automatically:

app.post(
    "/register",
    async (req, res, data) => {
        const { username, email, password } = data
        if (!username || !email || !password) {
            return res.status(400).json({ error: "Missing required fields" })
        }
        // proceed with registration...
        res.status(201).json({ message: "User registered" })
    },
    0.5,
)

URL-encoded Form Data

Standard HTML form submissions (application/x-www-form-urlencoded) are parsed automatically. LiteNode detects the encoding type — the enctype attribute on the form is optional.

Bracket notation in field names produces nested objects on the server:

<!-- form field: file[title] -->
<input type="text" name="file[title]" />
app.post("/create-file", (req, res, data) => {
    const { file } = data
    // file.title, file.description, etc.
    res.status(201).json({ message: "File created" })
})

multipart/form-data

Used for file uploads. LiteNode uses meros (1 KB) internally to parse multipart data efficiently.

Every field in a multipart form — whether a file or a plain text input — arrives as an array of objects. This is different from JSON and URL-encoded forms, where text fields are plain strings.

Each part object exposes:

Property File field Text field
filename Original filename from the client null
body Binary file content (Buffer) Field value as a UTF-8 Buffer
contentType MIME type of the file text/plain or empty string
headers Raw part headers Raw part headers

File-only upload

import { writeFile } from "node:fs/promises"

app.post(
    "/upload",
    async (req, res, data) => {
        const { files } = data
        for (const file of files) {
            await writeFile(`uploads/${file.filename}`, file.body)
        }
        res.redirect("/uploads")
    },
    5,
)

Mixed form: file + text fields

When a form includes both a file input and text fields alongside it, all fields follow the same array-of-objects shape. Text field values must be read from the body Buffer:

<form action="/profile" method="post" enctype="multipart/form-data">
    <input type="file" name="avatar" />
    <input type="text" name="username" />
    <input type="number" name="age" />
    <button type="submit">Save</button>
</form>
import { writeFile } from "node:fs/promises"

app.post(
    "/profile",
    async (req, res, data) => {
        // Files: use .body directly as a Buffer
        const avatar = data.avatar?.[0]

        // Text fields: .body is a Buffer — convert it to a string first
        const username = data.username?.[0]?.body.toString("utf-8").trim()
        const age = parseInt(data.age?.[0]?.body.toString("utf-8"), 10)

        if (!avatar || !username || isNaN(age)) {
            return res.status(400).json({ error: "Missing required fields." })
        }

        await writeFile(`avatars/${avatar.filename}`, avatar.body)
        res.status(201).json({ username, age })
    },
    2,
)

For forms with many text fields, a small helper keeps the extraction concise:

// Returns the string value of a multipart text field, or a fallback
function getField(data, name, fallback = "") {
    const field = data[name]
    if (!field || !Array.isArray(field) || !field[0]) return fallback
    return field[0].body.toString("utf-8").trim() || fallback
}

app.post(
    "/profile",
    async (req, res, data) => {
        const avatar = data.avatar?.[0]
        const username = getField(data, "username")
        const age = parseInt(getField(data, "age"), 10)

        if (!avatar || !username || isNaN(age)) {
            return res.status(400).json({ error: "Missing required fields." })
        }

        await writeFile(`avatars/${avatar.filename}`, avatar.body)
        res.status(201).json({ username, age })
    },
    2,
)

See the Examples page for full client + server examples for each content type.