Upload a file to S3 with Next.js App Router


Previously, with Nextjs. When you have to upload the file to s3 or any other storage provider you need a library like Formidable or any sort to handle and parsing the file content and all those logics.

But with the Latest release of Nextjs App Router, this can be done without any 3rd party library. The API routes can handle this directly.

This tutorial explains the implementation of this.

Prerequisite:

  • Nextjs App Router
  • S3 Bucket or Digital Ocean Spaces

I'm using TypeScript, You can use JavaScript too.

create a .env file with these values, You can get this from AWS Console.

AWS_S3_ENDPOINT
AWS_S3_BUCKET_REGION
AWS_S3_ACCESS_KEY
AWS_S3_ACCESS_SECRET

Install the following dependency, based on the package manager you're using.

#npm
npm install @aws-sdk/client-s3

#yarn
yarn add @aws-sdk/client-s3

#pnpm
pnpm add @aws-sdk/client-s3

Now create a new file called s3Client.ts and paste the following code.

// Filename: s3Client.ts

import { S3, S3ClientConfig } from "@aws-sdk/client-s3";

interface S3ClientCredentials {
    accessKeyId: string;
    secretAccessKey: string;
}

const s3ClientConfig: S3ClientConfig = {
    forcePathStyle: true,
    endpoint: process.env.AWS_S3_ENDPOINT,
    region: process.env.AWS_S3_BUCKET_REGION,
    credentials: {
        accessKeyId: process.env.AWS_S3_ACCESS_KEY as string,
        secretAccessKey: process.env.AWS_S3_ACCESS_SECRET as string,
    } as S3ClientCredentials,
};

const s3Client = new S3(s3ClientConfig);

export { s3Client };

the main advantage of creating a s3client is, that we can call it directly and it'll be imported with the config and everything. it also improves readability and less repetitive code.

Now create a file upload service that imports the above-created s3 client and handles the upload function.

// Filename: s3Service.ts

import { s3Client } from "./s3Client";
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";

export const uploadFile = async ({
    Filename,
    Body,
    CacheControl = 'max-age=15557000, stale-if-error=31536000',
}) => {

    let Key = Filename;

    const bucketParams = {
        Bucket: process.env.AWS_S3_BUCKET,
        Key,
        Body,
        CacheControl,
    };

    try {
        const data = await s3Client.send(new PutObjectCommand(bucketParams));

        if(!data) {
            throw new Error('Error uploading file');
        }
        return data;
    } catch (error) {
        console.log(error);
    }

}

/* Create other functions like delete, and update if needed */

Now comes the main part, the NextJS API route to upload the file.

create a new route.ts under `/api/upload/route.ts` and paste the following code.

import { authenticate } from "@/middleware/authMiddleware";
import { uploadFile } from "./s3Service";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
    const formData = await req.formData();
    const files = formData.getAll("file") as File[];

    const user = await authenticate(req);

    if (!user) {
        return new Response("Unauthorized", { status: 403 });
    }

    try {
        const response = await Promise.all(
            files.map(async (file) => {
                const Body = (await file.arrayBuffer()) as Buffer;
                return uploadFile({
                    Filename: file.name,
                    Body,
                });
            })
        );

        if (!response) {
            return new Response("Upload failed", { status: 500 });
        }

        return NextResponse.json({
            url: `https://${process.env.AWS_S3_BUCKET}.s3.amazonaws.com/${files[0].name}`
        }, {
            status: 200,
        });

    } catch (error) {
        console.log(error);
        return new Response("Upload failed", { status: 500 });
    }
}

Now you can send the request to this API endpoint as FormData and it'll be uploaded to your s3 bucket and return a URL. If your bucket is public, you can view the file using that URL.