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><labelhtmlFor="first_name"className="block mb-2 text-sm font-medium dark:text-white">First Name</label><inputid="first_name"type="text"placeholder="haimtran"name="username"className="text-sm rounded-sm block w-full p-2.5 "></input></div><div><labelhtmlFor="email"className="block mb-2 text-sm font-medium dark:text-white"></label><inputid="email"type="text"placeholder="hai@entest.io"name="email"className="text-sm rounded-sm w-full p-2.5 "></input></div><div><labelhtmlFor="upload"className="block mb-2 text-sm font-medium dark:text-white">Upload File</label><inputid="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><labelhtmlFor="first_name"className="block mb-2 text-sm font-medium dark:text-white">First Name</label><inputid="first_name"type="text"placeholder="haimtran"name="username"className="text-sm rounded-sm block w-full p-2.5 "></input></div><div><labelhtmlFor="email"className="block mb-2 text-sm font-medium dark:text-white"></label><inputid="email"type="text"placeholder="hai@entest.io"name="email"className="text-sm rounded-sm w-full p-2.5 "></input></div><div><labelhtmlFor="upload"className="block mb-2 text-sm font-medium dark:text-white">Upload File</label><inputid="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><buttonclassName="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
<formonSubmit={(event) => {event.preventDefault();submit();return false;}}method="POST"></form>
Second option, disable default behavior of form submit and setup onclick button
<form><buttonclassName="bg-orange-400 px-10 py-3 rounded-sm"type="submit"onClick={(event) => {event.preventDefault();submit();}}>Submit</button></form>