ai-express

🚀 Roya Framework

Express for Java. AI-Native. 100x Faster.

Roya brings Express.js’s beloved API to modern Java, with built-in AI superpowers and cloud-cost optimization.

import static com.akilisha.oss.roya.Roya.*;

void main() {
    var app = create();
    
    app.use(cors());
    app.use(json());
    
    app.get("/", (req, res, next) -> {
        res.send("Hello World");
    });
    
    app.get("/users/:id", (req, res, next) -> {
        res.json(Map.of("id", req.params().get("id")));
    });
    
    app.listen(3000);
}

If you know Express, you already know Roya.


Why Roya?

🎯 Express-Compatible API

⚡ Modern Java Performance

🤖 AI-Native Design

💰 Cloud Cost Optimization


Quick Start

Prerequisites

Installation (Coming Soon)

# CLI tool (planned)
$ roya new my-app
$ cd my-app
$ roya dev

Manual Setup (Current)

// Main.java
import static com.roya.Roya.*;

void main() {
    var app = create();
    
    app.get("/", (req, res, next) -> {
        res.send("Hello from Roya!");
    });
    
    app.listen(3000, () -> {
        System.out.println("Server running on http://localhost:3000");
    });
}

Examples

Basic REST API

import static com.roya.Roya.*;

record User(int id, String name, String email) {}

void main() {
    var app = create();
    
    app.use(cors());
    app.use(json());
    
    // Get all users
    app.get("/users", (req, res, next) -> {
        var users = db().query("SELECT * FROM users").list(User.class);
        res.json(users);
    });
    
    // Get user by ID
    app.get("/users/:id", (req, res, next) -> {
        var user = db().findOne(User.class, req.params().get("id"));
        if (user == null) {
            res.status(404).json(Map.of("error", "User not found"));
        } else {
            res.json(user);
        }
    });
    
    // Create user
    app.post("/users", (req, res, next) -> {
        var user = req.body(User.class);
        db().insert(user);
        res.status(201).json(user);
    });
    
    app.listen(3000);
}

AI-Powered Endpoint

import static com.roya.Roya.*;

void main() {
    var app = create();
    
    app.plugin(openai());  // Auto-configured from OPENAI_API_KEY
    
    // Simple chat
    app.post("/chat", (req, res, next) -> {
        var answer = ai.llm().ask("You are a helpful assistant", req.body().message());
        res.json(Map.of("answer", answer));
    });
    
    // Structured extraction
    record Product(String name, BigDecimal price, List<String> features) {}
    
    app.post("/extract", (req, res, next) -> {
        var product = ai.llm().extract(Product.class, req.body().description());
        res.json(product);  // Guaranteed valid Product
    });
    
    // RAG (Retrieval-Augmented Generation)
    app.post("/ask", (req, res, next) -> {
        var rag = ai.ragApi().ask(req.body().question());
        res.json(Map.of("answer", rag.answer()));
    });
    
    app.listen(3000);
}

Middleware & Authentication

Qdrant Setup (RAG)

Structured Request Logging

Object Storage: Presigned URLs and Multipart Upload

Presign a GET/PUT URL and upload large files via multipart using the example server ObjectStorageDemo (port 3003):

docker compose up -d minio
./gradlew :roya-examples:run --args="ObjectStorageDemo"

# Presigned PUT URL (JSON body)
curl -s localhost:3003/storage/presign/put \
  -H 'Content-Type: application/json' \
  -d '{"bucket":"media","key":"big.bin","ttlSeconds":600,"contentType":"application/octet-stream"}'

# Presigned GET URL
curl -s "localhost:3003/storage/presign/get?bucket=media&key=big.bin&ttlSeconds=600"

# Multipart upload (base64 body for demo simplicity)
CONTENT_B64=$(echo -n "$(head -c 10485760 /dev/zero | tr '\0' 'A')" | base64) # ~10MB of 'A'
curl -s localhost:3003/storage/multipart/put \
  -H 'Content-Type: application/json' \
  -d "{\"bucket\":\"media\",\"key\":\"big.bin\",\"contentBase64\":\"${CONTENT_B64}\",\"contentType\":\"application/octet-stream\",\"partSizeMb\":5}"

WebSocket

WebSocket registration:

app.ws("/ws/echo", new io.helidon.websocket.WsListener() {
  @Override public void onOpen(io.helidon.websocket.WsSession s) { s.send("connected"); }
  @Override public void onMessage(io.helidon.websocket.WsSession s, String text, boolean last) { s.send("echo: " + text); }
});

