Upload Single File In Node.js Using Express and Multer in 6 Steps
Tags: node, webdev, javascript, mongodb
Published: 2021-04-15 07:12:32

Every one of us takes different approaches while coding and building our folder and file structure for the projects. Here is the approach that I took for my first #Node application.

The front-end is build with #react-app. 

##Installation
```
// run this for yarn
yarn add express cors multer

// or using npm
npm install express cors multer --save
```

####Note:
• express: We will develop our API using ExpressJs
• cors: A node.js package that provides an Express/Connect middleware to enable Cross Origin Resource Sharing (CORS)
• multer: Node.js middleware for handling multipart/form-data

##Project structure
This project is a nodejs application using express framwork. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ef7bykoq67bnj1suhx0e.png)
* The express application inside the index.js file has one API end-point call. 
* Routes.js includes an API end-Point call for uploading file and updating the user collection in the database. 
* UserModel.js is a mongodB model. 

#### 1. Index.js File
```ruby
import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';

import Routes from './routes.js';

const app = express();
dotenv.config();

app.use(express.static('./public'));
app.use('/uploads', express.static('uploads'));

app.use(bodyParser.json({ limit: '30mb', extended: true }))
app.use(bodyParser.urlencoded({ limit: '30mb', extended: true }))

app.use(cors());

app.use(‘/myapi’,Routes);

const PORT = process.env.PORT|| 5000;

mongoose.connect(process.env.CONNECTION_URL, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => app.listen(PORT, () => console.log(`Server Running on Port: http://localhost:${PORT}`)))
  .catch((error) => console.log(`${error} did not connect`));

mongoose.set('useFindAndModify', false);
```

#####Important notes:
> *app.use('/uploads', express.static('uploads'));*

This middleware sets "upload" folder as static where the file will be uploaded in. Make sure to create a folder called upload in your application. This name can change to whatever you like to change.

> *app.use('/my_api', Routes);*

This line of code is for API end-point call for the main route that is called *my_api*. 

#### 2. Route.js File
``` ruby
import express from 'express';
import { updateAnUserImage } from './userController.js';
import upload from './upload.js'

const router = express.Router();
router.patch('/user/:_id', upload, updateAnUserImage);

export default router;

```

#####Important notes:
> *router.patch('/user/:_id', upload, updateAnUserImage);*


The user route will be added after my_api which was the main route in the index.js file. This route should look like this: *my_api/user/id*.

The upload middleware allows the user to upload a file at this API end-point call. The *"updateAnUserImage"* is the controller that takes the path and the linked of the file and insert it to the database.

#### 3. upload.js file (middleware) 
``` ruby
import multer from 'multer';

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, './uploads');
    },
    filename: function (req, file, cb) {
        cb(null, Date.now() + "--" + file.originalname);
    }
});  

const fileFilter = (req, file, cb) => {
    if((file.mimetype).includes('jpeg') || (file.mimetype).includes('png') || (file.mimetype).includes('jpg')){
        cb(null, true);
    } else{
        cb(null, false);

    }

};

let upload = multer({ storage: storage, fileFilter: fileFilter,});

export default upload.single('ProfilePicture')

```

#####Important notes:
  This is the place where Multer is used to define the file destination, file name, filteration and the logic for uploading the file.

The constant **"storage"** defines where the file will be uploaded and what will be the file name. 

The **fileFilter** is function declaration that takes three parameters; "request, file and a call back". The logic for the filtration is as below:

*if the file type is JPEG,JPG or PPNG then trigger the cb. The cb function takes two arguments first null and the second one is a Boolean. True means to allow save the file to the storage and false means to reject the file saving process to the storage.*

#### 4. userController.js (The controller file)

``` ruby
import User from './userModel.js';

export const updateAnUserImage = async (req, res) => {
    const id = req.params._id;

    if (!mongoose.Types.ObjectId.isValid(id)) return res.status(404).send(`No post with id: ${id}`);
   
    const path = req.file.path.replace(/\\/g, "/")

    await User.findByIdAndUpdate(id, req.body = {ProfilePicture: "http://localhost:5000/" + path}, { new: true });
    res.json(updateAnUser);
}

```

#####Important notes:
 The controller exports updateAnUserImage. updateAnUserImage is an asynchronous function that takes two parameters: request and response. This function checks if there is an _id in the params in request body. if yes then replace all the front slashes to the back slashes in the path string and then update the value of the ProfilePicture to the constant path. In simple words, it means to update the file link in the database.

#### 5. UserModel.js file

``` ruby
import mongoose from 'mongoose';

const userSchema = mongoose.Schema({  
    "firstName": { type: String, required: true},
    "email": { type: String, required: true, unique: true},
    "ProfilePicture": { type: String},
})

var User = mongoose.model('Users', userSchema);

export default User;

```

#### 6. Front-End
``` ruby
import React, { useState } from "react";
import { Button, Form } from "react-form-elements";


function EditProfile() {
  const [fileData, setFileData] = useState("");

  const fileChangeHandler = (e) => {
    setFileData(e.target.files[0]);
  };

  const onSubmitHandler = () => {
    if (
      (fileData && fileData.type === "image/png") ||
      fileData.type === "image/jpeg" ||
      fileData.type === "image/jpg"
    ) {
     
      const data = new FormData();
      data.append("ProfilePicture", fileData);

      fetch(
        `http://localhost:5000/my_api/user/${localStorage.getItem(
          "userID"
        )}`,
        {
          method: "PATCH",
          body: data,
        }
      )
        .then((result) => {
          console.log("File Sent Successful");
        })
        .catch((err) => {
          console.log(err.message);
        });
    }
  };
  

  return (
    <div>
      <Form onSubmit={ onSubmitHandler } name="edit profile form">
        <input type="file" onChange={fileChangeHandler} />
        <Button type="submit" className="profile-order-button">
          Save Changes
        </Button>
       </Form>
    </div>
  );
}

export default EditProfile;


```

#####Important notes:
> *data.append("ProfilePicture", fileData);*

Make sure to spell the first argument in the append which is in this case **"ProfilePicture"** same as you spelled it in the last line of the upload.js file.