Search code examples
node.jsangulartypescriptexpressmulter

req.file is undefined in multer image upload -- NodeJS, Angular


I am trying to upload an image for a blog posts using Multer. I am using mongodb and NodeJS for the backend and Angular for the front end. Whenever I perform the POST request and check in the console, the req.file is always undefined. I have tried using Multer's upload.any() with req.files and req.files[0].filename but to no avail. I have no clue why it stays undefined. Here's my Multer Code:

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
      cb(null, 'public');
    },
    filename: (req, file, cb) => {
      console.log(file);
      var filetype = '';
      if(file.mimetype === 'image/gif') {
        filetype = 'gif';
      }
      if(file.mimetype === 'image/png') {
        filetype = 'png';
      }
      if(file.mimetype === 'image/jpeg') {
        filetype = 'jpg';
      }
      cb(null, 'file-' + Date.now() + '.' + filetype);
    }
});

const upload = multer({storage: storage, limits: { fieldSize: 10 * 1024 * 1024 } });

Here's the Server POST request:

router.post('/newPost', passport.authenticate('jwt', { session: false}), upload.single('image'), function(req, res, next) {
  console.log(req.file); 
  let newPost = new Post({
    postTitle: req.body.postTitle,
    postAuthor: req.body.postAuthor,
    postImgUrl: 'http://localhost:3000/public/' + req.file.filename,
    postContent: req.body.postContent
  });

  Post.create(newPost, (err, user) => {
    if(err) {
      res.json({success: false, msg: 'Post failed to submit'});
    } else {
      res.json({success: true, msg: 'Successfully Posted'});
    }
  });
});

This is my Angular Service for the POST request:

addPost(post): Observable<post> {
    this.loadToken();
    const head = this.headers.append("Authorization", this.authToken);
    return this.http.post<post>(this.baseUri + "/newPost", post, { headers: head });
  }

This is my TypeScript Component code:

export class BlogAddComponent implements OnInit {

    postTitle: '';
    postAuthor: '';
    postContent: '';
    postImgUrl: string;
    post: any[] = [];
    postForm: FormGroup;
    public editor = Editor;
    config;

  constructor(private postService: PostService,
    private formBuilder: FormBuilder,
    private router: Router,
    private flashMessage: FlashMessagesService) {
  }

  onFileSelect(event: Event) {
    const file = (event.target as HTMLInputElement).files[0];
    this.postForm.patchValue({ image: file });
    const allowedMimeTypes = ["image/png", "image/jpeg", "image/jpg"];
    if (file && allowedMimeTypes.includes(file.type)) {
      const reader = new FileReader();
      reader.onload = () => {
        this.postImgUrl = reader.result as string;
      };
      reader.readAsDataURL(file);
    }
  }

  ngOnInit(): void {

    this.postForm = new FormGroup({
      postTitle: new FormControl(null),
      postAuthor: new FormControl(null),
      postContent: new FormControl(null),
      postImgUrl: new FormControl(null)
    });
}

 onBlogSubmit() {
    this.postService.addPost(this.postForm.value).subscribe(
        data => {     
            this.flashMessage.show('Blog Submitted Successfully', {cssClass: 'alert-success', timeout: 3000});
            this.router.navigate(['/blog-page']);
        },
        error => {
            this.flashMessage.show('Something Went Wrong', {cssClass: 'alert-danger', timeout: 3000});
        }
    );
  }
}

And this is my Component HTML:

<body>
  <div [formGroup]="postForm" class="container" *ngIf="post">
        <h1 class="mb-4">New blog post</h1>

        <form [formGroup]="postForm" (ngsubmit)="onBlogSubmit()" enctype="multipart/form-data">
            <div class="form-group">

                <label for="postImgUrl">Image</label>
                <input (change)="onFileSelect($event)" type="file" class="form-control" name="image" required>
            </div>

            <div class="form-group">

                <label for="title">Title</label>
                <input formControlName="postTitle" type="text" class="form-control" placeholder="Title" name="postTitle" required>
            </div>

            <div class="form-group">

                <label for="author">Author</label>
                <input formControlName="postAuthor" type="text" class="form-control" placeholder="Author" name="postAuthor" required>
            </div>
            <br>
            <div class="form-group">
              <ckeditor formControlName="postContent" name="postContent" [editor]="editor" [config]="config"></ckeditor>
            </div>
            <br>
            <div class="form-group">
                <a routerLink="/blog-page" class="btn btn-warning">Cancel</a>
                <button type="submit" class="btn btn-primary" (click)="onBlogSubmit()">Save</button>
            </div>
        </form>
    </div>
  </body>

I am really stuck with this. Any help, pointers or guidance are greatly appreiciated. Thank You very much.


Solution

  • You should send formData with the name image as you configured on backend:

    addPost(post): Observable<post> {
        this.loadToken();
        const head = this.headers.append("Authorization", this.authToken);
        const formData: FormData = new FormData();
        formData.append('image', post.postImgUrl);
        return this.http.post<post>(this.baseUri + "/newPost", formData, { headers: head });
    }