node.jsmean-stackmulter

MEAN project - Multer messes up request body


I am new to MEAN stack and I'm trying to create login and register functionalities where register includes profile photo upload. That's how I stumbled across the multer library.

Overall, after collecting form elements I send them to backend like this:

//login.component.ts login() function

login(): void {

  const form_data = new FormData();
  form_data.append("username", this.username);
  form_data.append("password", this.password);

  this.user_service.login(form_data).subscribe(
    user => {
      if (user != null) {
        localStorage.setItem("logged_user", JSON.stringify(user));
        alert("Success!");
      }
      else {
        alert("Wrong userame/password!");
      }
    }
  )
}

Then, I send the login request with my user_service:

constructor(private http: HttpClient) {}

login(form_data: FormData) {
   return this.http.post<User>("http://localhost:4000/users/login", form_data);
}

On the server side, apart from server.ts which is the entrypoint of my backend app and user.ts where my UserModel is defined, I created three folders:

  1. "Middlewares" with the upload.ts file:
import multer from "multer";

const storage = multer.diskStorage({
    destination: '../frontend/src/assets/profile_photos',
    filename: (req, file, cb) => {
      cb(null, file.originalname);   

    },
});
  
const upload = multer({ storage: storage });

export default upload;
  1. "Controllers" with user.controller.ts file:
import express from "express";

import UserModel from "../models/user"; 

export class UserController {
    
    login = async (req: express.Request, res: express.Response) => {
        
        // some login logic
        
    }
    
    register = async (req: express.Request, res: express.Response) => {
        
        // some register logic
        
    }
    
}
  1. "Routers" with user.router.ts file:
import express from "express";
import { UserController } from "../controllers/user.controller";
import upload from "../middlewares/upload";

const user_router = express.Router();

user_router.route("/login").post(
    (req, res) => new UserController().login(req, res)
)

user_router.route("/register").post(
    // ####
    upload.single('profile_photo_object'),
    (req, res) => new UserController().register(req, res)
)

export default user_router;

Now, the problem is that no matter what I send in request body, when I enter login() function, request.body is empty (I printed req.body.username and other fields from FormData object in console).

When it comes to register() function, req.body contains everything I need and works as expected (even the image file is successfully uploaded) as long as the line following #### comment is there. If I comment that line (upload.single('profile_photo_object')), request.body becomes empty in register() also.

My only conclusion from this is that multer is somehow affecting request body, but I don't know exactly how and why because I'm new to multer and I find the code I put in "Middlewares" on the internet.

Does anyone know what is the problem and how can I fix it in order to keep image upload functionality and also access request body objects?

I tested everything on the frontend, and FormData objects I send to the backend are always initialized the way they are supposed to.


Solution

  • The problem with login route is that when you send form data, the request is sent with multipart/form-data encoding, and you don't use any middleware, i.e. multer on that route, which is why body is empty, and which is why register on the other hand is working.

    So, you have two options: don't use form data on login route, or use form data and add multer middleware to process the request:

    // 1. don't use formdata, use json (also make sure you use json middleware)
    
    login(): void {
    
      this.user_service.login({this.username, this.password}).subscribe(
    
    // server
    app.use(express.json());
    
    // 2. mount multer middleware (use .none() to exclude files)
    
    user_router.route("/login").post(
        upload.none(),
        (req, res) => new UserController().login(req, res)