DEV Community

Cover image for Upload files with NextJS + Fetch + Api routes + Typescript
Igor
Igor

Posted on

Upload files with NextJS + Fetch + Api routes + Typescript

You will learn, how to upload files using React, NextJs and Fetch API.


First of all, let’s create NextJs application by running:

yarn create next-app --typescript
Enter fullscreen mode Exit fullscreen mode

After the installation is complete, run yarn dev to start the development server on http://localhost:3000 .

If you open the page, you will see something similar to the following:

NextJs welcome screen

Now let's install the packages, which will help us to parse form data:

yarn add -D formidable @types/formidable
Enter fullscreen mode Exit fullscreen mode

Creating the form

We will create small form, where we can upload files. I will simplify the code to make it more clear.

Go to pages/index.js and replace all the default code with the following:

import type { NextPage } from 'next'
import React from "react";

const Home: NextPage = () => {

    const [isLoading, setIsLoading] = React.useState(false);
    const inputFileRef = React.useRef<HTMLInputElement | null>(null);

    const handleOnClick = async (e: React.MouseEvent<HTMLInputElement>) => {

        /* Prevent form from submitting by default */
        e.preventDefault();

        /* If file is not selected, then show alert message */
        if (!inputFileRef.current?.files?.length) {
            alert('Please, select file you want to upload');
            return;
        }

        setIsLoading(true);

        /* Add files to FormData */
        const formData = new FormData();
        Object.values(inputFileRef.current.files).forEach(file => {
            formData.append('file', file);
        })

        /* Send request to our api route */
        const response = await fetch('/api/upload', {
            method: 'POST',
            body: formData
        });

        const body = await response.json() as { status: 'ok' | 'fail', message: string };

        alert(body.message);

        if (body.status === 'ok') {
            inputFileRef.current.value = '';
            // Do some stuff on successfully upload
        } else {
            // Do some stuff on error
        }

        setIsLoading(false);
    };

    return (
        <form>
            <div>
                <input type="file" name="myfile" ref={inputFileRef} multiple />
            </div>
            <div>
                <input type="submit" value="Upload" disabled={isLoading} onClick={handleOnClick} />
                {isLoading && ` Wait, please...`}
            </div>
        </form>
    )
}

export default Home
Enter fullscreen mode Exit fullscreen mode

Here we are using multiple files uploading. If you want to upload only one file, remove multiple from input:file:

<input type="file" name="myfile" ref={inputFileRef} />
Enter fullscreen mode Exit fullscreen mode

API Route

Let’s create an endpoint, where we will process the uploaded files.

Create the filepages/api/upload.ts:

import type { NextApiRequest, NextApiResponse } from 'next'
import { promises as fs } from "fs";
import path from "path";
import formidable, { File } from 'formidable';

/* Don't miss that! */
export const config = {
    api: {
        bodyParser: false,
    }
};

type ProcessedFiles = Array<[string, File]>;

const handler = async (req: NextApiRequest, res: NextApiResponse) => {

    let status = 200,
        resultBody = { status: 'ok', message: 'Files were uploaded successfully' };

    /* Get files using formidable */
    const files = await new Promise<ProcessedFiles | undefined>((resolve, reject) => {
        const form = new formidable.IncomingForm();
        const files: ProcessedFiles = [];
        form.on('file', function (field, file) {
            files.push([field, file]);
        })
        form.on('end', () => resolve(files));
        form.on('error', err => reject(err));
        form.parse(req, () => {
            //
        });
    }).catch(e => {
        console.log(e);
        status = 500;
        resultBody = {
            status: 'fail', message: 'Upload error'
        }
    });

    if (files?.length) {

        /* Create directory for uploads */
        const targetPath = path.join(process.cwd(), `/uploads/`);
        try {
            await fs.access(targetPath);
        } catch (e) {
            await fs.mkdir(targetPath);
        }

        /* Move uploaded files to directory */
        for (const file of files) {
            const tempPath = file[1].filepath;
            await fs.rename(tempPath, targetPath + file[1].originalFilename);
        }
    }

    res.status(status).json(resultBody);
}

export default handler;
Enter fullscreen mode Exit fullscreen mode

Congratulations, we’re done! You can test your form!

All files will be saved in directory /uploads inside your project root.


Use Api route as proxy

Here I suggest you to assume, that we need to transfer our files somewhere else to another server, because we don’t want to store them and doing difficult operations on files.

Add some new packages:

yarn add -D form-data node-fetch
Enter fullscreen mode Exit fullscreen mode

Change you /pages/api/upload.ts to:

import type { NextApiRequest, NextApiResponse } from 'next'
import fs from "fs";
import fetch from "node-fetch";
import FormData from 'form-data';
import formidable, { File } from 'formidable';

export const config = {
    api: {
        bodyParser: false,
    }
};

type ProcessedFiles = Array<[string, File]>;

const handler = async (req: NextApiRequest, res: NextApiResponse) => {

    let status = 200,
        resultBody = { status: 'ok', message: 'Files were uploaded successfully' };

    /* Get files using formidable */
    const files = await new Promise<ProcessedFiles | undefined>((resolve, reject) => {
        const form = new formidable.IncomingForm();
        const files: ProcessedFiles = [];
        form.on('file', function (field, file) {
            files.push([field, file]);
        })
        form.on('end', () => resolve(files));
        form.on('error', err => reject(err));
        form.parse(req, () => {
            //
        });
    }).catch(e => {
        console.log(e);
        status = 500;
        resultBody = {
            status: 'fail', message: 'Upload error'
        }
    });

    if (files?.length) {

        /* Add files to FormData */
        const formData = new FormData();
        for (const file of files) {
            formData.append(file[0], fs.createReadStream(file[1].filepath));
        }

        /* Send request to another server */
        const response = await fetch('PATH_TO_ANOTHER_SERVER', {
            headers: formData.getHeaders(),
            method: 'POST',
            body: formData
        });

        // Do anything you need with response

    }

    res.status(status).json(resultBody);
}

export default handler;
Enter fullscreen mode Exit fullscreen mode

Don’t forget to replace PATH_TO_ANOTHER_SERVER.


This is it, enjoy! Full repo https://github.com/gapon2401/upload-files-nextjs

Top comments (0)