A lightweight Android APK that runs a high-performance QuickJS Express.js HTTP server with optional Tor hidden service support.
- Ultra-lightweight: QuickJS uses ~1.5MB RAM vs Node.js ~92MB
- High performance: 8x faster than Node.js Express for simple APIs
- Auto-start on boot: Service starts automatically when device boots
- Tor integration: Optional hidden service support with automatic onion address generation
- Customizable: Modify
server.jsbefore building to create your own API - Debug mode: View real-time logs in the app
- Production mode: Headless background service
QuickJS with native sockets dramatically outperforms Node.js:
| Metric | QuickJS | Node.js | Improvement |
|---|---|---|---|
| Requests/sec | 1,375 | 700 | 2x faster |
| RAM Usage | 1.5MB | 92MB | 60x more efficient |
| Latency | 73ms | 142ms | 2x lower |
Benchmarks based on 10K requests with keep-alive connections
Edit config.json:
{
"app_name": "qjsrht",
"package_name": "com.stringmanolo.qjsrht",
"version_code": 1,
"version_name": "1.0.0",
"build_mode": "debug",
"server": {
"address": "127.0.0.1",
"port": 8080,
"use_onion": false
}
}Options:
build_mode:"debug"(shows UI with logs) or"production"(headless)address: Server bind address ("127.0.0.1","0.0.0.0", or leave as-is for Tor)port: Server portuse_onion:trueto create Tor hidden service,falsefor normal HTTP
Edit app/src/main/assets/js/server.js to add your own endpoints:
import express from './express.js';
const app = express();
// Your custom endpoints here
app.get('/api/hello', (req, res) => {
res.json({ message: 'Hello from QuickJS!' });
});
app.post('/api/data', (req, res) => {
const data = JSON.parse(req.body);
res.json({ received: data });
});
// Start server
const PORT = parseInt(std.getenv('SERVER_PORT') || '8080');
const ADDRESS = std.getenv('SERVER_ADDRESS') || '127.0.0.1';
app.listen(PORT, ADDRESS, () => {
console.log(`Server running on ${ADDRESS}:${PORT}`);
});-
Fork this repository or use it as a template
-
Push your changes to
mainormasterbranch -
GitHub Actions will automatically:
- Compile QuickJS for ARM32 and ARM64
- Compile network sockets module
- Download/prepare Tor binaries
- Generate Tor hidden service (if enabled)
- Build and package the APK
-
Download APK from the Actions tab β Artifacts
- Download the APK from GitHub Actions artifacts
- Install on your Android device (API 21-28)
- Launch the app (if debug mode) or just let it run in background
- Access your server:
- Local:
http://127.0.0.1:8080 - Network:
http://device-ip:8080(if using0.0.0.0) - Tor: Check build logs for your
.onionaddress
- Local:
qjsrht/
βββ config.json # Build configuration
βββ app/
β βββ src/main/
β β βββ java/com/stringmanolo/qjsrht/
β β β βββ MainActivity.kt # Main activity (debug UI)
β β β βββ BootReceiver.kt # Auto-start on boot
β β β βββ ServerService.kt # Core service
β β βββ assets/
β β β βββ js/
β β β β βββ express.js # Express-like framework
β β β β βββ server.js # YOUR CUSTOMIZABLE SERVER
β β β βββ armeabi-v7a/ # ARM32 binaries (auto-generated)
β β β βββ arm64-v8a/ # ARM64 binaries (auto-generated)
β β β βββ tor/ # Tor config (if enabled)
β β βββ AndroidManifest.xml
β βββ build.gradle
βββ .github/workflows/
β βββ build.yml # GitHub Actions build workflow
βββ README.md
Debug Mode ("build_mode": "debug"):
- Shows UI with real-time logs
- Useful for development and testing
- Logs saved to internal storage
Production Mode ("build_mode": "production"):
- No UI, runs as background service
- Lower resource usage
- No logging overhead
Tor Hidden Service
To enable Tor:
{
"server": {
"address": "127.0.0.1",
"port": 8080,
"use_onion": true
}
}When you build:
- GitHub Actions runs Tor to generate a hidden service
- Your
.onionaddress appears in build logs as:DEBUG-ONION: xxxxxxxxxx.onion - Cryptographic keys are embedded in the APK
- Your server is accessible via Tor browser
Change package_name in config.json to make it your own app:
{
"app_name": "MyApp",
"package_name": "com.example.myapp"
}The included express.js framework provides a familiar API:
import express from './express.js';
const app = express();
// Middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
// Route parameters
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ userId });
});
// Query parameters
app.get('/search', (req, res) => {
const query = req.query.q;
res.json({ query });
});
// POST with JSON body
app.post('/api/data', (req, res) => {
const data = JSON.parse(req.body);
res.status(201).json(data);
});
// Start server
app.listen(8080, '0.0.0.0');app.get(path, handler)app.post(path, handler)app.put(path, handler)app.delete(path, handler)app.patch(path, handler)app.all(path, handler)app.use(middleware)
req.method- HTTP methodreq.path- URL pathreq.url- Full URLreq.query- Query parameters objectreq.params- Route parametersreq.headers- Request headersreq.body- Raw request bodyreq.get(header)- Get header value
res.status(code)- Set status coderes.json(obj)- Send JSONres.send(data)- Send responseres.html(html)- Send HTMLres.text(text)- Send textres.set(key, value)- Set headerres.redirect(url)- 302 redirect
- Tor hidden services provide anonymity but require Tor browser to access
- Local binding (
127.0.0.1) only allows device-local access - Network binding (
0.0.0.0) exposes server on WiFi/cellular network - No HTTPS - Use reverse proxy (nginx, Caddy) for TLS
- API 28 restriction - Required for direct binary execution on Android
- Boot Receiver β Starts
ServerServiceon device boot - ServerService β Extracts binaries from APK assets to internal storage
- Tor (optional) β Starts hidden service with pre-generated keys
- QuickJS β Runs
server.jswith express.js framework - Native Sockets β Epoll-based event loop handles HTTP requests
- Minimal footprint: ~200KB engine vs 30MB+ for V8
- Fast startup: Instant vs seconds for Node.js
- Low memory: 60x more efficient than Node.js
- Embeddable: Perfect for Android apps
- ES2020 support: Modern JavaScript features
Android API 28 (Android 9.0) is the last version that allows apps to execute binaries directly. API 29+ requires complex workarounds or NDK integration.
- Android API 21-28 (Android 5.0 - 9.0)
- ARMv7 (32-bit) devices
- ARM64 (64-bit) devices
- Enable "Install from Unknown Sources"
- Check Android version (must be 5.0-9.0)
- Check logs in debug mode
- Verify port is not already in use
- Ensure binaries were extracted (check
/data/data/com.stringmanolo.qjsrht/files/bin/)
- Check build logs for
DEBUG-ONION:address - Wait 30-60 seconds for Tor to bootstrap
- Verify Tor binary was included in APK
- Use
"address": "0.0.0.0"in config - Check device firewall
- Ensure WiFi allows local network access
- QuickJS by Fabrice Bellard - https://bellard.org/quickjs/
- qjsNetworkSockets by StringManolo - https://github.com/StringManolo/qjsNetworkSockets
- Tor Project - https://www.torproject.org/
- KotlinApkTemplate by StringManolo - https://github.com/StringManolo/kotlinapktemplate