Introduction#

GitHub this note shows how to upload file to server in next.js using form and route handler.

  • server action and form
  • route handler in app
  • upload file with route handler

Setup Project#

Let create a new next.js project

npx create-next-app@latest

To experiment with server action, route handler, let create a project structure as below

|--app
|--form
|--action.ts
|--page.tsx
|--api
|--upload
|--route.ts
|--upload
|--api
|--route.ts
|--page.tsx
|--layout.tsx
|--page.tsx
|--globals.css
|--public
|--sg.png
|--tailwind.config.js
|--package.json
|--next.config.js
|--tsconfig.json

Server Action#

  • html form and get request
  • server action to handle the request

Let create a server action in /app/form/action.ts

"use server";
const handleForm = async (data: FormData) => {
console.log("username:", data.get("username"));
console.log("email: ", data.get("email"));
console.log("file: ", data.get("upload"));
return {
status: "OK",
message: "message sent OK",
};
};
export default handleForm;

The form at client side, /app/form/page.tsx

// haimtran 27/06/2023
// nextjs server action to handle form action
"use client";
import { useState } from "react";
import handleForm from "./action";
const FormPage = () => {
const [status, setStatus] = useState<string | null>(null);
const submit = async (data: FormData) => {
try {
const res = await handleForm(data);
setStatus("success");
} catch (error) {
console.log(error);
}
};
return (
<div className="dark:bg-slate-800 min-h-screen">
<div className="mx-auto max-w-4xl px-5">
{status ? (
<div className="dark:text-white">Submitted</div>
) : (
<form action={submit}>
<div className="grid gap-6 mb-6 grid-cols-1">
<div>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium dark:text-white"
>
First Name
</label>
<input
id="first_name"
type="text"
placeholder="haimtran"
name="username"
className="text-sm rounded-sm block w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium dark:text-white"
>
Email
</label>
<input
id="email"
type="text"
placeholder="hai@entest.io"
name="email"
className="text-sm rounded-sm w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="upload"
className="block mb-2 text-sm font-medium dark:text-white"
>
Upload File
</label>
<input
id="upload"
type="file"
name="upload"
className="text-sm rounded-sm w-full p-2.5 cursor-pointer dark:bg-white"
></input>
</div>
<div>
<button className="bg-orange-400 px-10 py-3 rounded-sm">
Submit
</button>
</div>
</div>
</form>
)}
</div>
</div>
);
};
export default FormPage;

Route Handler#

  • route handler which handle the upload file
  • upload form which sent the post request on submit

Let create an API or route handler /app/api/upload/route.ts

import { NextRequest, NextResponse } from "next/server";
import { stat, mkdir, writeFile } from "fs/promises";
import { join } from "path";
export async function GET() {
return NextResponse.json({ name: "hai" });
}
export const POST = async (request: NextRequest) => {
console.log("call post method in api upload");
const data = await request.formData();
const file = data.get("file") as Blob | null;
if (!file) {
return NextResponse.json({ name: "hai", route: "/api/upload" });
}
const buffer = Buffer.from(await file.arrayBuffer());
try {
await writeFile("hehe.jpg", buffer);
console.log("write file successfully");
} catch (error) {
console.log(error);
}
return NextResponse.json({ name: "hai", route: "/api/upload" });
};

Let create an UI form which send POST request to route handler on submit

// haimtran 27/06/2023
// nextjs server action to handle form action
"use client";
import { useState } from "react";
const UploadPage = () => {
const [status, setStatus] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const body = new FormData();
const submit = async () => {
if (file) {
console.log(file);
body.append("file", file);
}
try {
await fetch("/api/upload", {
method: "POST",
body: body,
});
} catch (error) {
console.log(error);
}
};
return (
<div className="dark:bg-slate-800 min-h-screen">
<div className="mx-auto max-w-4xl px-5">
{status ? (
<div className="dark:text-white">Submitted</div>
) : (
<form onSubmit={submit}>
<div className="grid gap-6 mb-6 grid-cols-1">
<div>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium dark:text-white"
>
First Name
</label>
<input
id="first_name"
type="text"
placeholder="haimtran"
name="username"
className="text-sm rounded-sm block w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium dark:text-white"
>
Email
</label>
<input
id="email"
type="text"
placeholder="hai@entest.io"
name="email"
className="text-sm rounded-sm w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="upload"
className="block mb-2 text-sm font-medium dark:text-white"
>
Upload File
</label>
<input
id="upload"
type="file"
name="upload"
className="text-sm rounded-sm w-full p-2.5 cursor-pointer dark:bg-white"
onChange={(event) => {
// console.log(event)
if (event.target.files && event.target.files.length > 0) {
let selected = event.target.files[0];
setFile(selected);
// console.log(selected);
}
}}
></input>
</div>
<div>
<button
className="bg-orange-400 px-10 py-3 rounded-sm"
type="submit"
>
Submit
</button>
</div>
</div>
</form>
)}
</div>
</div>
);
};
export default UploadPage;

HTML Form#

There are some options to handle forms

  • use onsubmit right in the form
  • use onclick button and event.preventDefault

Setup the submit function to send POST request to server

onst submit = async () => {
console.log("call submit form");
if (file) {
body.append("file", file);
}
try {
const response = await fetch("/api/upload", {
method: "POST",
body: body,
});
setStatus("success");
} catch (error) {
console.log(error);
}
return false;
};

First option, let use onsubmit in the form to submit or sending information to the backend server

<form
onSubmit={(event) => {
event.preventDefault();
submit();
return false;
}}
method="POST"
></form>

Second option, disable default behavior of form submit and setup onclick button

<form>
<button
className="bg-orange-400 px-10 py-3 rounded-sm"
type="submit"
onClick={(event) => {
event.preventDefault();
submit();
}}
>
Submit
</button>
</form>

Reference#