File upload with Angular and Restify (Tutorial)

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

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.

Getting started

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).

Create a project and setting up dependencies

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

Create the REST API

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() because fs.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.

Create the Angular application

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 our form we need to import both HttpClientModule and FormsModule in our app.module.ts file!

Build the application

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.