Skip to main content

File upload with Angular and Restify (Tutorial)

4 min read

Older Article

This article was published 8 years ago. Some information may be outdated or no longer applicable.

We’ll upload files from an Angular 5 application to a Node.js REST API running on Restify. For the upload itself, we’ll use the File Web API. No third-party components.

You can access the code found in this article on GitHub.

Getting started

Make sure you’ve got either the LTS or ‘Current’ Node.js installed (version 8.x or 9.x). You’ll also need the latest Angular CLI (version 1.6.x or above).

Create a project and setting up dependencies

We’ll use the Angular CLI to scaffold the project: ng new file-upload

Once the project is created and dependencies are installed, we’ll add two more folders: cd file-upload/ && mkdir api && mkdir uploads.

The api folder is where we’ll create the Restify server. The uploads folder is where uploaded files will land.

Install Restify: npm i --save restify

Create the REST API

Time to build the REST API. We’ll add a single endpoint that accepts HTTP POST requests, and we’ll also make the API serve our Angular application.

We’re serving the Angular application from the Restify API to avoid CORS requests. If you’re not familiar with CORS, 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'));

We create a Restify server and bring in the bodyParser plugin. That’s the bit that gives us access to request.files when files arrive from a form.

The server.get entry serves the Angular application (the frontend) from the dist folder. That’s all it does.

server.post iterates through the request.files object, moves each uploaded file from its temporary location to the uploads folder, and renames it to its original name.

There’s no need to remove the temporary file with fs.unlink() because fs.renameSync() both renames the file and deletes the old version.

The /upload endpoint returns a 202 (HTTP Accepted) header with a message object.

request.files

request.files is an object containing information about the file data received from a form. Here’s a trimmed-down version showing 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 use some of this information to move the file.

Create the Angular application

Now for the UI. We won’t create a new component. We’ll reuse the root component (app component).

First, the template:

<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">{{ message | json }}</p>
<hr />
<footer>
  <small
    >Tamas Piros -
    <a href="http://fullstack-developer.academy"
      >http://fullstack-developer.academy</a
    ></small
  >
</footer>

Nothing unusual here. We’ve got a form handler on submit (upload) and an input element that allows multiple file uploads via the multiple attribute. The change event handler collects the files users select.

Now the two event handler functions in the component. We’ll also use 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 files event handler collects all the files a user selects. The upload method creates [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData), which lets us send key/value pairs to the backend. Then we fire the HTTP POST to the /upload endpoint we created earlier.

Remember: to use HttpClient and the form event handlers, you need to import both HttpClientModule and FormsModule in your app.module.ts file.

Build the application

Build the application with the Angular CLI: ng build --prod

We’re building a production version so we can serve it from the Restify / endpoint we created earlier.

Once the dist folder is created, start up the API with node api/app.js, open a browser, and navigate to http://localhost:3000. You should see the form.

Test it by selecting one or multiple files, hitting the upload button, and checking the uploads folder.