A project that showcases high-throughput file & message transfer over gRPC together with a React/TypeScript frontend.
The stack is split into three Rust services plus a React UI:
- server – administrative gRPC + REST layer (authentication, user management, routing logic)
- runner – bundles two internal services:
- client – exposes an HTTP
/uploadendpoint that fans-out transfers to destination banks over gRPC - destination – receives inbound transfers, persists files/metadata & acknowledges progress
- client – exposes an HTTP
- frontend – simple React dashboard for admins & banks
- Docker & Docker Compose v2
- OpenSSL (only required once to generate the JWT key-pair)
- Optional – Rust toolchain & Node.js if you prefer running services outside Docker
git clone https://github.com/AdityaGund/ftp-grpc.git
cd ftp-grpcIf you already have the ftp-grpc-* images locally (or pulled from a registry):
docker compose up -ddocker compose -f docker-compose.build.yml build
docker compose -f docker-compose.build.yml up -dCreate an RSA-256 key-pair once under the keys/ folder (already mounted into every container):
Only share public key with banks.
mkdir -p keys
openssl genrsa -out keys/admin_private.pem 4096
openssl rsa -in keys/admin_private.pem -pubout -out keys/admin_public.pemThe paths inside the containers are fixed to /app/keys/admin_private.pem and /app/keys/admin_public.pem – change them only if you also update the corresponding JWT_*_KEY_PATH variables.
Each micro-service contains a ready-made .env.example. Copy it to .env and tweak as needed:
cp server/.env.example server/.env
cp client/.env.example client/.env
cp destination/.env.example destination/.env
cp runner/.env.example runner/.env
cp frontend/.env.example frontend/.envThings to change after copying .env files,
client/.env- change MONGO_URI and SERVER_HOSTdestination/.env- change MONGO_URIfrontend/.env- change VITE_SERVER_API_URLrunner/.env- change MONGO container credentials and ADMIN_SERVER_HOSTserver/.env- change MONGO_URI and MONGO container credentials
to change env files within the containers, run
docker exec -it <container-name> shand use vim/nano to edit the files
- Admin DB – used exclusively by the
serverservice (admin & routing data). - Bank DB – shared by both the
clientanddestinationservices (transfer metadata).
They can live on the same Mongo instance – simply point each .env file to the appropriate database name.
The database container passwords are set as:
- username:
mongoAdmin - password:
adminPass123
- username:
mongoBank - password:
bankPass123
To change these, make changes in server/.env and runner/.env.
Make proper changes to the MONGO_URI as well.
Before logging in to the dashboard you must create at least one Admin account in the admin_db.admin collection. Without it the authentication endpoint will return an error.
-
Ensure the stack is running:
docker compose up -d # or the build variant if you built the images locally -
Open an interactive Mongo shell inside the
mongoAdmincontainer (password:adminPass123):docker exec -it mongoAdmin mongosh -u mongoAdmin -p adminPass123 --authenticationDatabase admin -
Insert an admin document (adjust the ObjectId / credentials as you like):
use admin_db db.admin.insertOne({ _id: ObjectId("685258ebc25f4303af50ddf2"), username: "A001", password: "$argon2id$v=19$m=19456,t=2,p=1$GTwEdGQ07tZ1zOWLU8UShQ$5M3mYiVPgnR7nsH3rm7Orcdj24V8xGL+AZIHv1Uafwo" })
the above password is
testpass123hashed using argon2 crate. change it later using UI.You can also run the helper script
playground-1.mongodb.js(VS Code MongoDB playground)
Once at least one Admin exists you can log in (POST /login) to test the UI.
| Method | Path | Auth | Purpose | Required headers / fields |
|---|---|---|---|---|
| POST | /login |
No | Obtain JWT. | username, password headers |
| POST | /api/add |
Admin | Add a new user (Bank or Admin). | Headers: username, password, ip (IP mandatory for Bank users) |
| POST | /api/update |
Admin | Update user password and/or IP. | Headers: username and optional password, ip |
| POST | /api/delete |
Admin | Remove a user. | Header: username |
| GET | /api/available |
Any JWT | List all available bank destinations. | – |
| GET | /api/users |
Admin | Full list of banks & admins. | – |
| GET | /api/file-info |
Any JWT | Fetch stored file metadata (all banks). | – |
| POST | /api/admin-upload |
Admin | Send file or text message to selected banks. | multipart/form-data fields: • file – binary file (optional)• message – text (optional)• destinations – JSON string [ {"username":"BANK_D", "ip":"127.0.0.1"}, ... ]• sender – your admin username |
The token returned by
/loginendpoint is only valid for 60 minutes. This can be changed insideserver/src/handlers.rs.
| Method | Path | Auth | Purpose | Required headers / fields |
|---|---|---|---|---|
| POST | /upload |
Bank JWT | Upload a file and/or message to other banks via the Admin server. | multipart/form-data fields identical to /api/admin-upload |
| GET | /file-info |
Bank JWT | Retrieve this bank's transfer history. | – |
- Proto file:
proto/ftp.proto - Services are exposed on:
- Admin Server →
grpc://localhost:50051 - Destination Bank →
grpc://localhost:50053
- Admin Server →
service TransferService {
rpc Transfer(stream TransferRequest) returns (stream TransferResponse);
}
Use any gRPC client (e.g. grpcurl, Postman) to interact. Maximum message size is 8 mb.
- Bank ➜ Bank transfer (with Server in the middle)
- A Bank runs the runner container which bundles two micro-services:
- client – exposes a simple HTTP
POST /uploadendpoint for the local Bank UI or CLI. - destination – receives inbound gRPC streams from other peers.
- client – exposes a simple HTTP
- When a Bank user uploads a file/message, client breaks the payload into chunks and opens a bidirectional gRPC stream (
TransferService.Transfer) to the server. - The server acts as a smart router: it checks the JWT, looks up the requested recipient Banks in its MongoDB, then fans-out the same stream to each recipient's destination service.
- Each destination writes the incoming chunks to disk / DB, sends progress back through the stream, and finally ACKs success or failure.
- Admin ➜ Bank transfer
- Admins talk to the server directly over HTTP (
/api/admin-upload). - The server reuses the exact same gRPC fan-out pipeline described above to push the payload to one or many Banks.
- Transport & APIs
- gRPC (powered by
tonic) is used for the heavy-lifting: it enables bidirectional streaming and 8 MB message sizes. - actix-web powers all REST endpoints (
/login,/upload, etc.). - JWT (RSA-256) secures every hop – Banks and Admins present the token on both HTTP and gRPC calls.
- Why this matters
- Multi-Bank: one push can target any subset of Banks; adding a new Bank is just a DB insert.
- Resilience: chunked streaming means large files resume on network hiccups (server can ask for
RETRY). - Observability: every transfer is timestamped and stored, so transfer history is queryable via REST.
-
Frontend → Server: make sure
VITE_SERVER_API_URLpoints toftp-grpc-server:50052(already shown above). This is the service name reachable from the browser running inside the frontend container. -
Ad-hoc file transfers from your host → runner: send gRPC/HTTP requests to an address that's inside the compose network – either use the service name
ftp-grpc-runneror the container's IP (obtain withdocker inspect ftp-grpc-runner). Requests tolocalhostwon't work because they bypass the Docker bridge.