basic-ftp is surprisingly good at letting you treat FTP servers like local file systems, so much so that you can forget you’re even talking to a remote machine.
Here’s a Node.js script that demonstrates transferring files to and from an FTP server using the basic-ftp library.
const ftp = require('basic-ftp');
const fs = require('fs');
async function uploadFile(localFilePath, remoteFilePath) {
const client = new ftp.Client();
client.ftp.verbose = false; // Set to true for detailed FTP command logging
try {
await client.access({
host: 'ftp.example.com',
user: 'your_username',
password: 'your_password',
secure: false // Set to true for FTPS
});
console.log('Connected to FTP server.');
// Ensure the remote directory exists (optional, but good practice)
await client.ensureDir('/remote/path/to/upload');
const fileStream = fs.createReadStream(localFilePath);
const res = await client.upload(fileStream, remoteFilePath);
console.log(`File uploaded successfully: ${remoteFilePath}`);
console.log('Server response:', res);
} catch (err) {
console.error('Error uploading file:', err);
} finally {
client.close();
}
}
async function downloadFile(remoteFilePath, localFilePath) {
const client = new ftp.Client();
client.ftp.verbose = false;
try {
await client.access({
host: 'ftp.example.com',
user: 'your_username',
password: 'your_password',
secure: false
});
console.log('Connected to FTP server.');
const fileStream = fs.createWriteStream(localFilePath);
const res = await client.download(remoteFilePath, fileStream);
console.log(`File downloaded successfully: ${localFilePath}`);
console.log('Server response:', res);
} catch (err) {
console.error('Error downloading file:', err);
} finally {
client.close();
}
}
// Example Usage:
// Assume you have a file named 'local_file.txt' in your current directory
// and you want to upload it to '/remote/path/to/upload/remote_file.txt'
// and then download it back as 'downloaded_file.txt'.
// Create a dummy local file for testing
fs.writeFileSync('local_file.txt', 'This is a test file content.\n');
// Upload the file
uploadFile('local_file.txt', '/remote/path/to/upload/remote_file.txt')
.then(() => {
// Download the file after successful upload
return downloadFile('/remote/path/to/upload/remote_file.txt', 'downloaded_file.txt');
})
.then(() => {
console.log('Download complete. Check downloaded_file.txt');
// Clean up dummy files
fs.unlinkSync('local_file.txt');
fs.unlinkSync('downloaded_file.txt');
console.log('Dummy files removed.');
})
.catch(err => {
console.error('An error occurred during the process:', err);
});
This script defines two asynchronous functions: uploadFile and downloadFile.
The uploadFile function:
- Initializes a new
ftp.Client. - Connects to the FTP server using provided credentials.
secure: falseindicates a standard FTP connection; change totruefor FTPS. client.ensureDir('/remote/path/to/upload')is a convenient method that creates the remote directory if it doesn’t exist, preventing upload failures due to missing parent directories.- Opens a read stream for the local file.
- Uses
client.upload(fileStream, remoteFilePath)to send the file content.basic-ftphandles the streaming and FTP protocol details. - Logs success or error messages.
- Ensures the client connection is closed in the
finallyblock.
The downloadFile function mirrors this process but uses fs.createWriteStream for the local file and client.download(remoteFilePath, fileStream) to retrieve data from the server.
The example usage demonstrates creating a local file, uploading it, then downloading it back, and finally cleaning up the generated files.
A key feature of basic-ftp is its stream-based API for uploads and downloads. This means you don’t need to load the entire file into memory before transferring it, making it efficient for large files. The client.access() method is where you configure your connection details, including the host, user, password, and whether to use secure (FTPS) connections.
When client.ftp.verbose = true, you’ll see every FTP command sent and received, which is invaluable for debugging connection or transfer issues. You can see commands like USER, PASS, CWD, PASV, STOR (for upload), RETR (for download), and QUIT.
The basic-ftp library abstracts away a lot of the complexity of the FTP protocol. For instance, it handles the establishment of separate data connections for transfers (often using the PASV command for active mode or setting up the server to connect back for passive mode), managing the control connection for commands, and parsing server responses.
One detail that trips people up is how basic-ftp handles file paths. By default, operations are relative to the user’s home directory on the FTP server. If you specify an absolute path like /remote/path/to/upload/remote_file.txt, it’s interpreted as such. However, if you just use remote_file.txt, it will be placed in the current working directory on the server, which might not be what you expect if you haven’t explicitly changed it using client.cd('/some/other/directory').
The next step you’ll likely encounter is handling FTP directory listings and potentially synchronizing entire directory structures.