3 min read

How to upload a file in Next.js 13+ App Directory with No libraries

How to upload a file in Next.js 13+ App Directory with No libraries
Photo by Markus Winkler / Unsplash

Uploading a file with Next.js 13 can be done with no third-party libraries letting you move fast and keep bundle size down.

The key is to use the new APIs in Next.js 13 app directory to read out the data from the form upload.

First, let's take a quick look at what a typical client looks like for file upload:

Client Side

'use client'

import { useState } from 'react'

export function UploadForm() {
  const [file, setFile] = useState<File>()

  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (!file) return

    try {
      const data = new FormData()
      data.set('file', file)

      const res = await fetch('/api/upload', {
        method: 'POST',
        body: data
      })
      // handle the error
      if (!res.ok) throw new Error(await res.text())
    } catch (e: any) {
      // Handle errors here
      console.error(e)
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input
        type="file"
        name="file"
        onChange={(e) => setFile(e.target.files?.[0])}
      />
      <input type="submit" value="Upload" />
    </form>
  )
}

This code defines a React functional component called UploadForm. The component renders a form with a file input field and a submit button, allowing users to upload a file to a server. Here are the key parts:

The useState hook is used to create a state variable file and its corresponding update function setFile.

The onSubmit function is an asynchronous event handler for the form submission. It performs the following actions:

  1. Prevents the default form submission behavior.
  2. Checks if a file is selected. If not, it returns early.
  3. Creates a FormData object and appends the selected file to it.
  4. Sends an HTTP POST request to the /api/upload endpoint with the file as its body.
  5. If the response is not successful, it throws an error with the response text.
  6. Catches any errors that occur and logs them to the console.

Note that you do not need to set the header Content-Type to be multipart/form-data. This is done automatically when sending FormData.

The return statement renders the form with the following elements:

  • A file input field, which sets the file state variable when a file is selected.
  • A submit button that triggers the form submission and calls the onSubmit function.

When a user selects a file and clicks the "Upload" button, the onSubmit function sends the file to the server.

This is pretty standard React code. But how do we handle this on the server and do something with the file?

Server API Route

Create the file app/api/upload/route.ts and add the following code:

import { writeFile } from 'fs/promises'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const data = await request.formData()
  const file: File | null = data.get('file') as unknown as File

  if (!file) {
    return NextResponse.json({ success: false })
  }

  const bytes = await file.arrayBuffer()
  const buffer = Buffer.from(bytes)

  // With the file data in the buffer, you can do whatever you want with it.
  // For this, we'll just write it to the filesystem in a new location
  const path = `/tmp/${file.name}`
  await writeFile(path, buffer)
  console.log(`open ${path} to see the uploaded file`)

  return NextResponse.json({ success: true })
}

This code defines an asynchronous function POST that handles an HTTP POST request containing a file, reads the file, and then writes it to the server's local filesystem. Here are the key parts:

  1. The POST function takes a request parameter of type NextRequest. It starts by reading the form data from the request using the await request.formData() method.
  2. Extract the uploaded File object from the form data using data.get('file'). If no file is found, it returns a JSON response with { success: false }.
  3. Read the file content as an ArrayBuffer using await file.arrayBuffer() and then convert it into a Node.js Buffer object using Buffer.from(bytes). This is key. It turns the data from a Web API object into a Node.js Buffer, allowing you to handle the data easily.
  4. For this demo, with the file data in the buffer, the code writes the file to a new location in the server's local filesystem using the writeFile function. The new file path is set to /tmp/${file.name}.
  5. If everything is successful, return a JSON response with { success: true }.

When a client sends a file via an HTTP POST request to this API route, this code reads the file, saves it to the server's filesystem, and returns a JSON response indicating success or failure.

Code

You can grab the full sample project here.