How to Upload a Folder to an SFTP Server Using TypeScript and ssh2-sftp-client

When working on Node.js applications, there are many times when you need to transfer files to remote servers. While FTP is still in use, SFTP (Secure File Transfer Protocol) is more secure and increasingly preferred. Today, I’ll walk you through how to upload a folder of files to an SFTP server using TypeScript and the ssh2-sftp-client library.

Thank me by sharing on Twitter 🙏

I’ll break this down into clear steps, from setting up the SFTP connection to recursively uploading files and ensuring the target directories exist. The process may seem a bit daunting at first, but by the end, you’ll have a working solution.

Why SFTP?

Before we get into the how-to, let’s briefly touch on why SFTP is a great choice. SFTP provides a secure way to transfer files over a network. Unlike FTP, which sends data in plain text, SFTP encrypts your data, making it much safer. This is especially important when dealing with sensitive information. Plus, SFTP is widely supported and relatively easy to set up in many hosting environments.

Installing ssh2-sftp-client

To begin, you’ll need to install the ssh2-sftp-client package, which is a well-maintained SFTP client library for Node.js. It handles the complexity of connecting and transferring files via SFTP with minimal configuration.

To install it, run:

ShellScript
npm install ssh2-sftp-client

We’ll also need a way to load environment variables securely. For that, we’ll use the dotenv package:

ShellScript
npm install dotenv

This allows us to keep sensitive information, like server credentials, out of the main code.

Setting Up Environment Variables

Instead of hardcoding sensitive information such as your server’s credentials directly in your script, it’s better to store them in an .env file. This way, you can load them dynamically and keep your code secure and flexible.

Here’s an example of what your .env file might look like:

Plaintext
SFTP_HOST=your-sftp-server.com
SFTP_PORT=22
SFTP_USER=your-username
SFTP_PASSWORD=your-password

Make sure to replace these values with your actual SFTP server credentials. The SFTP_PORT defaults to 22, which is standard for SFTP, but you can change it if necessary.

Building the Upload Function

Now that we have the ssh2-sftp-client package installed and environment variables set up, let’s write the TypeScript code to upload a folder of files to the SFTP server. We want our solution to be flexible and handle different file types, subfolders, and directory creation if needed.

First, load your environment variables and import the required packages:

TypeScript
import * as dotenv from "dotenv";
import * as path from "path";
import * as fs from "fs-extra";
import SftpClient from "ssh2-sftp-client";

dotenv.config();

Creating Directories on the Server

One thing you’ll encounter when working with SFTP is the need to ensure that the destination directory exists. If the directory doesn’t exist, your upload will fail. To address this, we need to create a helper function that checks if a directory exists on the server and, if not, creates it.

Here’s how you can handle that:

TypeScript
async function ensureRemoteDirExists(sftp: SftpClient, remoteFolder: string) {
    const dirExists = await sftp.exists(remoteFolder);
    if (!dirExists) {
        console.log(`Creating remote folder: ${remoteFolder}`);
        await sftp.mkdir(remoteFolder, true); // Recursive create
    } else {
        console.log(`Remote folder ${remoteFolder} already exists.`);
    }
}

This function checks for the existence of the directory using sftp.exists() and, if necessary, creates it using sftp.mkdir(). The true flag ensures that the directory is created recursively if needed.

Uploading Files and Folders Recursively

Next, let’s write the core function that uploads the contents of a local folder to the remote SFTP server. It needs to:

  1. Read all files and directories from the local folder.
  2. Ensure that remote directories exist.
  3. Upload each file, recursively processing subdirectories.

Here’s what that function looks like:

TypeScript
async function uploadFolderToSftp(localFolder: string, remoteFolder: string) {
    const sftp = new SftpClient();

    try {
        // Connect to SFTP server using credentials from .env
        await sftp.connect({
            host: process.env.SFTP_HOST!,
            port: parseInt(process.env.SFTP_PORT || "22", 10),
            username: process.env.SFTP_USER!,
            password: process.env.SFTP_PASSWORD!,
        });

        // Ensure the remote folder exists
        await ensureRemoteDirExists(sftp, remoteFolder);

        // Get the list of files in the local folder
        const files = await fs.readdir(localFolder);

        for (const file of files) {
            const localFilePath = path.join(localFolder, file);
            const remoteFilePath = path.join(remoteFolder, file);

            const stats = await fs.stat(localFilePath);

            if (stats.isDirectory()) {
                // Recursively upload subfolders
                await uploadFolderToSftp(localFilePath, remoteFilePath);
            } else {
                // Upload file
                console.log(`Uploading ${localFilePath} to ${remoteFilePath}`);
                await sftp.put(localFilePath, remoteFilePath);
            }
        }

        console.log("All files uploaded successfully.");
    } catch (err) {
        console.error("Error uploading files:", err);
    } finally {
        // Close the SFTP connection
        await sftp.end();
    }
}

Here’s what’s happening in this function:

  • Connecting to the SFTP server: We use the credentials loaded from the .env file.
  • Ensure directories exist: For each file or folder, the script checks if the remote directory exists before uploading.
  • Upload files: It uploads files one by one using sftp.put(), and if it encounters a directory, it recursively calls the same function to handle subfolders.

Handling Permission Issues

One issue you might encounter is permission errors, especially when creating directories on the server. If you see an error like Permission denied, it’s likely that the SFTP user account doesn’t have sufficient privileges to write to the target directory.

Make sure that the user has write permissions in the target folder. In some cases, this might require changing the path to a directory where the user has access, like the user’s home directory.

Final Steps

Once you’ve written and tested your script, make sure you run it like any Node.js script. Use tsc to compile it to JavaScript if needed, then execute it. The script will handle creating directories, checking if files exist, and uploading everything, including nested subdirectories.

If you run into any errors related to permissions or folder existence, the script will log those errors, helping you debug and resolve issues.

Conclusion

SFTP is a secure and efficient way to transfer files, and with ssh2-sftp-client, it’s easy to automate the process using TypeScript. In this post, I showed how to upload an entire folder, recursively create directories, and handle common issues like missing folders or permission problems. By utilizing environment variables, we keep our credentials secure and flexible. I hope this guide helps you integrate SFTP functionality into your projects, making your file transfer processes secure and reliable.

Share this:

Leave a Reply