Create a File Storage Mobile App with NativeScript 5
Tags: nativescript, javascript, node, express
Published: 2019-03-04 17:34:24

---
title: Create a File Storage Mobile App with NativeScript 5
published: true
description: In this tutorial, you'll learn how to quickly create a cross-platform file storage mobile app for Android and iOS using a single NativeScript 5 code base.
tags: NativeScript, JavaScript, Node, Express
canonical_url: https://blog.jscrambler.com/create-a-file-storage-mobile-app-with-nativescript-5/
cover_image: https://blog.jscrambler.com/content/images/2019/01/jscrambler-blog-create-a-file-storage-mobile-app-nativescript-5.jpg
---

In this article, let's create a small demo project with NativeScript 5. To start with, let’s set up a scenario for the demo.

SimpleFileTransfer is a virtual file locker. Users can sign up for the service and get 100 MB of free virtual storage space. Users can then download and upload files on a server. Users will be able to increase their storage space by filling a form.

Let’s jot down the App functionalities before moving ahead:

-    Signup: User can signup for the app.
-    Login: Authenticates user.
-    Details Page: Provides user details like current quota and total space. Also, we can display a list of files.
-    Download file: Download file from the server to a device.
-    Upload file: Upload files from a device to the server.
-    Increase Quota: Increases storage quota of a user by a specified amount.