At listen(), Roya installs collected WebSocket endpoints using Helidon’s WsRouting. See Helidon docs for details: https://helidon.io/docs/v4/se/websocket

Enable JSON logs for request tracing (Logstash-compatible) using Morgan:

app.use(Morgan.builder()
    .format(Morgan.Format.COMBINED)
    .structured(true)
    .captureRequestIdHeader("X-Request-Id")
    .redactHeaders(Set.of("authorization","cookie","set-cookie"))
    .build());

Fields include: http.method, path, status, duration_ms, remote.ip, request_id, trace/span IDs, and redacted headers. Logs are emitted to stdout for Docker log collectors.

Rate Limiting

Protect endpoints from abuse with IP-based rate limiting:

import com.akilisha.oss.roya.core.middleware.RateLimit;
import java.time.Duration;

// Default: 100 requests per 15 minutes per IP
app.use(RateLimit.rateLimit());

// Custom limit
app.use(RateLimit.builder()
    .max(200)
    .window(Duration.ofMinutes(1))
    .build());

// Custom key function (e.g., rate limit by user ID)
app.use(RateLimit.builder()
    .key(req -> req.get("user").userId())
    .max(50)
    .window(Duration.ofSeconds(60))
    .build());

When the limit is exceeded, requests receive 429 Too Many Requests with Retry-After header and X-RateLimit-* headers.

Roya CLI (MVP)

Quick commands to boost dev ergonomics:

# Help
./gradlew :roya-cli:run --args="--help"

# Scaffold a minimal Roya app
./gradlew :roya-cli:run --args="new my-app --group com.acme"

# Run an example (same as :roya-examples:run with args)
./gradlew :roya-cli:run --args="dev HelloWorld"

# Run any module/class
./gradlew :roya-cli:run --args="run --class com.akilisha.oss.roya.examples.ObjectStorageDemo --module :roya-examples --args \"ObjectStorageDemo\""

# Docker helpers (qdrant|minio|postgres|vault)
./gradlew :roya-cli:run --args="compose up --service qdrant"
./gradlew :roya-cli:run --args="compose down --service qdrant"

# Dry run (print commands without executing)
./gradlew :roya-cli:run --args="--dry-run dev HelloWorld"

Health & Tracing

Roya enables Helidon Health/Tracing when present on the classpath:

RAG requires a running Qdrant instance and an embedding model API key.

Secrets (Vault) and Config

vault.url=http://localhost:8200
vault.token=root
vault.kvMount=secret

In code, SecretsMiddleware.defaults() registers Secrets into services. It auto-detects Vault config and reads from KV v2 (/v1/{mount}/data/{path}), falling back to config-backed secrets at secrets.<path>.<key>.

1) Start Qdrant (docker-compose already includes it):

docker compose up -d qdrant

2) Configure environment:

export QDRANT_URL=http://localhost:6333
export OPENAI_API_KEY=your-api-key

3) Use the unified AI APIs:

ai.vectors().indexPath("kb", Path.of("docs/"), AI.ChunkingOptions.fixed(800, 200));
var rag = ai.ragApi().ask("How do I configure caching?");
import static com.roya.Roya.*;

void main() {
    var app = create();
    
    app.use(cors());
    app.use(json());
    
    // Logging middleware
    app.use((req, res, next) -> {
        System.out.printf("%s %s%n", req.method(), req.path());
        next.handle(req, res);
    });
    
    // Auth middleware
    Handler auth = (req, res, next) -> {
        var token = req.headers().get("Authorization");
        if (token.isEmpty()) {
            res.status(401).json(Map.of("error", "Unauthorized"));
            return;
        }
        
        var user = verifyToken(token.get());
        req.set(CURRENT_USER, user);
        next.handle(req, res);
    };
    
    // Public endpoint
    app.get("/", (req, res, next) -> {
        res.json(Map.of("message", "Public"));
    });
    
    // Protected endpoint
    app.get("/profile", auth, (req, res, next) -> {
        var user = req.get(CURRENT_USER);
        res.json(user);
    });
    
    app.listen(3000);
}

Nested Routers

import static com.roya.Roya.*;

void main() {
    var app = create();
    
    // API router
    var apiRouter = Router.create();
    
    apiRouter.get("/status", (req, res, next) -> {
        res.json(Map.of("status", "ok"));
    });
    
    // Users router
    var usersRouter = Router.create();
    usersRouter.get("/", (req, res, next) -> res.json(getAllUsers()));
    usersRouter.get("/:id", (req, res, next) -> res.json(getUser(req.params().get("id"))));
    usersRouter.post("/", (req, res, next) -> res.status(201).json(createUser(req.body())));
    
    // Mount routers
    apiRouter.use("/users", usersRouter);
    app.use("/api", apiRouter);
    
    // Result: /api/status, /api/users, /api/users/:id
    
    app.listen(3000);
}

