Skip to main content
Nanoservices are workerd’s approach to application architecture: split your application into components that are decoupled and independently deployable like microservices, but with the performance of a local function call.

How nanoservices work

When one nanoservice calls another, the callee runs in the same thread and process. This provides:
  • Microservice isolation: Components are decoupled and independently deployable
  • Local performance: No network overhead between services
  • Capability-based security: Services connect through explicit bindings rather than global namespaces

Deployment model

Instead of deploying different microservices to different machines in your cluster, deploy all your nanoservices to every machine in the cluster. This approach:
  • Simplifies load balancing significantly
  • Eliminates network hops between services
  • Maintains service isolation and independent deployment

Configuration

Nanoservices are defined in your workerd config file using service bindings. Here’s an example:
using Workerd = import "/workerd/workerd.capnp";

const config :Workerd.Config = (
  services = [
    (name = "main", worker = .mainWorker),
    (name = "auth", worker = .authWorker),
    (name = "storage", worker = .storageWorker),
  ],

  sockets = [
    ( name = "http",
      address = "*:8080",
      http = (),
      service = "main"
    ),
  ]
);

const mainWorker :Workerd.Worker = (
  serviceWorkerScript = embed "main.js",
  compatibilityDate = "2024-01-01",
  bindings = [
    (name = "AUTH", service = "auth"),
    (name = "STORAGE", service = "storage"),
  ],
);

const authWorker :Workerd.Worker = (
  serviceWorkerScript = embed "auth.js",
  compatibilityDate = "2024-01-01",
);

const storageWorker :Workerd.Worker = (
  serviceWorkerScript = embed "storage.js",
  compatibilityDate = "2024-01-01",
);

Using service bindings

In your main worker, call other services using the fetch() API:
export default {
  async fetch(request, env) {
    // Call the auth service
    const authResponse = await env.AUTH.fetch(
      new Request("https://fake-host/validate", {
        method: "POST",
        body: request.headers.get("Authorization")
      })
    );

    if (!authResponse.ok) {
      return new Response("Unauthorized", { status: 401 });
    }

    // Call the storage service
    const data = await env.STORAGE.fetch(
      "https://fake-host/data"
    );

    return data;
  }
};
The URL passed to fetch() is used for routing within the service but doesn’t result in an actual network request.

Capability bindings

workerd uses capability-based security instead of global namespaces to connect services. Each service explicitly declares its dependencies through bindings in the config file. This approach:
  • Makes code more composable
  • Prevents SSRF (Server-Side Request Forgery) attacks
  • Provides clear dependency graphs
  • Enables fine-grained access control

Benefits

Performance

In-process calls eliminate network latency between services

Isolation

Each service runs in its own JavaScript isolate with separate memory

Security

Capability-based bindings prevent unauthorized access

Simplicity

Homogeneous deployment simplifies infrastructure

Comparison to microservices

AspectMicroservicesNanoservices
DeploymentDifferent services on different machinesAll services on every machine
CommunicationNetwork calls (HTTP/gRPC)In-process function calls
LatencyMilliseconds (network overhead)Microseconds (local call)
Load balancingComplex (service discovery required)Simple (every machine has all services)
IsolationProcess/container boundariesV8 isolate boundaries
Resource usageHigher (separate processes)Lower (shared process)

Best practices

  1. Keep services focused: Each nanoservice should have a single, well-defined responsibility
  2. Use explicit bindings: Declare all service dependencies in your config file
  3. Version carefully: Changes to service interfaces may require coordinated deployments
  4. Monitor per-service: Track metrics for each nanoservice independently
  5. Test in isolation: Each service should have its own test suite

Reference

For complete configuration options, see the workerd.capnp schema.

Build docs developers (and LLMs) love