You can find the whole code on [GitHub](https://github.com/JscramblerBlog/SimpleFileTransfer).

## Structuring the Backend

The backend must provide the functionalities of managing routes, providing basic authentication and storage, and facilitating file transfers.

Based on the requirements above, we will be using the following stack:

- Node: Server
- [Express](https://expressjs.com):  Middleware
- [Sequelize](http://docs.sequelizejs.com/): ORM Middleware
- SQLite: Database

We will also be using libraries like [multer](https://github.com/expressjs/multer) and [bcrypt](https://github.com/kelektiv/node.bcrypt.js/) for specific functionalities that will be explained later on.


## Initializing the Backend Project

We will be using express-generator to set up the project. Install `express-generator` globally using:

```bash
npm install express-generator -g
```

Start a new project using the command:

```bash
express file-server
```

Navigate to the `file-server` directory and install the dependencies using `npm install`. Also, install the following dependencies:

```bash
npm install multer async sequelize sqlite3 body-parser bcrypt --save
```

Additionally, we will be creating some extra folders for:

- Database: Storing SQLite DB & DB script.
- Model: Storing Models.
- Upload: Temporarily storing uploaded files.
- Storage: storing file for specific users.


### Starting with Sequelize

[Sequelize](http://docs.sequelizejs.com/) is an ORM middleware for SQLite, MySQL, PostgreSQL and MSSQL. For small projects, it’s convenient to use the Sequelize + SQLite combo.

In our current scenario, we require only one Model. We will define our model user as follows:

```javascript
   const User = sequelize.define('user', {
   uid: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
       username:  { type: Sequelize.STRING, unique: true },
       password: Sequelize.STRING,
       quota: {type: Sequelize.INTEGER, defaultValue: 104857600},
       createdAt: Sequelize.DATE,
       updatedAt: Sequelize.DATE,
     })
```

We can use Sequelize's  `Model.sync` to initialize Models Table in a database. To initialize users table, we will be using the code below.

```javascript
     User.sync({force: true}).then(() => {
        // Table created
      });
```

We will store the user model in the `user.js` file in the model folder.

### Signup & Login

This part is pretty straightforward. For signup, the server accepts a username and password and stores it in the database. We will be using the bcrypt library to salt the passwords. As shown below, we are salting the password 10 times before storing it in the database. We are using Sequelize's `Model.create` to store the value. Once a user is created, we will create a directory on our server for his uploads.

The code is as below:
```javascript
     router.post('/', function(req, res, next) {
       console.log(req);
       bcrypt.hash(req.body.password, 10, function(err, hash) {
         User
         .create({ username: req.body.username, password: hash })
         .then(user => {    
         if (!fs.existsSync('storage/'+user.get('uid'))){
         fs.mkdirSync('storage/'+user.get('uid'));
         } 
           res.send({status: true, msg: 'User created', uid: user.get('uid')});
         }).catch(err => {
           res.send({status: false, msg: err });
         })
       });
     });
```

For login, the server accepts a username and password and validates it against the database. We are using `Model.findAll` to get the database record. We use `bcrypt.compare` to compare passwords.

```javascript
   router.post('/', function(req, res, next) {
     console.log(req);
     User.findAll({
       attributes: ["username", "password"],
       where: {
         username: req.body.username
       }
     }).then(dbQ => {    
         if(dbQ.length > 0) {
           bcrypt.compare(req.body.password, dbQ[0].dataValues.password, function(err, result) {
             if (result == true){
               res.send({status: true, msg: 'Login Success'});
             } else {
               res.send({status: false, msg: 'Incorrect Password'});
             }            
         });
       } else {
         res.send({status: false, msg: 'User not found'});
       }         
     });
   });
```



### Defining the Users Route
An authenticated user is allowed to perform the following functions:

- Upload file
- Download file
- Get Details
- Increase quota

Let's define the routes for those functions:

- Upload: `POST /users/:id/upload`
- Download: `GET /users/:id/download/:filename`
- Details: `GET /users/:id/details`
- Increase Quota: `POST /users/:id/increasequota`

### Uploading a File to the Server

We will be using [multer](https://www.npmjs.com/package/multer) to handle uploads.

The multer library is useful to handle multi-part form data. Initially, we will be uploading the file to the uploads folder. Then, the file will be moved to `/storage/uid` folder where uid is user id.

```javascript
   var storage = multer.diskStorage({
     destination: function (req, file, cb) {
       cb(null, 'uploads/')
     },
     filename: function (req, file, cb) {
       cb(null, file.originalname )
     }
   });
  
   router.post('/:id/upload', upload.single('fileparam'), function(req, res, next) {
     if (!req.file) {
       console.log("No file received");
       return res.send({
         success: false,
         msg: "Error Uploading files"
       });
     } else {
       console.log('file received');
       fs.rename('uploads/'+ req.file.originalname, 'storage/'+req.params.id+'/'+req.file.originalname, function (err) {
           if (err) {
                console.log(err);
               return;
           }  
           return res.send({
             success: true,
             msg: "File Uploaded"
           })   
       });   
     }
   });
```

The `upload.single` method is used for handling uploads. This route is expecting a file with name `fileparam` in the URL call. This is quickly done by adding a name attribute an HTML form. We will need the name attribute app side.

### Download Route

ExpressJS provides us with a function to set the download route, conveniently called download.

This is the logic we are following:

- A user logs into the app.
- He selects a file and initiates the download.
- The server receives a request with userid and filename.
- The server sends the file back to the user.

The code for the route is below

```javascript
    router.get('/:id/download/:filename', function(req, res, next) {
         const file = 'storage/'+req.params.id + '/' + req.params.filename;
         res.download(file);
    });
```

### Increase User Quota Route

We will be invoking `Model.update` to adjust the quota. By default, we have 104857600 bytes — which is equivalent to 100 MB — assigned to each user. You can find the query below.

```javascript
   router.post('/:id/increasequota', function(req, res, next) {
     User.update({
       quota: req.body.quota,
     }, {
       where: {
         uid: req.params.id        
       }
     }).then(response => {
       res.send({status: true, msg: "Quota Increased"});
     }).catch(err => {
       res.send({status: false, msg: err});
     }); 
   });
```

### User Details Route

This is a route which we will be using for fetching multiple data, such as:

- Storage Limit of user: from the DB,
- Current File Space occupied: from the `/storage/userid` directory,
- Space Remaining: it's just Point 1 - Point 2,
- File List: list of Files,

We can fetch the storage limit of a user using `Model.findAll`. For fetching file names and storage space, we are using `fs .readdir`, `fs.stat` and `async`.

```javascript
   function getStorageSpace(relpath) {
     let space = 0;
     let fileNames = [];
     let promise = new Promise(function (resolve, reject) {
       fs.readdir(relpath, function (err, items) {
         if (err){
           reject(err);
         }
         fileNames = items;
         let filesArr = items.map(function (val) {
           return relpath + '/' + val;
         });
         async.map(filesArr, fs.stat, function (err, results) {
          
           for (let i = 0; i < results.length; i++) {
             if (err) {
               reject(err);
             }
             space = space + results[i].size;
           }
           resolve({fileNames: fileNames, space: space });
         });
       });
     });
     return promise;
   }
  
   function getStorageLimit(uid){
     let promise = new Promise(function (resolve, reject) {
       User.findAll({
         attributes: ["quota"],
         where: {
           uid: uid
         }
       }).then(dbQ => {
       
         if(dbQ.length < 1) {
           reject("Quota Not Found")
         } else {
           resolve(dbQ[0].dataValues.quota);
         }     
       }).catch(err => {
         reject(err);
       });
     });
     return promise; 
   }

   router.get('/:id/details', function(req, res, next) {
     let it;
     let relpath = 'storage/'+req.params.id;
     Promise.all([getStorageSpace(relpath), getStorageLimit(req.params.id)]).then(result => {
      
       res.send({storageLimit: result[1], occupiedSpace: result[0].space, fileNames: result[0].fileNames, remainingSpace: result[1]- result[0].space});
     })
   });
```

**N.B.:** The code works on assumption that user is not allowed to create a subdirectory in his folder.

The code for enforcing storage limit will be discussed later in the article.

## NativeScript App

For the app side, we will be taking an alternative approach.  A demo project based on Angular-Blank template will be shared with users. A significant part of this article will cover details about plugins concerning the plugin functionalities.

### Consuming Web Services

We are consuming data from simple web services for Login / Signup / User Details Page.

As mentioned in the [previous article](https://blog.jscrambler.com/introduction-to-nativescript/), we can access these webservices using the [HttpClient Module](https://docs.nativescript.org/angular/ng-framework-modules/http). The basic steps are as follows:

- Import `NativeScriptHttpClientModule` in the PageModule.
- Import `HttpClient` and `HttpHeaders` in Component or Provider.
- Consume the URL as you will in an Angular application.
- We will set `Content-Type` header to `application/json`.

For the JavaScript/TypeScript templates, we can use the NativeScript Core [http](https://docs.nativescript.org/angular/ng-framework-modules/http#http) module. The `http. getJson` function provides the required framework to consume webservices. Alternatively, we can also use the [fetch](https://docs.nativescript.org/ns-framework-modules/fetch) module.

As a response from the server, we will be receiving the `uid` of a user. After authentication, we need to store the `uid` so we can allow a mobile user to access `/users/uid route`.

### Storing Data

The NativeScript framework doesn't have any method to store data persistently. We can add that functionality using plugins. We are going to look at two of these plugins.

- [nativescript-sqlite](https://www.npmjs.com/package/nativescript-sqlite): This plugin provides an interface for the SQLite library. This works well if your app needs to store a large volume of records. Install it with:

```bash
tns plugin add nativescript-sqlite
```
- [nativescipt-localstorage](https://www.npmjs.com/package/nativescript-localstorage): This plugins provides a key value API for string data, similar to `window.localstorage`. This works well if your app doesn't have lot of records. Install it with:

```bash
tns plugin add nativescript-localstorage
```

The demo app will be using `nativescript-localstorage`.

<a href="https://blog.jscrambler.com/protecting-your-nativescript-source-code-with-jscrambler/" target="_blank" onClick="ga('send', 'event', 'Blog_Ad', 'Click', 'Protect_NativeScript');"><img alt="Protect your NativeScript App with Jscrambler" src="https://media.jscrambler.com/blog/nativescript-ad.jpg"></a>

### Uploading Files from a Device to a Server

Let's break this functionality into subtasks:

1. Choose Files from the device.
2. Get File Path.
3. Upload File over uploads WebService.

To choose a file and get a file path, we will be using the `nativescript-mediapicker` plugin.  The plugin has multiple modes and we can customize it for specific use cases. You can check the plugin documentation [here](https://github.com/jibon57/nativescript-mediafilepicker).

To select a file, first, we need to define extensions. This is different for both OSes.

For Android devices, we have to use file extensions based on mime types like `let extensions = ["xlsx", "xls", "doc", "docx", "ppt", "pptx", "pdf", "txt", "png"]`
For iOS devices, we have to define extensions from list for Unified Type identifiers: `let extensions = [kUTTypePDF, kUTTypeText];`

You can read more about UTIs [here](https://developer.apple.com/documentation/mobilecoreservices/uttype) and [here](https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html).

The code for invoking `filepicker` is as below:

```javascript
   let options: FilePickerOptions = {
       android: {
           extensions: extensions,
           maxNumberFiles: 1
       },
       ios: {
           extensions: extensions,
           multipleSelection: false
       }
   };
  
   let mediafilepicker = new Mediafilepicker();
   mediafilepicker.openFilePicker(options);
  
   `mediafilepicker.on("getFiles", function (res) {
       let results = res.object.get('results');
       console.dir(results);
   });
  
   mediafilepicker.on("error", function (res) {
       let msg = res.object.get('msg');
       console.log(msg);
   });
  
   mediafilepicker.on("cancel", function (res) {
       let msg = res.object.get('msg');
       console.log(msg);
   });`
```

As above, we will receive the filepath of a file in the `getFiles` event.

We will send the file to the server using the `nativescript-background-http` plugin. You can read about the plugin [here](https://github.com/NativeScript/nativescript-background-http).

Earlier, we defined the `/users/:id/upload` route. As mentioned earlier, our server is expecting the file in the attribute named `fileparam`.

The background `http` provides us with two functions: `uploadFile` and `multipartUpload`. Since we need to set the name attribute, we will be using the `multiPartUpload` function.

```javascript
    let session = bgHttp.session("image-upload");
    let request: bgHttp.Request = {
        url: Config.apiUrl  + '/users/' + localStorage.getItem('uid') + '/upload'   ,
        method: "POST",
        headers: {
            "Content-Type": "multipart/form-data"
        },
        description: 'FileName'
    };
    let params = [{
        name: 'file',
        filename: path
    }];
    let task: bgHttp.Task = session.multipartUpload(params, request);
    task.on("error", (e) => {
        reject(e);
    });
    task.on("complete", (e) => {
        resolve(e);
    }); 

  
```

###  Downloading a File to the Device

We will be using the core [file-system](https://docs.nativescript.org/ns-framework-modules/file-system), [platform](https://docs.nativescript.org/ns-framework-modules/platform) and [utils](https://docs.nativescript.org/core-concepts/utils) modules to achieve the result. Both Android and iOS handle downloads differently.  We will be using `isAndroid` and `isIOS` variables from platform module to segregate the code.

The file-system module provides us with a `knownFolders` sub-module. Three predefined folders for both Android and iOS are available:

- `knownFolders.currentApp()`
- `knownFolders.documents()`
- `knownFolders.temp()`


Additionally, an [iOS sub-module](https://docs.nativescript.org/api-reference/modules/_file_system_.knownfolders.ios) provides us with some other predefined folders. E.g:

- `knownFolders.ios.download`
- `knownFolders.ios.sharedPublic`

#### iOS Code

On an iOS scenario, this is straightforward:

- Show a list of server files.
- Download the files to the documents folder.
- List downloaded files in a separate view
- Use the `utils.openFile` function to open the file.


To download the files, we will be using the [http](https://docs.nativescript.org/ns-framework-modules/http) module of the NativeScript framework. The `getFile` function can be used to fetch files from the server and save them to a specific file location. The snippet for iOS is below:

```javascript
      let filePath: string = path.join(knownFolders.documents().path, fileName);
           getFile(download_url + fileName, filePath).then((resultFile) => {
                   // The returned result will be File object
   }, (e) => {
       console.log(e);
```

Once the file has been downloaded, we can use the `openFile` function from the `utils` module to open a file on iOS.

#### Android Code

The Android side of coding is a bit trickier. The locations of the `knownFolders` module are as below.

- currentFolder:  `/data/data/:appid/files/app`
- documents: `/data/user/:androiduser/:appid/files`
- temp:  `/data/user/:androiduser/:appid/cache`

As you can see, all the folders are located in `/data`. `/data` is inaccessible to normal users. Furthermore, external apps won't be able to access the files in those folders. Also, there is no `openFile` function for Android.

As of now, the best we can do is:

- Show a list of server files.
- Download a file to a user accessible location.
- List the files present in the location.

To implement the functionality, we will be using a bit of native code.
Before moving ahead, we will have to install `tns-platform-declarations` with:

```bash
npm i tns-platform-declarations --save
```

Create a `reference.d.ts` file in the root folder and add the following lines:

```javascript
`/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />`
`/// <reference path="./node_modules/tns-platform-declarations/android.d.ts" />`
```

You can check the [readme](https://github.com/NativeScript/NativeScript/tree/master/tns-platform-declarations) for more details.

[Android OS](https://developer.android.com/reference/android/os/Environment) provides us with a function to access the external storage.

We will be using the constant `DIRECTORY_DOWNLOADS` and the function `getExternalStoragePublicDirectory` to create a publicly accessible download location.

We will append a path “SimpleFileTransfer” to create a custom `folderPath` and `filePath`.

```javascript
   const androidDownloadsPath = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).toString();
   const androidFolderPath = fs.path.join(androidDownloadsPath, "SimpleFileTransfer");
   const filePath: string = fs.path.join(androidFolderPath, fileName);
   getFile(download_url + fileName, filePath).then((resultFile) => {
                   // The returned result will be File object
   }, (e) => {
       console.log(e);
```

If you check your file explorer, a new directory will be created in the Downloads folder called SimpleFileTransfer. You will find all the files downloaded there.

### Listing Downloaded Files

We will be using the `file-system` module. The Folder class of the `file-system` module has a [getEntities](https://docs.nativescript.org/ns-framework-modules/file-system#read) function which allows us to list files in a folder. As with `fs.readdir` in Node.js, we can only list the files.

For iOS, the path is

```javascript
const  folderPath:  string  =  fs.knownFolders.documents().path;
```

For Android, the path is
```javascript
const androidDownloadsPath  =  android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).toString();

`const  folderPath=  fs.path.join(androidDownloadsPath, "SimpleFileTransfer");`
```

To access the Folder functions, we define a folder using

```javascript
let  internalFolder  =  fs.Folder.fromPath(folderPath);
```

Then, we use `getEntities` to get a list of files:

```javascript
   internalFolder.getEntities()
               .then((entities) => {
                   // entities is array with the document's files and folders.
                  
                   entities.forEach((entity) => {
                   let  fileSize  =  fs.File.fromPath(entity.path).size;
                       this.listArray.push({
                           name: entity.name,
                           path: entity.path,
                           lastModified: entity.lastModified.toString(),
                           size : fileSize
                       });
                   });                  
               }).catch((err) => {
                   // Failed to obtain folder's contents.
                   console.log(err.stack);
               });
```

Additionally, we have used the size property of File class to get file size.

### Enforcing Storage Limit

The storage limit can be enforced in two ways:

- Upload file to the server --> Checking remaining space --> Reject the upload on the server side.
- Check remaining space using webservice -->  Check file size --> Cancel the upload on the app side.

To enforce the former, we can modify the upload route as below:

```javascript
   Promise.all([getStorageSpace(relpath), getStorageLimit(req.params.id)]).then(result => {
     if (result[1] - result[0].space > req.file.size){
       fs.rename('uploads/'+ req.file.originalname, 'storage/'+req.params.id+'/'+req.file.originalname, function (err) {
         if (err) {
           return res.send({
             success: false,
             msg: "Error Uploading files"
           });
         }  
         return res.send({
           success: true,
           msg: "File Uploaded"
         })   
     });
     } else {
       return res.send({
         success: false,
         msg: "Storage Limit Exceeded"
       });
     } 
     })
```

To enforce the latter, we get the file size of the file selected by the `mediafilepicker` plugin and check it against the space remaining using the details webservice.

```javascript
`let  fileSize  =  fs.File.fromPath(results[0].file).size;`

    if(fileSize < remainingSpace){
    // Send To server
   }`else {
   // alert user about lack of space
   }
```

## Closing Thoughts

This demo covers quite a few different concepts.

We divided the solution into a series of functionalities. We used core NativeScript for UX, interacting with the backend, file system management, and Routing. We extended the framework by installing plugins for functionalities like picking files. Going further, we used a bit of native code for solving a specific problem.

Using NativeScript allowed us to develop the app faster for both platforms as against individually.

**If you want to learn how you can secure your NativeScript source code against theft and reverse-engineering, be sure to check <a href="https://blog.jscrambler.com/protecting-your-nativescript-source-code-with-jscrambler/?utm_source=dev.to&utm_medium=referral" target="_blank">our tutorial</a>.**

**This article was originally published on the [Jscrambler Blog](https://blog.jscrambler.com/create-a-file-storage-mobile-app-with-nativescript-5/?utm_source=dev.to&utm_medium=referral) by Karan Gandhi**.