Skip to main content

Nexus Go Quickstart

Build a Nexus Service that wraps an existing Temporal Workflow, then invoke it from a caller Workflow.

WHAT IS NEXUS?

Temporal Nexus connects Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. Learn more in the Nexus encyclopedia.

Prerequisites: Complete the Go SDK Quickstart first. You should have activity.go, workflow.go, worker/main.go, and start/main.go from that guide.

Then install the Nexus Go SDK:

go get github.com/nexus-rpc/sdk-go

What you'll build

You have SayHelloWorkflow running in the default Namespace. By the end of this guide:

  1. A Nexus Service will expose SayHelloWorkflow as an Operation.
  2. A second Namespace will contain a Workflow that calls that Operation.
  3. The caller Workflow will get back "Hello Temporal" — the same result, but across Namespaces.

1. Define Nexus Service

Create a file called service.go that defines the Nexus Service contract.

Creating a Nexus Service establishes the contract between your implementation and any callers. It provides type safety when invoking Nexus Operations and ensures that Operation Handlers fulfill the contract.

HelloServiceName and HelloOperationName are string constants that uniquely identify the Service and Operation. HelloInput defines the input type for the Operation. SayHelloWorkflow returns string, so the Operation output type is also string.

USE AN IDL TO GENERATE SERVICE DEFINITIONS

You can also define your Nexus Service contract using an IDL and generate the Go service definitions with nexus-rpc-gen.

package greeting

const (
HelloServiceName = "my-hello-service"
HelloOperationName = "say-hello"
)

type HelloInput struct {
Name string
}

2. Define Operation Handler

Create a file called nexus_handler.go that implements the Nexus Operation handler.

Operation handlers contain the logic that runs when a caller invokes a Nexus Operation.

HelloNexusWorkflow acts as the handler Workflow. It bridges the Nexus HelloInput to SayHelloWorkflow's string parameter by extracting input.Name.

temporalnexus.NewWorkflowRunOperation creates an asynchronous Nexus Operation that starts HelloNexusWorkflow when invoked. The Options function returns client.StartWorkflowOptions, including a stable Workflow ID derived from options.RequestID.

package greeting

import (
"context"

"github.com/nexus-rpc/sdk-go/nexus"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporalnexus"
"go.temporal.io/sdk/workflow"
)

// HelloNexusWorkflow is the handler Workflow for the Nexus SayHello Operation.
// It bridges the Nexus HelloInput to SayHelloWorkflow's string parameter.
func HelloNexusWorkflow(ctx workflow.Context, input HelloInput) (string, error) {
return SayHelloWorkflow(ctx, input.Name)
}

var HelloOperation = temporalnexus.NewWorkflowRunOperation(
HelloOperationName,
HelloNexusWorkflow,
func(ctx context.Context, input HelloInput, options nexus.StartOperationOptions) (client.StartWorkflowOptions, error) {
return client.StartWorkflowOptions{
// RequestID is stable across retries, making it safe to use as a Workflow ID.
ID: options.RequestID,
}, nil
},
)

3. Register Nexus Service Handler in a Worker

Update your existing worker/main.go to register the Nexus Service Handler.

This Worker runs in the default Namespace — the same Namespace where SayHelloWorkflow is already registered.

A Worker will only poll for and process incoming Nexus requests if the Nexus Service Handlers are registered. This is the same Worker concept used for Workflows and Activities.

nexus.NewService creates a named Nexus Service. service.Register adds the HelloOperation to the Service. w.RegisterNexusService registers the Service with the Worker so it can receive Nexus Operation requests. HelloNexusWorkflow must also be registered so the Worker can execute it as the handler Workflow.

package main

import (
"log"

"my-org/greeting"

"github.com/nexus-rpc/sdk-go/nexus"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
)

func main() {
// Empty Options defaults to the "default" Namespace.
c, err := client.Dial(client.Options{})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()

w := worker.New(c, "my-task-queue", worker.Options{})
w.RegisterWorkflow(greeting.SayHelloWorkflow)
w.RegisterWorkflow(greeting.HelloNexusWorkflow)
w.RegisterActivity(greeting.Greet)

service := nexus.NewService(greeting.HelloServiceName)
err = service.Register(greeting.HelloOperation)
if err != nil {
log.Fatalln("Unable to register operations", err)
}
w.RegisterNexusService(service)

err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("Unable to start worker", err)
}
}

4. Caller Workflow

Create a file called caller_workflow.go that defines a Workflow which invokes the Nexus Operation.