Comparison

Express.js vs Roya

Feature Express Roya
API Similarity - ✅ 90% compatible
Lines of Code 100 100
Type Safety ❌ Runtime errors ✅ Compile-time safety
Concurrent Connections ~10K 1M+
Memory Usage 400MB 50MB
Requests/Second 5K 50K
Built-in AI ✅ LLM, RAG, Agents
Cost (AWS, 10K RPS) $730/mo $73/mo

Spring Boot vs Roya

Feature Spring Boot Roya
Learning Curve Steep (annotations, magic) Gentle (Express-like)
Startup Time 2-10 seconds <100ms
Memory Baseline 250MB 50MB
Code Verbosity High Low
AI Integration Bolt-on (Spring AI) Native
Serverless-Ready ⚠️ Slow start ✅ Fast start

Roadmap

✅ Phase 0: Design (COMPLETE - January 2025)

🚧 Phase 1: Prototype (Q1 2025)

📅 Phase 2: MVP (Q2 2025)

📅 Phase 3: Ecosystem (Q3-Q4 2025)

📅 Phase 4: Enterprise (2026)


Architecture

Core Principles

  1. Everything is middleware - Handlers, routers, apps all implement the same interface
  2. Request-scoped by default - Services injected per-request via scoped values
  3. Virtual threads everywhere - No thread pools, no async complexity
  4. AI as a first-class citizen - Not bolted on, built in
  5. Express compatibility - Same mental model, same API patterns

Tech Stack


Contributing

We’re just getting started and would love your help!

How to Contribute

  1. Star this repo ⭐ - Help us gain visibility
  2. Join discussions - Share ideas, use cases, feedback
  3. Code contributions - Once prototype is ready (Q1 2025)
  4. Documentation - Examples, tutorials, guides
  5. Spread the word - Tweet, blog, talk about Roya

Areas We Need Help

See CONTRIBUTING.md for detailed guidelines. (Coming soon)


Community


Inspiration

Roya stands on the shoulders of giants:

We’re not reinventing the wheel. We’re building the wheel that 2025 needs.


FAQ

Why “Roya”?

Roya (رویا) is Persian for “dream” - fitting for an ambitious vision to unite Express’s elegance with Java’s power.

Is this production-ready?

Not yet. We’re in the design/prototype phase (Q1 2025). Follow the repo for updates.

How can I migrate from Express?

Migration guide coming in Q2 2025. Expect 90% code compatibility - mostly syntax changes (const → var, arrow functions, etc.)

How can I migrate from Spring Boot?

Migration path coming in Q2 2025. Focus will be on greenfield microservices first, then gradual migration strategies.

Will this support GraalVM native compilation?

Yes, planned for Phase 4 (2026). Fast startup + small binary = perfect for serverless.

What about Kotlin?

Absolutely. Roya is designed for modern JVM languages. Kotlin’s concise syntax will be a great fit.

Can I use this with existing Java libraries?

Yes. Roya is just Java. Any JVM library works. JDBC, Jackson, etc. all compatible.

How do I handle database connections?

Built-in database plugin (JOOQ-based) manages connection pooling, virtual thread integration, and provides a clean query API.

What about transactions?

Planned for Phase 2. Simple API: db().transaction(tx -> { ... })

Is there support for WebSockets?

Planned for Phase 2. Express-compatible API: app.ws('/chat', handler)

What AI providers are supported?

Phase 1: OpenAI
Phase 2: Anthropic, Cohere
Phase 3: Local models (Ollama, llama.cpp), Azure OpenAI, AWS Bedrock

How much does it cost?

Free and open source (Apache 2.0 license). Commercial support and managed hosting coming later.


License

Apache License 2.0

Roya is free and open source. Use it in commercial projects, modify it, distribute it. Just give credit and don’t sue us. 😊

See LICENSE for full details.


Status

Current Status: 🟡 Design & Architecture Complete

Next Milestone: 🔵 Prototype (Q1 2025)

Looking for: Contributors, feedback, early adopters, sponsors


Support This Project

If you believe in this vision:


Built with ❤️ by developers who love Express but need Java’s power.

Let’s make Java web development fun again.


Last updated: January 2025