The JS runtimes
Published in

The JS runtimes

File upload server with Bun

In this article, we’ll build a simple native file upload server in Bun. We’ll not use any frameworks, only native APIs. The file upload server will store the uploaded file on the disk. Let’s get started.

First, let’s develop a skeleton for the file server. Here is the code of a simple HTTP server that always returns a 201 response.

const STORE_PATH = "/var/tmp";Bun.serve({
port: 3000,
async fetch(req) {
return new Response(null, { status: 201 });
},
error() {
return new Response(null, { status: 500 });
},
});

Now, let’s add an endpoint called /upload that’ll accept uploaded files. As we’re not using any frameworks, we’ll write a very basic router ourselves.

const STORE_PATH = "/var/tmp";Bun.serve({
port: 3000,
async fetch(req) {
const reqPath = new URL(req.url).pathname;
if (reqPath !== "/upload") {
return new Response(null, { status: 404 });
}
if (req.method !== "POST") {
return new Response(null, { status: 405 });
}
return new Response(null, { status: 201 });
},
error() {
return new Response(null, { status: 500 });
},
});

Next, all the POST requests to /upload must have a request body. Also, there must be a content-disposition header that contains the file name. Let’s add these additional checks.

const STORE_PATH = "/var/tmp";Bun.serve({
port: 3000,
async fetch(req) {
const reqPath = new URL(req.url).pathname;
if (reqPath !== "/upload") {
return new Response(null, { status: 404 });
}
if (req.method !== "POST") {
return new Response(null, { status: 405 });
}
if (!req.body) {
return new Response(null, { status: 400 });
}
const cd = req.headers.get("content-disposition");
if (!cd) {
return new Response(null, { status: 400 });
}
const cdFileName = cd.split(";")[1];
if (!cdFileName) {
return new Response(null, { status: 400 });
}
const fileName = cdFileName.split("=")[1];
if (!fileName) {
return new Response(null, { status: 400 });
}
return new Response(null, { status: 201 });
},
error() {
return new Response(null, { status: 500 });
},
});

At this point, let’s run a round of tests:

> curl http://localhost:3000 -v
> GET / HTTP/1.1
>
< HTTP/1.1 404 HM
< Content-Length: 0
> curl http://localhost:3000/upload -v -X POST
> POST /upload HTTP/1.1
>
< HTTP/1.1 400 HM
< Content-Length: 0
> curl http://localhost:3000/upload -v --data-binary "@./public/sample.pdf"
> POST /upload HTTP/1.1
> Content-Length: 69273
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 400 HM
< Content-Length: 0

Looks good. Next, we’ll create a file with the given file name and then store the uploaded file into the created file. To make this happen, we’ll use Bun’s readableStreamToBlob API. Here is the final code:

const STORE_PATH = "/var/tmp/";Bun.serve({
port: 3000,
async fetch(req) {
const reqPath = new URL(req.url).pathname;
if (reqPath !== "/upload") {
return new Response(null, { status: 404 });
}
if (req.method !== "POST") {
return new Response(null, { status: 405 });
}
if (!req.body) {
return new Response(null, { status: 400 });
}
const cd = req.headers.get("content-disposition");
if (!cd) {
return new Response(null, { status: 400 });
}
const cdFileName = cd.split(";")[1];
if (!cdFileName) {
return new Response(null, { status: 400 });
}
const fileName = cdFileName.split("=")[1];
if (!fileName) {
return new Response(null, { status: 400 });
}
const filePath = `${STORE_PATH}${fileName}`;
await Bun.write(filePath, await Bun.readableStreamToBlob(req.body));
return new Response(null, { status: 201 });
},
error() {
return new Response(null, { status: 500 });
},
});

Let’s do a test using curl:

> curl http://localhost:3000/upload -v --data-binary "@./public/sample.pdf" -H 'content-disposition: attachment; filename=DL.pdf'
> POST /upload HTTP/1.1
> content-disposition: attachment; filename=DL.pdf
> Content-Length: 69273
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 201 HM
< Content-Length: 0
> ls -ltr /var/tmp/DL.pdf ./public/sample.pdf
-rw-r--r--@ 1 mayankc staff 69273 Apr 3 2021 ./public/sample.pdf
-rw-r--r-- 1 mayankc wheel 69273 Nov 8 23:02 /var/tmp/DL.pdf

The file upload server works fine.

Thanks for reading! More articles on similar topics can be seen in the magazine: The JS runtimes.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store