Tutorial: Your First Job in Go
This tutorial walks you through building a background job system with the Go SDK. You will enqueue, process, and monitor a job using Go — no JavaScript required.
Prerequisites
Section titled “Prerequisites”Step 1: Start the OJS server
Section titled “Step 1: Start the OJS server”If you haven’t already, create a docker-compose.yml:
services: redis: image: redis:7-alpine ports: - "6379:6379"
ojs-server: image: ghcr.io/openjobspec/ojs-backend-redis:latest ports: - "8080:8080" environment: REDIS_URL: redis://redis:6379 depends_on: - redisdocker compose up -dStep 2: Initialize the project
Section titled “Step 2: Initialize the project”mkdir ojs-go-tutorial && cd ojs-go-tutorialgo mod init ojs-go-tutorialgo get github.com/openjobspec/ojs-go-sdkStep 3: Enqueue a job
Section titled “Step 3: Enqueue a job”Create main.go:
package main
import ( "context" "fmt" "log"
"github.com/openjobspec/ojs-go-sdk/client")
func main() { ctx := context.Background()
// Create a client pointing to the OJS server c, err := client.New("http://localhost:8080") if err != nil { log.Fatal(err) }
// Enqueue a job of type "email.send" on the "default" queue job, err := c.Enqueue(ctx, "email.send", client.WithArgs("user@example.com", "welcome"), client.WithQueue("default"), ) if err != nil { log.Fatal(err) }
fmt.Printf("Enqueued job %s in state: %s\n", job.ID, job.State)}Run it:
go run main.goYou should see:
Enqueued job 019461a8-1a2b-7c3d-8e4f-5a6b7c8d9e0f in state: availableStep 4: Build a worker
Section titled “Step 4: Build a worker”Create worker/main.go:
package main
import ( "context" "fmt" "log" "os" "os/signal" "syscall"
"github.com/openjobspec/ojs-go-sdk/worker")
func main() { // Create a worker that polls the "default" queue w, err := worker.New("http://localhost:8080", worker.WithQueues("default"), worker.WithConcurrency(5), ) if err != nil { log.Fatal(err) }
// Register a handler for "email.send" jobs w.Handle("email.send", func(ctx worker.JobContext) error { to := ctx.Args[0].(string) template := ctx.Args[1].(string) fmt.Printf("Sending %q email to %s\n", template, to)
// Your email logic goes here return nil })
// Graceful shutdown on Ctrl+C stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) go func() { <-stop fmt.Println("\nShutting down worker...") w.Stop(context.Background()) }()
fmt.Println("Worker started, waiting for jobs...") if err := w.Start(context.Background()); err != nil { log.Fatal(err) }}Run the worker:
go run worker/main.goOutput:
Worker started, waiting for jobs...Sending "welcome" email to user@example.comStep 5: Add retry logic
Section titled “Step 5: Add retry logic”Modify the enqueue call to add a retry policy:
job, err := c.Enqueue(ctx, "email.send", client.WithArgs("user@example.com", "welcome"), client.WithQueue("default"), client.WithRetry(client.RetryPolicy{ MaxAttempts: 5, Backoff: "exponential", }),)If the worker handler returns an error, the job transitions to retryable and is automatically rescheduled with exponential backoff.
Step 6: Add middleware
Section titled “Step 6: Add middleware”Add logging and recovery middleware to the worker:
import "github.com/openjobspec/ojs-go-sdk/middleware"
w, err := worker.New("http://localhost:8080", worker.WithQueues("default"), worker.WithConcurrency(5), worker.WithMiddleware( middleware.Recovery(), middleware.Logging(slog.Default()), ),)This logs every job execution and recovers from panics in handlers.
What you built
Section titled “What you built”- A Go client that enqueues jobs to an OJS server
- A Go worker that processes jobs with concurrency and graceful shutdown
- Retry policies for automatic failure recovery
- Middleware for cross-cutting concerns
Next steps
Section titled “Next steps”- Add workflow orchestration with chain, group, and batch
- Explore scheduled jobs for delayed and cron execution
- See the Go SDK reference for the full API