Node.js/Express RestAPIs server – Angular 9 Upload/Download Files

Tutorial: Node.js/Express RestAPIs server – Angular 9 Upload/Download Files – Multer + Bootstrap

In the tutorial, we show how to upload files, download files from Angular 9 Client to Node.js RestAPIs server using Multer middleware.

Related posts:
NodeJS/Express – Upload/Download MultipartFiles/Images – Multer + JQuery Ajax + Bootstrap
Node.js RestAPIs – Angular 9 HttpClient – Get/Post/Put/Delete requests + Bootstrap 4

Technologies

  • Angular 9
  • RxJS 6
  • Nodejs – v8.11.3

Overview

We create 2 projects: {Node.js, Angular}

Node.js RestAPIs

– Node.js project exposes RestAPIs to upload/download files:

  • router.post(‘/api/file/upload’, upload.single(“file”), fileWorker.uploadFile);
  • router.get(‘/api/file/all’, fileWorker.listUrlFiles);
  • router.get(‘/api/file/:filename’, fileWorker.downloadFile);

Configure cross-origin for Angular-Client which running at port: 4200.


const cors = require('cors')
const corsOptions = {
  origin: 'http://localhost:4200',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions))

angular-6-upload-multipart-file-nodejs-restapis-nodejs-project-structure

Angular 9 Client

Project structure:

angular-6-upload-multipart-file-nodejs-restapis-angular-project-structure

  • upload-file.service provides methods: push File to Storage and get Files.
  • list-upload.component gets and displays list of Files.
  • form-upload.component helps upload File.
  • details-upload.component is detail for each item in list of Files.

angular-6-upload-multipart-file-nodejs-restapis-angular-overview

Practice

Node.js RestAPIs

Setting up NodeJS/Express project

Create a folder Node.js-UploadFiles:


mkdir Node.js-UploadFiles
cd Node.js-UploadFiles

Then init NodeJS project:


>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (node.js-uploadfiles)
version: (1.0.0)
description: Node.js RestAPIs to Upload Files
entry point: (index.js) server.js
test command:
git repository:
keywords: Node.js,Express,Multer,Upload-Files
author: grokonez.com
license: (ISC)
About to write to C:\nodejs\Node.js-UploadFiles\package.json:

{
  "name": "node.js-uploadfiles",
  "version": "1.0.0",
  "description": "Node.js RestAPIs to Upload Files",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Node.js",
    "Express",
    "Multer",
    "Upload-Files"
  ],
  "author": "grokonez.com",
  "license": "ISC"
}


Is this ok? (yes) yes

We need express, multer and cors modules.
– Express is one of the most popular web frameworks for NodeJs which is built on top of Node.js http module, and adds support for routing, middleware, view system etc.
– Multer is a node.js middleware for handling multipart/form-data , which is primarily used for uploading files.
– Cors: a mechanism that uses HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin.

-> Install Express, Multer & Cors:


npm install express multer cors --save

-> Check package.json file:


{
  "name": "node.js-uploadfiles",
  "version": "1.0.0",
  "description": "Node.js RestAPIs to Upload Files",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Node.js",
    "Express",
    "Multer",
    "Upload-Files"
  ],
  "author": "grokonez.com",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.4",
    "express": "^4.16.3",
    "multer": "^1.3.1"
  }
}

Config Multer Upload

– Create ./app/config/multer.config.js file:


const multer = require('multer');
 
var storage = multer.diskStorage({
	destination: (req, file, cb) => {
	  cb(null, __basedir + '/uploads/')
	},
	filename: (req, file, cb) => {
	  cb(null, file.originalname)
	}
});
 
var upload = multer({storage: storage});
 
module.exports = upload;

Express Routers

– Create ./app/routers/file.router.js file:


let express = require('express');
let router = express.Router();
let upload = require('../config/multer.config.js');

let fileWorker = require('../controllers/file.controller.js');

router.post('/api/file/upload', upload.single("file"), fileWorker.uploadFile);

router.get('/api/file/all', fileWorker.listUrlFiles);
 
router.get('/api/file/:filename', fileWorker.downloadFile);
 
module.exports = router;

File Controllers

– Create ./app/controllers/file.controller.js file


const uploadFolder = __basedir + '/uploads/';
const fs = require('fs');
 
exports.uploadFile = (req, res) => {
	res.send('File uploaded successfully! -> filename = ' + req.file.filename);
}
 
exports.listUrlFiles = (req, res) => {
	fs.readdir(uploadFolder, (err, files) => {
		for (let i = 0; i < files.length; ++i) {
			files[i] = "http://localhost:8080/api/file/" + files[i];
		}
		
		res.send(files);
	})
}

exports.downloadFile = (req, res) => {
	let filename = req.params.filename;
	res.download(uploadFolder + filename);  
}

Server.js

Implement ./server.js file:


var express = require('express');
var app = express();

const cors = require('cors')
const corsOptions = {
  origin: 'http://localhost:4200',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions));
 
global.__basedir = __dirname;
 
let router = require('./app/routers/file.router.js');
app.use('/', router);

// Create a Server
let server = app.listen(8080, () => {

  let host = server.address().address
  let port = server.address().port

  console.log("App listening at http://%s:%s", host, port); 
})

Angular 9 Client

Generate Service & Components

Run commands below:

  • ng g s upload/UploadFile
  • ng g c upload/FormUpload
  • ng g c upload/ListUpload
  • ng g c upload/DetailsUpload

