Deno: Request body processing in Oak

Mayank C
Tech Tonic

--

In this article, we’ll cover how Oak handles different request body types. We’ll learn how to access the data present in different request body types. We’ll take examples of the commonly used body types.

How to know if a body is present?

The request object present in the context has an API hasBody, that can be used to find out if a body came in the HTTP request.

import { Application } from "https://deno.land/x/oak/mod.ts";const app = new Application({ logErrors: false });app.use((ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
});
app.listen({ port: 8080 });

Here is a quick test with curl:

~: curl http://localhost:8080 -v
> GET / HTTP/1.1
< HTTP/1.1 415 Unsupported Media Type
< content-type: text/plain; charset=utf-8
< content-length: 22

Unsupported Media Type

How to get request body?

Oak has automatic body parsing feature that is based on the HTTP content-type header. In most of the cases, we don’t have to worry about parsing the request body. There are special cases like stream, where we need to explicitly provide additional options for Oak to return the body as a stream.

Here is the one-liner to get the request body:

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBody = await ctx.request.body().value;
ctx.response.status = 200;
});

How to get a textual body?

The textual request body is returned as a string. There is no additional processing required.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBody = await ctx.request.body().value;
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 -d 'Hello from curl' -H 'content-type: text/plain'
  • Type of reqBody is string
  • Content of reqBody is ‘Hello from curl’

How to get a JSON body?

The JSON request body is returned as a JavaScript object. There is no additional processing required.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBody = await ctx.request.body().value;
console.log(reqBody, typeof reqBody);
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 -d '{"a":1, "b": "c"}' -H 'content-type: application/json'
  • Type of reqBody is Object
  • Content of reqBody is { a: 1, b: “c” }

How to get URL encoded body?

The URL encoded body is returned as an object of URLSearchParams. To get an individual key, get() can be used. To iterate through all the fields, entries() can be used.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBody = await ctx.request.body().value;
console.log("a=", reqBody.get("a"));
for (const e of reqBody.entries()) {
console.log(e);
}
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 -d 'a=1&b=c&d' -H 'content-type: application/x-www-form-urlencoded'
  • Type of reqBody is URLSearchParams
  • Content of reqBody can be obtained using APIs like entries, forEach, get, etc. The return type of value is always a string.
a= 1
[ "a", "1" ]
[ "b", "c" ]
[ "d", "" ]

How to get multipart/form-data with simple fields?

The multipart/form-data request body is returned as a FormDataReader object. This is the proprietary Oak data type. By default, the request body is not parsed. To parse it, we need to call FormDataReader’s read() API that would return a JavaScript object containing an attribute fields that contains KVs of the simple fields.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBodyRaw = await ctx.request.body().value;
const reqBody = await reqBodyRaw.read();
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 -F a=1 -F b=c
  • Type of reqBodyRaw is FormDataReader
  • Type of reqBody is Object
  • Content of reqBody is { fields: { a: “1”, b: “c” } }

How to get multipart/form-data with fields and files?

The multipart/form-data request body is returned as a FormDataReader object. This is the proprietary Oak data type. By default, the request body is not parsed. To parse it, we need to call FormDataReader’s read() API that would return a JavaScript object containing an attribute fields that contains KVs of the simple fields, and an attribute files that contains details of the uploaded files.

By default, the uploaded files are written in the OS temporary folder, and a link to the file path is present in the files object.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBodyRaw = await ctx.request.body().value;
const reqBody = await reqBodyRaw.read();
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 -F a=1 -F b=c -F app.js=@./testdata/sample.js -F random.pdf=@./testdata/sample.pdf

Here is the dump of the reqBody object:

object {
fields: { a: "1", b: "c" },
files: [
{
content: undefined,
contentType: "application/octet-stream",
name: "app.js",
filename: "/var/folders/hx/0f6tcxq52f396vfq0c8xfszc0000gn/T/2a883129/80f3aa6e29fc1ac49a08216cec839d5e5722c9ee.b...",
originalName: "sample.js"
},
{
content: undefined,
contentType: "application/pdf",
name: "random.pdf",
filename: "/var/folders/hx/0f6tcxq52f396vfq0c8xfszc0000gn/T/2a883129/90ec0309b206d9c617250fb2cd6d1111fe1c1f5d.p...",
originalName: "sample.pdf"
}
]
}

How to get body as a ReadableStream?

The request body can be read as a ReadableStream by passing type=stream in the body API.

app.use(async (ctx) => {
if (!ctx.request.hasBody) {
ctx.throw(415);
}
const reqBody = await ctx.request.body({ type: "stream" }).value;
ctx.response.status = 200;
});

Here is a test using curl:

> curl http://localhost:8080 --data-binary "@./testdata/sample.pdf"
  • Type of reqBody is ReadableStream
  • The ReadableStream can be read or redirected

--

--