In this final part, we’ll explore how to set up, run, and test the complete application across different platforms.
Before running the application, ensure you have:
localhost:5672 with guest:guestdocker run -d --name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3-management
firebase-admin-sdk.json in the project rootSERVICE_ACCOUNT_PATH environment variableThe JVM target is ideal for development:
./gradlew jvmRun
This command:
For production deployment, use native executables:
macOS (ARM64):
./gradlew runDebugExecutableMacosArm64
Linux (x64):
./gradlew runDebugExecutableLinuxX64
Windows (x64):
./gradlew runDebugExecutableMingwX64
The application will:
You should see output similar to:
[main] INFO Application - Application started
[main] INFO Application - Responding at http://0.0.0.0:8080
To build optimized native executables:
./gradlew build
Executables will be located at:
build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexebuild/bin/linuxX64/releaseExecutable/ktor-native-worker-tutorial.kexebuild/bin/mingwX64/releaseExecutable/ktor-native-worker-tutorial.exeThese are standalone executables that can be deployed without a JVM.
Configure the application using environment variables:
export RABBITMQ_HOST=localhost
export RABBITMQ_PORT=5672
export RABBITMQ_USER=guest
export RABBITMQ_PASSWORD=guest
export SERVICE_ACCOUNT_PATH=/path/to/firebase-admin-sdk.json
Then run:
./gradlew jvmRun
Or for native executables:
export SERVICE_ACCOUNT_PATH=/path/to/firebase-admin-sdk.json
./build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexe
Send a test notification request:
curl -X POST http://localhost:8080/api/notifications \
-H "Content-Type: application/json" \
-d '{
"token": "your-fcm-device-token-here",
"title": "Hello World",
"body": "This is a test notification from Ktor Native!"
}'
Expected response:
HTTP/1.1 200 OK
If you prefer HTTPie:
http POST localhost:8080/api/notifications \
token=your-fcm-device-token-here \
title="Hello World" \
body="This is a test notification from Ktor Native!"
http://localhost:8080/api/notificationsContent-Type: application/json{
"token": "your-fcm-device-token-here",
"title": "Hello World",
"body": "This is a test notification from Ktor Native!"
}
When you send a request, here’s what happens:
/api/notificationsNotificationPayloadSendNotificationEventSendNotificationHandler invokedSendNotificationEventNotificationService.sendNotification()If you’re running RabbitMQ with the management plugin:
http://localhost:15672 in your browserguest:guestnotifications_queueAfter starting the application, verify RabbitMQ connection:
notifications_exchange exists (type: topic)notifications_queue exists (durable: true)notifications.sendnotifications_queuecurl -X POST http://localhost:8080/api/notifications \
-H "Content-Type: application/json" \
-d '{"invalid": "data"}'
Expected: 400 Bad Request (missing required fields)
curl -X POST http://localhost:8080/api/notifications \
-d '{"token": "xxx", "title": "Hi", "body": "Test"}'
Expected: 415 Unsupported Media Type
curl -X POST http://localhost:8080/api/notifications \
-H "Content-Type: application/json" \
-d '{
"token": "invalid-token",
"title": "Test",
"body": "This will fail"
}'
Expected: 200 OK (request accepted), but notification sending will fail in the worker. Check application logs for FCM error.
for i in {1..100}; do
curl -X POST http://localhost:8080/api/notifications \
-H "Content-Type: application/json" \
-d "{\"token\": \"test-$i\", \"title\": \"Test $i\", \"body\": \"Message $i\"}" &
done
wait
This sends 100 concurrent requests. Monitor:
ab -n 1000 -c 10 -p payload.json -T application/json \
http://localhost:8080/api/notifications
Create payload.json:
{
"token": "test-token",
"title": "Load Test",
"body": "Testing"
}
JVM:
time ./gradlew jvmRun
Native:
time ./build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexe
Native executables typically start much faster than JVM.
Monitor memory usage while running:
On macOS/Linux:
# JVM
ps aux | grep java
# Native
ps aux | grep ktor-native-worker-tutorial
Native executables typically use less memory than JVM.
Use Apache Bench or a similar tool to compare request handling performance between JVM and native builds.
Create a Dockerfile for native builds:
FROM alpine:latest
# Install dependencies (if any needed by your native binary)
RUN apk add --no-cache libstdc++
# Copy the native executable
COPY build/bin/linuxX64/releaseExecutable/ktor-native-worker-tutorial.kexe /app/server
# Copy Firebase service account
COPY firebase-admin-sdk.json /app/
WORKDIR /app
# Set environment variables
ENV RABBITMQ_HOST=rabbitmq
ENV SERVICE_ACCOUNT_PATH=/app/firebase-admin-sdk.json
# Run the executable
CMD ["/app/server"]
Build and run:
docker build -t ktor-worker .
docker run -p 8080:8080 --link rabbitmq ktor-worker
The native executable is perfect for Kubernetes:
You can run multiple instances of the application:
Check RabbitMQ Connection:
# Verify RabbitMQ is running
docker ps | grep rabbitmq
# Check connectivity
telnet localhost 5672
Check Firebase Service Account:
# Verify file exists
ls -la firebase-admin-sdk.json
# Verify it's valid JSON
cat firebase-admin-sdk.json | python -m json.tool
Check Consumer Status:
Check Queue Bindings:
notifications.sendtopicVerify FCM Token:
Check Application Logs:
This tutorial covered:
Putting it all together:
The result is a production-ready, native-compiled backend that:
Congratulations on completing the tutorial!