In this tutorial we'll learn how to upload files from an Angular5 application to a Node.js REST API that runs using Restify. For the file upload functionality we'll use the File Web API, without relying on a third party component.
You can access the code found in this article on GitHub.
In order to get started make sure that you have either the LTS or 'Current' Node.js installed (should be version 8.x or 9.x). Also please make sure that the latest version of the Agular CLI is installed (version 1.6.x or above).
For creating a project we'll use the Angular CLI: ng new file-upload
Once the project is created and the dependencies are installed we'll extend it by adding one more folder to the existing folder structure: cd file-upload/ && mkdir api && mkdir uploads
.
The api
folder will be the location where we'll create the Restify server, and the uploads
folder will be where uploaded files will reside.
Let's also install Restify: npm i --save restify
It's time to create the REST API itself, we'll only add a simple endpoint that will accept HTTP POST requests. We'll create this under the api
folder that we have just created. On top of this we'll also make the API to serve our Angular application.
The reason why we are serving the Angular application from the Restify RESTful API is to avoid CORS requests. If you're not familiar with CORS please read this introductory article.
const fs = require('fs');
const restify = require('restify');
const server = restify.createServer();
server.use(
restify.plugins.bodyParser({
mapParams: false,
})
);
server.get(
'//(.*)?.*/',
restify.plugins.serveStatic({
directory: `${__dirname}/../dist`,
default: './index.html',
maxAge: 0,
})
);
server.post('/upload', (request, response) => {
for (var key in request.files) {
if (request.files.hasOwnProperty(key)) {
fs.renameSync(
request.files[key].path,
`${__dirname}/../uploads/${request.files[key].name}`
);
fs.unlink(request.files[key].path);
}
}
response.send(202, { message: 'File uploaded' });
});
server.listen(3000, () => console.info('Restify server is up on port 3000'));
In the code above we created a Restify server and we also bring in the bodyParser
plugin. This step is important as this allows us to access request.files
when files are being sent from a form.
The server.get
entry will serve the Angular application (essentially the frontend of the application) from the dist
folder. It doesn't do anything else.
server.post
is iterating through the request.files
object and it moves the uploaded file from it's temporary location to the uploads
folder and it also renames the file to it's original name.
Please note that there's no need to remove the temporary file that the application creates with
fs.unlink()
becausefs.renameSync()
not only renames a file but also deletes the old version of it.
The /upload
endpoint also returns a 202 (HTTP Accepted
) header with a message object.
request.files
request.files
is an object that contains information about the file data received from a form. It contains a lot of information - here's a trimmed down verison of it with some of the more interesting keys:
{ fils0:
File {
size: 1030956,
path: '/var/folders/l9/7n1_z0ks29l6g940zk0xjf540000gn/T/upload_8c344945cd2035bb93fbd84204ba8dee',
name: 'owlman-08.jpg',
type: 'image/jpeg',
lastModifiedDate: 2018-01-05T17:33:37.259Z,
} }
We are using some of this information to move our file.
Let's now create the UI for the application. We are not going to create a new component but we'll be reusing the 'root' component (app component).
First of all let's put together the template for the component which should look like this:
<h1>Angular, Restify file upload</h1>
<p>Select a file to upload. Upload will happen automatically.</p>
<form (ngSubmit)="upload()">
<input type="file" id="file" multiple (change)="files($event.target.files)" />
<button type="submit">Upload</button>
</form>
<p *ngIf="message"></p>
<hr />
<footer>
<small
>Tamas Piros -
<a href="http://fullstack-developer.academy"
>http://fullstack-developer.academy</a
></small
>
</footer>
There's nothing extraordinary here - we have a form handler event on submit (upload
) and notice that we have specified our input
element to allow multiple file uploads via the multiple
attribute as well as a change
event handler - this event handler will be responsible for collecting the files that users select.
We now need to specify the two event handler functions in our component.
In the component we'll also use the HttpClient
and FormData
.
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
private filesToUpload = null;
public message = '';
constructor(private http: HttpClient) {}
files(files) {
this.message = '';
this.filesToUpload = files;
}
upload() {
const formData = new FormData();
const files = this.filesToUpload;
for (let i = 0; i < files.length; i++) {
formData.append(`fil${i}`, files.item(i), files.item(i).name);
}
this.http
.post('http://localhost:3000/upload', formData)
.subscribe((response) => (this.message = response['message']));
}
}
The code above should be very straight forward. For the files
event handler we collect all the files that a user selects. Then on the upload
method we create [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
- this allows us to send key/value pairs to our backend. And last but not least we also specify the HTTP POST
method to send the data to the /upload
endpoint that we have created previously.
Remember that in order to use the
HttpClient
and the event handler functions for ourform
we need to import bothHttpClientModule
andFormsModule
in ourapp.module.ts
file!
Now it's time to build the application and we'll use the Angular CLI to achieve this: ng build --prod
Please note that we are building a production version of our application so that we can serve it in an easy way using the Restify
/
endpoint that we have created earlier.
Once the dist
folder is created with our application, we can go ahead and start up the API with node api/app.js
, open a browser and navigate to http://localhost:3000
where we should see our form.
Test the application by selecting one or multiple files, hit the upload
button and see the files in the uploads
folder.