Skip to content

HTTP Ingress Handlers

HTTP ingress rules map an HTTP method + URL path to a JavaScript script. When a matching request arrives, DieselEngine creates a fresh GraalVM context, evaluates your script as an ES module, and calls the exported handleRequest function.

How It Works

  1. A request arrives at a URL matching an ingress rule (e.g., GET /api/items)
  2. The engine loads the linked script and creates a new GraalVM context
  3. All global bindings (diesel, Results, okHttpClient, etc.) are injected
  4. The script's handleRequest(context) export is called
  5. The returned IngressResult is sent as the HTTP response
  6. The GraalVM context is closed

INFO

Each HTTP request gets its own isolated GraalVM context. There is no shared state between requests unless you use the database, Redis, or the registry.

Ingress Types

TypeHTTP MethodUse Case
HTTP_GETGETRead operations, queries
HTTP_POSTPOSTCreate/update operations
HTTP_DELETEDELETEDelete operations

Basic Handler

Every HTTP ingress script must export a handleRequest function that receives a RequestContext and returns a Results chain:

javascript
export function handleRequest(context) {
  return Results.json().renderRaw(JSON.stringify({
    message: "Hello from DieselEngine!",
    timestamp: Date.now()
  }));
}

URL Path

All HTTP ingress routes are served behind the /backend path prefix (added by the Caddy reverse proxy in the standard Docker setup). So an ingress with route /api/items is accessible at:

http://your-host/backend/api/items

Reading Parameters

Query Parameters (GET)

javascript
export function handleRequest(context) {
  const page = context.getParameterAsInteger("page") || 1;
  const limit = context.getParameterAsInteger("limit") || 20;
  const search = context.getParameter("q") || "";

  const items = ItemService.findItems(search, page, limit);
  return Results.json().renderRaw(JSON.stringify({ data: items, page, limit }));
}

Request Body (POST)

javascript
export function handleRequest(context) {
  const body = JSON.parse(context.getBody());
  const { name, email, role } = body;

  if (!name || !email) {
    return Results.badRequest().json().renderRaw(
      JSON.stringify({ error: "name and email are required" })
    );
  }

  const id = UserService.createUser({ name, email, role });
  return Results.created("/api/users/" + id)
    .json()
    .renderRaw(JSON.stringify({ id }));
}

Path-Based Routing

DieselEngine routes are exact matches. To handle paths like /api/items/123, use query parameters (/api/items?id=123) or parse the path manually:

javascript
export function handleRequest(context) {
  const path = context.getRequestPath();
  const segments = path.split("/");
  const id = segments[segments.length - 1];
  // ...
}

The Delegation Pattern

Keep handlers thin. Extract parameters, delegate to a service class, and format the response:

javascript
import { ItemService } from '../../services/ItemService.js';

export function handleRequest(context) {
  const id = context.getParameterAsInteger("id");

  if (!id) {
    return Results.badRequest().json().renderRaw(
      JSON.stringify({ error: "Missing 'id' parameter" })
    );
  }

  const item = ItemService.getItemById(id);

  if (!item) {
    return Results.notFound().json().renderRaw(
      JSON.stringify({ error: "Item not found" })
    );
  }

  return Results.json().renderRaw(JSON.stringify(item));
}

The service class encapsulates all database access:

javascript
// /services/ItemService.js
export class ItemService {
  static getItemById(id) {
    const ps = diesel.prepareStatement(
      'SELECT id, data, created_at FROM engine."items" WHERE id = ? AND deleted = false'
    );
    ps.setLong(1, id);
    const rs = ps.executeQuery();
    if (!rs.next()) return null;
    return {
      id: rs.getLong("id"),
      ...JSON.parse(rs.getString("data")),
      created_at: rs.getString("created_at")
    };
  }
}

Auth Patterns

Bearer Token

javascript
export function handleRequest(context) {
  const auth = context.getHeader("Authorization");
  if (!auth || !auth.startsWith("Bearer ")) {
    return Results.unauthorized().json().renderRaw(
      JSON.stringify({ error: "Missing or invalid token" })
    );
  }

  const token = auth.substring(7);
  const user = AuthService.validateToken(token);
  if (!user) {
    return Results.forbidden().json().renderRaw(
      JSON.stringify({ error: "Invalid token" })
    );
  }

  // Proceed with authenticated user...
  return Results.json().renderRaw(JSON.stringify({ user }));
}
javascript
export function handleRequest(context) {
  const cookie = context.getHeader("Cookie");
  const sessionId = parseCookie(cookie, "session_id");
  // ...
}

File Uploads

Handle multipart file uploads using the file item iterator:

javascript
export function handleRequest(context) {
  const iterator = context.getFileItemIterator();

  while (iterator.hasNext()) {
    const item = iterator.next();
    if (!item.isFormField()) {
      const fieldName = item.getFieldName();
      const fileName = item.getName();
      const stream = item.openStream();
      // Upload to MinIO, process, etc.
    }
  }

  return Results.json().renderRaw(JSON.stringify({ uploaded: true }));
}

Error Handling

Script exceptions are caught by the engine. If your handler throws, the engine returns HTTP 500 and pushes the stack trace to the console. For controlled error responses, use try/catch:

javascript
export function handleRequest(context) {
  try {
    const data = JSON.parse(context.getBody());
    const result = processData(data);
    return Results.json().renderRaw(JSON.stringify(result));
  } catch (e) {
    console.error("Handler error:", e.message);
    return Results.internalServerError().json().renderRaw(
      JSON.stringify({ error: "Internal error", detail: e.message })
    );
  }
}

Response Patterns

Redirect

javascript
return Results.redirect("/dashboard");           // 303 See Other
return Results.redirectTemporary("/maintenance"); // 307 Temporary Redirect

No Content

javascript
// DELETE handler
return Results.noContent();  // 204

Custom Headers

javascript
return Results.json()
  .addHeader("X-Total-Count", String(total))
  .addHeader("Cache-Control", "public, max-age=300")
  .renderRaw(JSON.stringify(data));

Non-JSON Content

javascript
// CSV export
return Results.ok()
  .contentType("text/csv")
  .addHeader("Content-Disposition", "attachment; filename=export.csv")
  .renderRaw(csvContent);

// HTML page
return Results.html().render(htmlString);

// Plain text
return Results.text().renderRaw("OK");

DieselEngine Scripting Documentation