Uploading file in chunks from React to Node-Express
Tags: webdev, javascript, react, node
Published: 2024-01-23 16:12:58

In almost every web application we get some use cases where we need to upload files to server.
One of the easiest way is using input type="file" and then sending to server as a blob.

But sometimes sending files in chunks can be useful, for performance, security and other stuffs like showing upload completion percentage.

In this tutorial we are going to upload a file from React application to Server in node and express.

## Step 1 Setting up React App
I am using [react-formvik](https://www.npmjs.com/package/react-formvik?activeTab=readme) for quick form setup, I will create input type="file".

```
import React from 'react';
import { Form } from 'react-formvik';
const App = () => {
    return <Form
        config={
            {
                fields: [
                    {
                        field: 'avatar',
                        css: {
                            containerClass: 'mt-4',
                            inputClass: 'form-control',
                            labelClass: 'form-label'
                        },
                        inputProps: {
                            type: 'file'
                        },
                        label: 'Upload'
                    }
                ],
                css: {
                    formContainerClass: 'container'
                }
               
            }
        }
        onChange={async ({ avatar }) => {
           //We will add code here in further steps
        }
}

export default App;
```

I have also installed bootstrap to style our input field.
Add below line in index.js or you can import it in css file as well.
```
import 'bootstrap/dist/css/bootstrap.css';
```

**Preview**

![initial react preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkzwrkvfp03le1g1yshh.png)

## Step 2: Setting up server

In express app, we need multer,
`npm i -S multer`

Add following line in your server file
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({ storage });


Make sure you have body parser setup in express app.

**Create one route for upload**

```
app.post('upload', (req, res) => {
    //We will add code here in further steps
});
```

## Step 3: Understanding the approach of File chunking on Client
So, we are going to follow below sub steps
  - we will click browse on UI and upload a file(image)
  - In React component - in onChange method we will get file blob.
  - We will keep one constant for chunk size (say: 1024 bytes) 
  - Knowing the chunkSize and file Size, we can find out how many chunks we need to create.
  - We will create one loop and slice blob based on chunk size and in each iteration we will be sending next chunk to the server
  - That means we are going to call server multiple times for the same route '/upload'.
**For backend(node) approach we will discuss in upcoming steps.**

## Step 4: Implementing chunking in onChange method
Now that we understand how we are going to implement we can start writing code.
First we need to get file details in onChange event

```
 onChange={async ({ avatar }) => {
            const chunkSize = 1024;
            if (avatar) {
                  // we will proceed if we have files here
             }

}
```

Next, lets find out total chunks we need

```
 onChange={async ({ avatar }) => {
            const chunkSize = 1024;
            if (avatar) {
                   const totalChunks = Math.ceil(avatar[0].size/chunkSize);
             }

}
```
Create a loop to iterate for each chunk
```
 onChange={async ({ avatar }) => {
            const chunkSize = 1024* 100;
            if (avatar) {
                   const totalChunks = Math.ceil(avatar[0].size/chunkSize);

           for(let i=0; i<totalChunks; i++) {
                    const start = i*chunkSize;
                    const end = (i + 1) * chunkSize;
                    await sendChunk(start,end);
                }
             }

}
```

Lets create a sendChunk method to send chunk to server.

```
 onChange={async ({ avatar }) => {
            const chunkSize = 1024;
            if (avatar) {
                   const totalChunks = Math.ceil(avatar[0].size/chunkSize);

       
                const sendChunk = async (start,end) =>{
                    const formData = new FormData();
                    const blobSlice = avatar[0].slice(start, end);
                    formData.append('file', blobSlice, avatar[0].name);
                    return await fetch('http://localhost:3000/upload', {
                        method: 'POST',
                        body: formData
                    });
                }
           for(let i=0; i<totalChunks; i++) {
                    const start = i*chunkSize;
                    const end = (i + 1) * chunkSize;
                    await sendChunk(start,end);
                }
             }

}
```

## Step 5: Implementing server

```
app.post('/upload', upload.single('file'), (req, res) => {
    const { buffer, originalname } = req.file;
    const writeStream = fs.createWriteStream(originalname, { flags: 'a' });
    writeStream.write(buffer);
    writeStream.end();

    writeStream.on('finish', () => {
        res.status(200).send('File received successfully.');
    });

    // Event listener for any errors during the write operation
    writeStream.on('error', (err) => {
        console.error(err);
        res.status(500).send('Internal Server Error');
    });

```
Lets understand above code, 
- upload.single('file') - is a middleware for multer which expects a form data with a file name 'file'
- In fs.createWriteStream we have added a flag 'a' which will actually append the buffer at the end of file every time it gets a request.

## Step 6: Testing 

![Uploading an avatar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t51ap7s4zlb0pu9uiw79.png)

**Server console:**

![Server console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oi1torcs56j14ow6kz9d.png)

**Network Requests:**

![Network requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpzdxw90bj24sjjwpfll.png)

**Uploaded file:**

![Uploaded file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eva0sc2j7ubsrparccds.png)

There can be many other approaches, if you know one share with me as well.

Thank you for reading this!