Skip to content

Service

This content is for the 0.6.3 version. Switch to the latest version for up-to-date documentation.

The Service component provides the foundation for building HTTP services in Raindrop applications. Services handle incoming HTTP requests and can communicate with other services through type-safe service-to-service calls. Every Service must implement a fetch method, even when used exclusively for internal service-to-service communication.

Services support three visibility levels that control how they can be accessed. Public services receive automatic URL assignment for external access. Protected services require authentication for access. Private services can only be accessed through service-to-service calls from other services within the same application.

Service-to-service communication happens through the environment bindings, allowing services to call methods on other services using env.SERVICENAME.methodName(). These calls must be awaited and support passing structured clonable objects including Blobs, ArrayBuffers, and Request objects. Each service receives its own execution context, enabling independent background task management.

Prerequisites

  • Understanding of HTTP request/response patterns
  • Familiarity with async/await JavaScript
  • Knowledge of TypeScript generics for type-safe environment bindings
  • Basic understanding of service environments

Creating

Define services in your raindrop.manifest file with the appropriate visibility level:

application "my-app" {
service "api-gateway" {
visibility = "public"
}
service "business-logic" {
visibility = "private"
}
service "admin-api" {
visibility = "protected"
}
}

Accessing

Import the Service class and extend it to create your service implementation:

import { Service } from '@liquidmetal-ai/raindrop-framework';
import { Env } from './raindrop.gen';
export default class extends Service<Env> {
async fetch(request: Request): Promise<Response> {
// Handle HTTP requests
const result = await this.env.BUSINESS_LOGIC.processData({ id: 123 });
return new Response(JSON.stringify(result));
}
}

Core Concepts

Main Interfaces

  • Service<Env> - Base class for all HTTP service implementations
  • ServiceStub<T> - Type-safe stub for service-to-service communication
  • ExecutionContext - Runtime context for managing background tasks
  • Env - Generated environment type containing service bindings

Core Data Types

Service Class

abstract class Service<Env> {
ctx: ExecutionContext; // Runtime context for background tasks
env: Env; // Environment bindings including other services
constructor(ctx: ExecutionContext, env: Env);
}

ServiceStub Type

// Converts service methods to async Promise-based calls
type ServiceStub<T extends Service<Env>, Env = unknown> = Stub<T>;

ExecutionContext Type

type ExecutionContext = {
waitUntil(promise: Promise<any>): void; // Register background tasks
};

System Limits

  • Service-to-service calls accumulate execution time as they must be awaited
  • Each service receives independent execution context
  • Structured clonable objects required for inter-service data passing

fetch Method

Example

Implementing the required fetch handler for HTTP requests:

import { Service } from '@liquidmetal-ai/raindrop-framework';
import { Env } from './raindrop.gen';
export default class ApiGateway extends Service<Env> {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
// Route to appropriate handler
if (url.pathname === '/users') {
// Call private service for business logic
const users = await this.env.USER_SERVICE.getAllUsers();
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' }
});
}
return new Response('Not Found', { status: 404 });
}
}
// HTTP Request object
request: Request

Service-to-Service Communication

Example

Calling methods on other services through environment bindings:

import { Service } from '@liquidmetal-ai/raindrop-framework';
import { Env } from './raindrop.gen';
export default class PublicAPI extends Service<Env> {
async fetch(request: Request): Promise<Response> {
const { searchParams } = new URL(request.url);
const userId = searchParams.get('userId');
// Call private service method - must await
const userData = await this.env.USER_SERVICE.getUserById(userId);
const orders = await this.env.ORDER_SERVICE.getOrdersByUser(userId);
return new Response(JSON.stringify({ user: userData, orders }));
}
}
// Private service implementation
export class UserService extends Service<Env> {
// Required fetch implementation even for private services
async fetch(request: Request): Promise<Response> {
return new Response('Not Implemented', { status: 501 });
}
async getUserById(userId: string) {
// Business logic here
return { id: userId, name: 'John Doe' };
}
}
// Service method parameters
userId: string // Can be any structured clonable type

Background Tasks with ExecutionContext

Example

Using execution context for background operations:

import { Service } from '@liquidmetal-ai/raindrop-framework';
import { Env } from './raindrop.gen';
export default class DataProcessor extends Service<Env> {
async fetch(request: Request): Promise<Response> {
const data = await request.json();
// Register background task - won't block response
this.ctx.waitUntil(this.processInBackground(data));
return new Response('Processing started', { status: 202 });
}
private async processInBackground(data: any) {
// Long-running operation
await this.env.ANALYTICS_SERVICE.analyze(data);
await this.env.NOTIFICATION_SERVICE.sendComplete(data.id);
}
}
// Promise to execute in background
promise: Promise<any>