The caller Workflow demonstrates the consumer side of Nexus. Instead of importing handler code directly, the caller only depends on the Service contract. This keeps the caller and handler decoupled so they can live in separate Namespaces, repositories, or even teams.

workflow.NewNexusClient creates a client bound to your Nexus Service and Endpoint. ExecuteOperation starts the Operation and returns a future. fut.Get blocks until the Operation completes and writes the result into result.

package greeting

import (
"time"

"go.temporal.io/sdk/workflow"
)

const (
CallerTaskQueue = "my-caller-task-queue"
NexusEndpoint = "my-nexus-endpoint-name"
)

func CallerWorkflow(ctx workflow.Context, name string) (string, error) {
c := workflow.NewNexusClient(NexusEndpoint, HelloServiceName)

fut := c.ExecuteOperation(ctx, HelloOperationName, HelloInput{Name: name}, workflow.NexusOperationOptions{
ScheduleToCloseTimeout: 10 * time.Second,
})

var result string
if err := fut.Get(ctx, &result); err != nil {
return "", err
}
return result, nil
}

5. Create Caller Namespace and Nexus Endpoint

Before running the application, create a caller Namespace and a Nexus Endpoint to route requests from the caller to the handler. The handler uses the default Namespace that was created when you started the dev server.

Namespaces provide isolation between the caller and handler sides. The Nexus Endpoint acts as a routing layer that connects the caller Namespace to the handler's target Namespace and Task Queue. The endpoint name must match the NexusEndpoint constant defined in caller_workflow.go from step 4.

Make sure your local Temporal dev server is running (temporal server start-dev).

temporal operator namespace create --namespace my-caller-namespace
temporal operator nexus endpoint create \
--name my-nexus-endpoint-name \
--target-namespace default \
--target-task-queue my-task-queue

6. Run and Verify

Create caller/main.go to start the caller Worker and execute the Workflow.

This brings everything together: the caller Worker hosts CallerWorkflow, which uses the Nexus client to invoke say-hello on the handler side. The full request flows from the caller Workflow, through the Nexus Endpoint, to the handler Worker running HelloNexusWorkflow (which calls SayHelloWorkflow), and back to the caller.

Run the application:

  1. Start the handler Worker in one terminal:
go run worker/main.go
  1. Run the caller in another terminal:
go run caller/main.go

You should see:

Workflow result: Hello Temporal

Open the Temporal Web UI and switch between Namespaces to see both Workflow Executions. In my-caller-namespace, find the CallerWorkflow execution — you should see NexusOperationScheduled, NexusOperationStarted, and NexusOperationCompleted events in its history. In default, find the HelloNexusWorkflow execution that was started by the Nexus Operation.

package main

import (
"context"
"log"

"my-org/greeting"

"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
)

func main() {
c, err := client.Dial(client.Options{
Namespace: "my-caller-namespace",
})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()

w := worker.New(c, greeting.CallerTaskQueue, worker.Options{})
w.RegisterWorkflow(greeting.CallerWorkflow)

if err := w.Start(); err != nil {
log.Fatalln("Unable to start worker", err)
}
defer w.Stop()

wr, err := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{
ID: "nexus-caller-workflow",
TaskQueue: greeting.CallerTaskQueue,
}, greeting.CallerWorkflow, "Temporal")
if err != nil {
log.Fatalln("Unable to execute workflow", err)
}

var result string
if err := wr.Get(context.Background(), &result); err != nil {
log.Fatalln("Unable to get workflow result", err)
}
log.Println("Workflow result:", result)
}

Next Steps

Now that you have a working Nexus Service, here are some resources to deepen your understanding:

  • Go Nexus Feature Guide: Covers synchronous and asynchronous Operations, error handling, cancellation, and cross-Namespace calls.
  • Nexus Operations: The full Operation lifecycle, including retries, timeouts, and execution semantics.
  • Nexus Services: Designing Service contracts and registering multiple Services per Worker.
  • Nexus Patterns: Comparing the collocated and router-queue deployment patterns.
  • Error Handling in Nexus: Handling retryable and non-retryable errors across caller and handler boundaries.
  • Execution Debugging: Bi-directional linking and OpenTelemetry tracing for debugging Nexus calls.
  • Nexus Endpoints: Managing Endpoints and understanding how they route requests.
  • Temporal Nexus on Temporal Cloud: Deploying Nexus in a production Temporal Cloud environment with built-in access controls and multi-region connectivity.