On each Component selector, delete app- prefix, then change tslint.json rules – "component-selector" to false.

App Module

app.module.ts ->


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
 
import { AppComponent } from './app.component';
import { DetailsUploadComponent } from './upload/details-upload/details-upload.component';
import { FormUploadComponent } from './upload/form-upload/form-upload.component';
import { ListUploadComponent } from './upload/list-upload/list-upload.component';
 
@NgModule({
  declarations: [
    AppComponent,
    DetailsUploadComponent,
    FormUploadComponent,
    ListUploadComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Upload File Service

upload/upload-file.service.ts ->


import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
 
@Injectable({
  providedIn: 'root'
})
export class UploadFileService {
 
  constructor(private http: HttpClient) { }
 
  pushFileToStorage(file: File): Observable> {
    const formdata: FormData = new FormData();
 
    formdata.append('file', file);
 
    const req = new HttpRequest('POST', '/post', formdata, {
      reportProgress: true,
      responseType: 'text'
    });
 
    return this.http.request(req);
  }
 
  getFiles(): Observable {
    return this.http.get('/getallfiles');
  }
}

Component for getting List of Files

upload/list-upload.component.ts ->


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UploadFileService } from '../upload-file.service';
 
@Component({
  selector: 'list-upload',
  templateUrl: './list-upload.component.html',
  styleUrls: ['./list-upload.component.css']
})
export class ListUploadComponent implements OnInit {
 
  showFile = false;
  fileUploads: Observable;
 
  constructor(private uploadService: UploadFileService) { }
 
  ngOnInit() {
  }
 
  showFiles(enable: boolean) {
    this.showFile = enable;
 
    if (enable) {
      this.fileUploads = this.uploadService.getFiles();
    }
  }
}

upload/list-upload.component.html ->

<button class="button btn-info" *ngIf='showFile' (click)='showFiles(false)'>Hide Files</button>
 
<button class="button btn-info" *ngIf='!showFile' (click)='showFiles(true)'>Show Files</button>
 
<div [hidden]="!showFile">
  <div class="panel panel-primary">
    <div class="panel-heading">List of Files</div>
    <div *ngFor="let file of fileUploads | async">
      <div class="panel-body">
        <details-upload [fileUpload]='file'></details-upload>
      </div>
    </div>
  </div>
</div>

upload/details-upload.component.ts ->


import { Component, OnInit, Input } from '@angular/core';
 
@Component({
  selector: 'details-upload',
  templateUrl: './details-upload.component.html',
  styleUrls: ['./details-upload.component.css']
})
export class DetailsUploadComponent implements OnInit {
 
  @Input() fileUpload: string;
 
  constructor() { }
 
  ngOnInit() {
  }
 
}

upload/details-upload.component.html ->

<a href="{{fileUpload}}">{{fileUpload}}</a>

Component for uploading File

upload/form-upload.component.ts


import { Component, OnInit } from '@angular/core';
import { UploadFileService } from '../upload-file.service';
import { HttpResponse, HttpEventType } from '@angular/common/http';
 
@Component({
  selector: 'form-upload',
  templateUrl: './form-upload.component.html',
  styleUrls: ['./form-upload.component.css']
})
export class FormUploadComponent implements OnInit {
 
  selectedFiles: FileList;
  currentFileUpload: File;
  progress: { percentage: number } = { percentage: 0 };
 
  constructor(private uploadService: UploadFileService) { }
 
  ngOnInit() {
  }
 
  selectFile(event) {
    this.selectedFiles = event.target.files;
  }
 
  upload() {
    this.progress.percentage = 0;
 
    this.currentFileUpload = this.selectedFiles.item(0);
    this.uploadService.pushFileToStorage(this.currentFileUpload).subscribe(event => {
      if (event.type === HttpEventType.UploadProgress) {
        this.progress.percentage = Math.round(100 * event.loaded / event.total);
      } else if (event instanceof HttpResponse) {
        console.log('File is completely uploaded!');
      }
    });
 
    this.selectedFiles = undefined;
  }
 
}

upload/form-upload.component.html ->

<div *ngIf="currentFileUpload" class="progress">
  <div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" attr.aria-valuenow="{{progress.percentage}}"
    aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width:progress.percentage+'%'}">
    {{progress.percentage}}%</div>
</div>
 
<label class="btn btn-default">
  <input type="file" (change)="selectFile($event)">
</label>
 
<button class="btn btn-success" [disabled]="!selectedFiles" (click)="upload()">Upload</button>

App Component

app.component.ts ->


import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'JavaSampleApproach';
  description = 'Angular-SpringBoot Demo';
}

app.component.html ->

<div class="container" style="width:400px">
  <div style="color: blue; margin-bottom: 20px">
    <h1>{{title}}</h1>
    <h3>{{description}}</h3>
  </div>
 
  <form-upload></form-upload>
 
  <br/>
  <br/>
 
  <list-upload></list-upload>
</div>

Check Results

Run Node.js Server (using npm start) and Angular 9 Client App (using ng serve)
Then open Browser with url http://localhost:4200/.

-> Upload files and show list of Files:

angular-6-upload-multipart-file-nodejs-restapis-upload-files

-> Inside ‘Node.js-UploadFiles’ project folder, open ‘uploads’ folder:

angular-6-upload-multipart-file-nodejs-restapis-upload-files-result

-> Click to the links to download files:

angular-6-upload-multipart-file-nodejs-restapis-nodejs-download-files

Sourcecode



By grokonez | April 1, 2021.


Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*