2026-05-04 14:47:06 -04:00
---
2026-05-19 15:12:20 -04:00
title : Build a CLI app with Logos Storage
2026-05-04 14:47:06 -04:00
doc_type : procedure
product : storage
2026-05-19 15:12:20 -04:00
topics : []
2026-05-04 14:47:06 -04:00
steps_layout : sectioned
authors :
owner : logos
doc_version : 1
2026-05-25 14:41:49 -04:00
slug : storage-tutorial
2026-05-04 14:47:06 -04:00
---
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
# Build a CLI app with Logos Storage
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
#### Get started building a CLI application that transfers files over the Logos Storage network.
2026-02-03 16:42:23 -03:00
2026-05-19 15:17:38 -04:00
This tutorial walks you through building a simple CLI application that uploads and downloads files over the Logos Storage network using the [Logos Storage Module API v0.3.2 ](https://logos-co.github.io/logos-storage-module/api_reference.html ). It is intended for developers who are setting up a new application using the skeleton project and working through the module lifecycle for the first time.
2026-02-03 16:42:23 -03:00
2026-05-28 20:31:32 +02:00
The tutorial uses the [Logos Storage App Skeleton ](https://github.com/logos-storage/logos-storage-app-skeleton ), which provides a ready-made entry point at `app_main` that uses the `LogosModules` object to access the API. The skeleton also provides a set of Qt-compatible synchronization utilities.
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
**Before you start** , make sure you have the following:
2026-02-03 16:42:23 -03:00
2026-05-28 20:31:32 +02:00
- [Nix package manager ](https://nixos.org/download/ )
2026-05-19 15:12:20 -04:00
- Git
2026-02-03 16:42:23 -03:00
2026-05-04 14:47:06 -04:00
## What to expect
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
- You can initialize, start, and cleanly shut down the storage module within your application.
- You can upload a file to the network and receive a Content Identifier (CID) that uniquely identifies it.
2026-05-28 20:31:32 +02:00
- You can download a file from the network using a Content Identifier (CID).
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
## Step 1: Build the skeleton app
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
Clone the skeleton repository and compile the binary so you have a working entry point before adding any storage logic.
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
1. Clone the skeleton repository:
```bash
git clone https://github.com/logos-storage/logos-storage-app-skeleton.git
cd logos-storage-app-skeleton
` ``
1. Build with Nix:
` ``bash
nix build
` ``
2026-05-28 20:31:32 +02:00
If you don't have flakes enabled globally, add experimental flags:
` ``bash
nix build --extra-experimental-features 'nix-command flakes'
` ``
2026-05-19 15:12:20 -04:00
1. Confirm the compiled binary is available at ` ./result/bin/storage-app`.
## Step 2: Initialize the module
2026-05-28 20:31:32 +02:00
The skeleton's ` app_main` already initializes and starts the module, so you have a working baseline. The snippets in the rest of this tutorial show the calls in isolation; extend ` app/main.cpp` to add upload and download logic on top.
2026-05-19 15:12:20 -04:00
2026-05-28 20:31:32 +02:00
Call ` init()` with your configuration once at startup, passing a JSON configuration string. See the [API Reference](https://logos-co.github.io/logos-storage-module/api_reference.html) for all available options.
2026-05-19 15:12:20 -04:00
2026-05-28 20:31:32 +02:00
` ``cpp
const QString jsonConfig = "{"
"\"listen-addrs\": [\"/ip4/0.0.0.0/tcp/8000\"],"
"\"disc-port\": 9000,"
"\"data-dir\": \"./app-data\","
"\"nat\": \"none\""
"}";
bool result = m_logos->storage_module.init(jsonConfig);
` ``
> [!CAUTION]
2026-05-19 15:12:20 -04:00
> Do not call ` init()` more than once per instance unless you call ` destroy()` first.
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
## Step 3: Start the node
2026-02-03 16:42:23 -03:00
2026-05-19 15:12:20 -04:00
Subscribe to the ` storageStart` event and call ` start()` afterward, to be able to detect failures.
2026-02-03 16:42:23 -03:00
2026-05-28 20:31:32 +02:00
` ``cpp
m_logos->storage_module.on("storageStart", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString error = data[1].toString();
// Handle error
}
});
2026-05-04 14:47:06 -04:00
2026-05-28 20:31:32 +02:00
bool result = m_logos->storage_module.start();
` ``
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
## Step 4: Upload a file
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
The Storage Module allows for two upload approaches. Choose ` uploadUrl` for straightforward cases. Use the streaming API when you need fine-grained control over how data is sent.
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
### Upload with ` uploadUrl`
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
The simplest way to upload files is to subscribe to the upload events, then call ` uploadUrl()` with the path to your file. The network returns a Content Identifier (CID) on success.
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
1. Subscribe to the ` storageUploadDone` and ` storageUploadProgress` events:
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
` ``cpp
//m_logos is the LogosModules object, used for API calls
m_logos->storage_module.on("storageUploadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
QString cidOrError = data[2].toString();
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
if (success) {
qDebug() << "Upload complete. CID:" << cidOrError;
} else {
qDebug() << "Upload failed:" << cidOrError;
2026-05-04 14:47:06 -04:00
}
2026-05-19 15:12:20 -04:00
});
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
m_logos->storage_module.on("storageUploadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
int bytes = data[2].toInt();
qDebug() << "Uploaded" << bytes << "bytes";
});
2026-05-04 14:47:06 -04:00
` ``
2026-05-19 15:12:20 -04:00
1. Call ` uploadUrl()` with the local file path:
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
` ``cpp
QUrl fileUrl = QUrl::fromLocalFile("/path/to/myfile");
LogosResult result = m_logos->storage_module.uploadUrl(fileUrl);
2026-05-04 14:47:06 -04:00
` ``
2026-05-19 15:12:20 -04:00
### Upload with the streaming API
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
Use the streaming upload API for chunk-level control.
1. Initialize the session:
2026-05-28 20:31:32 +02:00
` ``cpp
LogosResult result = m_logos->storage_module.uploadInit(filename);
QString sessionId = result.getValue<QString>();
` ``
2026-05-19 15:12:20 -04:00
2026-05-28 20:31:32 +02:00
1. Upload chunks:
` ``cpp
QFile file(filepath);
file.open(QIODevice::ReadOnly);
int chunkSize = 1024 * 64;
while (!file.atEnd()) {
QByteArray chunk = file.read(chunkSize);
result = m_logos->storage_module.uploadChunk(sessionId, chunk);
if (!result.success) {
// Handle error
break;
}
}
` ``
1. Finalize:
` ``cpp
result = m_logos->storage_module.uploadFinalize(sessionId);
if (result.success) {
QString cid = result.getValue<QString>();
qDebug() << "CID:" << cid;
}
` ``
2026-05-19 15:12:20 -04:00
## Step 5: Download a file
2026-05-28 20:31:32 +02:00
To download content, you need the CID returned during upload. The storage module discovers the content on the network using the CID.
2026-05-19 15:12:20 -04:00
1. Subscribe to the ` storageDownloadDone` and ` storageDownloadProgress` events:
` ``cpp
//m_logos is the LogosModules object, used for API calls
m_logos->storage_module.on("storageDownloadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString message = data[1].toString();
if (success) {
qDebug() << "Download complete";
} else {
qDebug() << "Download failed:" << message;
}
});
m_logos->storage_module.on("storageDownloadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
int size = data[2].toInt();
qDebug() << "Downloaded" << size << "bytes";
});
2026-05-04 14:47:06 -04:00
` ``
2026-05-19 15:12:20 -04:00
1. Call ` downloadToUrl()` with the CID and the local destination path:
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
` ``cpp
QUrl destination = QUrl::fromLocalFile("/path/to/output");
LogosResult result = m_logos->storage_module.downloadToUrl(cid, destination /*, local = false*/);
2026-05-04 14:47:06 -04:00
` ``
2026-05-19 15:12:20 -04:00
- Set the ` local` (third) parameter of ` downloadToUrl` to ` true` to retrieve only locally-cached data.
- Leave ` local` as ` false` (default) to fetch from the network.
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
## Step 6: Stop and clean up
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
Always stop the node before destroying resources to avoid leaving sessions open. To do so, call ` stop()` and wait for the ` storageStop` event, then call ` destroy()`:
2026-05-28 20:31:32 +02:00
` ``cpp
LogosResult result = m_logos->storage_module.stop();
// Wait for storageStop event...
result = m_logos->storage_module.destroy();
` ``
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
## Frequently asked questions
2026-05-04 14:47:06 -04:00
2026-05-19 15:12:20 -04:00
### Can I run the storage module without a UI?
2026-05-04 14:47:06 -04:00
2026-05-28 20:31:32 +02:00
Yes. The storage module is a Qt plugin that can be loaded by any Logos Core host, including the headless [` logoscore`](https://github.com/logos-co/logos-logoscore-cli) CLI. See the [` logoscore` README](https://github.com/logos-co/logos-logoscore-cli) for headless usage and how to wire the storage module plugin into it.