Sidecar infrastructure services for mik.
mikcar provides HTTP-based infrastructure services for WASM handlers running in mik:
- Storage - Object storage (S3, GCS, MinIO, local filesystem)
- KV - Embedded key-value store (redb - pure Rust, no external dependencies)
- SQL - Database proxy (Postgres, SQLite)
- Queue - Message queues (Redis Streams, RabbitMQ)
- Secrets - Secret managers (Vault, AWS Secrets Manager, GCP Secret Manager)
- Email - SMTP email (works with any provider)
Same API works locally and in production. Your WASM handlers don't know or care if they're talking to MinIO or AWS S3.
Two optimized images available:
| Image | Size | Queue Support | Base |
|---|---|---|---|
mikcar |
~21MB | No | scratch (musl static) |
mikcar-all |
~65MB | Redis, RabbitMQ | distroless |
# Minimal image (no queue) - recommended for most use cases
docker pull ghcr.io/dufeutech/mikcar:latest
# Full image with queue support
docker pull ghcr.io/dufeutech/mikcar-all:latest
# Pull specific version
docker pull ghcr.io/dufeutech/mikcar:0.1.0docker run -p 3001:3001 \
-e SIDECAR_TOKEN=secret \
-e KV_URL=memory:// \
-e STORAGE_URL=memory:// \
ghcr.io/dufeutech/mikcar:latest --kv --storage# Minimal image (no queue)
docker build -t mikcar .
# Full image with queue support
docker build -f Dockerfile.all -t mikcar:all .# Install with default services (storage, kv, sql, secrets, email)
cargo install mikcar
# Install with queue support
cargo install mikcar --features all
# Install specific services only
cargo install mikcar --features storage,kv# Run storage service
STORAGE_URL=file:///data mikcar --storage --port 3001
# Run KV service (embedded redb, no external dependencies)
KV_URL=file:///data/kv.redb mikcar --kv --port 3002
# Run SQL service
DATABASE_URL=postgres://user:pass@localhost/db mikcar --sql --port 3003
# Run secrets service
SECRETS_URL=vault://localhost:8200 mikcar --secrets --port 3004
# Run email service
EMAIL_URL=smtp://localhost:1025 mikcar --email --port 3005
# Supercar mode (multiple services on one port)
mikcar --kv --storage --sql --email --port 3001GET /object/{path} Get object
PUT /object/{path} Put object (body = content)
DELETE /object/{path} Delete object
HEAD /object/{path} Check exists + metadata
GET /list/{prefix} List objects
GET /get/{key} Get value
POST /get/batch Get multiple (body = {keys: [...]})
POST /set/{key}?ttl= Set value (body = value)
POST /set/batch Set multiple (body = {key: value, ...})
DELETE /del/{key} Delete key
GET /keys/{pattern} List keys matching pattern
POST /increment/{key} Increment (atomic)
GET /exists/{key} Check if key exists
GET /ttl/{key} Get TTL (-1 = no TTL, -2 = not found)
POST /expire/{key} Set expiration (body = {seconds: N})
POST /query Execute SELECT (body = {sql, params})
POST /execute Execute INSERT/UPDATE/DELETE
POST /batch Execute multiple statements in transaction
POST /script Execute JavaScript with SQL in transaction
The /sql/script endpoint executes JavaScript code with SQL operations within a single database transaction. This enables complex conditional logic, validation, and multi-step operations with automatic rollback on failure.
Request:
{
"script": "var user = sql.query('SELECT * FROM users WHERE id = $1', [input.userId]); if (!user.length) throw new Error('User not found'); return { user: user[0] };",
"input": { "userId": 42 }
}Response:
{
"result": { "user": { "id": 42, "name": "Alice" } },
"queries_executed": 1
}Script API:
sql.query(sql, params)- Execute SELECT, returns array of row objectssql.execute(sql, params)- Execute INSERT/UPDATE/DELETE, returns rows affectedinput- The input object from the requestreturn- Specify the response value
Supported script formats:
// 1. Export default function (recommended)
export default function(input) {
var users = sql.query("SELECT * FROM users WHERE org = $1", [input.org]);
return { count: users.length };
}
// 2. Raw code with return (simple scripts)
var users = sql.query("SELECT * FROM users");
return { count: users.length };
// 3. Raw expression (simplest)
sql.query("SELECT COUNT(*) FROM users")Transaction semantics:
- All SQL operations run in a single transaction
- If the script throws an error, the transaction is rolled back
- If any SQL operation fails, the transaction is rolled back
- Only on successful completion is the transaction committed
Example: Fund transfer with balance check
export default function(input) {
// Check source account
var from = sql.query("SELECT id, balance FROM accounts WHERE id = $1", [input.from]);
if (!from.length) throw new Error("Source account not found");
if (parseFloat(from[0].balance) < input.amount) throw new Error("Insufficient funds");
// Check destination account
var to = sql.query("SELECT id FROM accounts WHERE id = $1", [input.to]);
if (!to.length) throw new Error("Destination account not found");
// Perform transfer (atomic - both succeed or both fail)
sql.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [input.amount, input.from]);
sql.execute("UPDATE accounts SET balance = balance + $1 WHERE id = $2", [input.amount, input.to]);
return { success: true, transferred: input.amount };
}POST /publish/{topic} Publish message
GET /subscribe/{topic} Long-poll for messages
POST /ack/{topic}/{id} Acknowledge message
POST /push/{queue} Push to work queue
GET /pop/{queue} Pop from work queue
POST /send Send single email
POST /send/batch Send multiple emails
Request body (POST /send):
{
"from": "sender@example.com",
"to": ["recipient@example.com"],
"cc": ["cc@example.com"],
"bcc": ["bcc@example.com"],
"subject": "Hello",
"text": "Plain text body",
"html": "<p>HTML body</p>",
"reply_to": "reply@example.com"
}Response:
{"id": "message-id", "success": true}Batch request (POST /send/batch):
{
"emails": [
{"from": "...", "to": ["..."], "subject": "...", "text": "..."},
{"from": "...", "to": ["..."], "subject": "...", "text": "..."}
]
}Set SIDECAR_TOKEN or AUTH_TOKEN environment variable. Requests must include:
Authorization: Bearer <token>
The /health endpoint is always accessible without authentication.
| Variable | Service | Example |
|---|---|---|
STORAGE_URL |
storage | file:///data, s3://bucket, memory:// |
KV_URL |
kv | file:///data/kv.redb, memory:// |
DATABASE_URL |
sql | postgres://user:pass@host/db |
QUEUE_URL |
queue | redis://host:port, amqp://user:pass@host:port |
SECRETS_URL |
secrets | vault://host:port, awssm://region, gcpsm://project |
EMAIL_URL |
smtp://host:port, smtps://user:pass@host:465 |
|
SIDECAR_TOKEN |
auth | Bearer token for API authentication |
mikcar acts as an HTTP proxy to queue infrastructure. Supported backends:
| Backend | URL Format | Platform |
|---|---|---|
| In-memory | memory:// |
All |
| Redis Streams | redis://host:port |
Linux/macOS |
| RabbitMQ | amqp://user:pass@host:port |
Linux/macOS |
Auto-creation: Queues are automatically created on first push:
- Redis: Creates stream + consumer group (
XGROUP CREATE ... MKSTREAM) - RabbitMQ: Declares durable queue
No manual setup required - just push and pop.
Note: Queue requires the all feature or Dockerfile.all. Windows builds only support memory://.
# Redis
QUEUE_URL=redis://localhost:6379 mikcar --queue
# RabbitMQ
QUEUE_URL=amqp://guest:guest@localhost:5672 mikcar --queue
# In-memory (dev only)
QUEUE_URL=memory:// mikcar --queueSMTP is the universal email protocol - works with any provider.
| Provider | URL Format |
|---|---|
| Local dev (Mailpit) | smtp://localhost:1025 |
| Gmail | smtps://user:app-password@smtp.gmail.com:465 |
| SendGrid | smtps://apikey:SG.xxx@smtp.sendgrid.net:465 |
| AWS SES | smtps://AKIA...:secret@email-smtp.us-east-1.amazonaws.com:465 |
| Resend | smtps://resend:re_xxx@smtp.resend.com:465 |
Port behavior:
- Ports 25, 1025, 2525: Plain SMTP (no TLS) - for local testing
- Port 465: Implicit TLS (
smtps://) - Port 587: STARTTLS (
smtp://)
# Local development with Mailpit
EMAIL_URL=smtp://localhost:1025 mikcar --email
# Production with SendGrid
EMAIL_URL=smtps://apikey:SG.xxx@smtp.sendgrid.net:465 mikcar --emailmikcar can be used as a Rust library to embed sidecars in your own applications (e.g., Tauri, Axum, or custom servers).
[dependencies]
mikcar = { git = "https://github.com/dufeutech/mikcar", features = ["all"] }
tokio = { version = "1", features = ["full"] }use mikcar::{SidecarBuilder, StorageService};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let storage = StorageService::from_url("memory://")?;
SidecarBuilder::new()
.port(3001)
.auth_token("secret")
.serve(storage)
.await
}use mikcar::{SidecarBuilder, KvService, StorageService, SqlService};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let kv = KvService::from_url("memory://")?;
let storage = StorageService::from_url("memory://")?;
let sql = SqlService::from_url("postgres://user:pass@localhost/db").await?;
SidecarBuilder::new()
.port(3001)
.auth_token("secret")
.add(kv)
.add(storage)
.add(sql)
.serve_many()
.await
}use mikcar::{SidecarBuilder, KvService, StorageService};
use std::sync::Arc;
use tokio::sync::Mutex;
struct AppState {
sidecars: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
}
#[tauri::command]
async fn start_sidecars(state: tauri::State<'_, AppState>) -> Result<(), String> {
let kv = KvService::from_url("memory://").map_err(|e| e.to_string())?;
let storage = StorageService::from_url("memory://").map_err(|e| e.to_string())?;
let handle = tokio::spawn(async move {
let _ = SidecarBuilder::new()
.port(3001)
.add(kv)
.add(storage)
.serve_many()
.await;
});
*state.sidecars.lock().await = Some(handle);
Ok(())
}use mik::runtime::HostBuilder;
use mikcar::{SidecarBuilder, KvService, StorageService};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Start sidecars on port 3001
let kv = KvService::from_url("memory://")?;
let storage = StorageService::from_url("memory://")?;
tokio::spawn(async move {
SidecarBuilder::new()
.port(3001)
.add(kv)
.add(storage)
.serve_many()
.await
.unwrap();
});
// Start WASM runtime on port 3000
let host = HostBuilder::new()
.port(3000)
.modules_dir("./modules".into())
.build()
.await?;
host.serve().await?;
Ok(())
}| Type | Description |
|---|---|
SidecarBuilder |
Builder for configuring and starting sidecars |
StorageService |
S3/GCS/Azure/local filesystem storage |
KvService |
Embedded key-value store (redb) |
SqlService |
PostgreSQL/SQLite proxy |
QueueService |
Redis Streams/RabbitMQ queues |
SecretsService |
Vault/AWS/GCP secret managers |
EmailService |
SMTP email sending |
Sidecar trait |
Implement custom sidecars |
mikcar re-exports commonly used crates for convenience:
use mikcar::axum; // Web framework
use mikcar::tower; // Service abstractions
use mikcar::tower_http; // HTTP middlewareMIT