blwa

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package blwa provides a batteries-included framework for building HTTP services on AWS Lambda Web Adapter (LWA).

Overview

blwa handles the boilerplate of setting up an HTTP server optimized for Lambda: environment parsing, structured logging, OpenTelemetry tracing, AWS SDK clients, and graceful shutdown. A complete application can be created in a single call:

blwa.NewApp[Env](func(m *blwa.Mux, h *Handlers) {
    m.HandleFunc("GET /items", h.ListItems)
    m.HandleFunc("GET /items/{id}", h.GetItem, "get-item")
},
    blwa.WithAWSClient(dynamodb.NewFromConfig),
    blwa.WithFx(fx.Provide(NewHandlers)),
).Run()

Environment Configuration

Define your environment by embedding BaseEnvironment:

type Env struct {
    blwa.BaseEnvironment
    MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

BaseEnvironment provides the following environment variables:

| Variable                      | Required | Default | Description                                          |
|-------------------------------|----------|---------|------------------------------------------------------|
| AWS_LWA_PORT                  | Yes      | -       | Port the HTTP server listens on                      |
| AWS_LWA_READINESS_CHECK_PATH  | Yes      | -       | Health check endpoint path for LWA readiness         |
| AWS_REGION                    | Yes      | -       | AWS region (set automatically by Lambda runtime)     |
| BW_SERVICE_NAME               | Yes      | -       | Service name for logging and tracing                 |
| BW_PRIMARY_REGION             | Yes      | -       | Primary deployment region (injected by CDK)          |
| BW_LOG_LEVEL                  | No       | info    | Log level (debug, info, warn, error)                 |
| BW_OTEL_EXPORTER              | No       | stdout  | Trace exporter: "stdout" or "xrayudp"                |
| BW_GATEWAY_ACCESS_LOG_GROUP   | No       | -       | API Gateway access log group for X-Ray correlation   |

The AWS_LWA_* variables match the official Lambda Web Adapter configuration, so values you set for LWA are automatically picked up by blwa. AWS_REGION is set automatically by the Lambda runtime, while BW_PRIMARY_REGION is injected by the bwcdklwalambda CDK construct.

Runtime

Runtime provides access to app-scoped dependencies and should be injected into handler constructors via fx. This follows idiomatic Go patterns where app-level dependencies are passed explicitly, not pulled from context.

Runtime provides:

Example handler struct with Runtime:

type Handlers struct {
    rt     *blwa.Runtime[Env]
    dynamo *dynamodb.Client
}

func NewHandlers(rt *blwa.Runtime[Env], dynamo *dynamodb.Client) *Handlers {
    return &Handlers{rt: rt, dynamo: dynamo}
}

func (h *Handlers) GetItem(ctx context.Context, w bhttp.ResponseWriter, r *http.Request) error {
    env := h.rt.Env()                      // typed environment
    url, _ := h.rt.Reverse("get-item", id) // URL generation
    h.dynamo.GetItem(ctx, ...)             // direct client access
    // ...
}

Secrets

Runtime.Secret retrieves secrets from AWS Secrets Manager with caching. Secrets are fetched per-request to support rotation without redeployment.

// Raw string secret
apiKey, err := h.rt.Secret(ctx, "my-api-key-secret")

// JSON secret with nested path extraction (uses gjson syntax)
// e.g., secret contains: {"database": {"host": "...", "password": "secret123"}}
password, err := h.rt.Secret(ctx, "my-db-credentials", "database.password")

Context

Handlers receive *Context, which embeds context.Context and provides method access to request-scoped values. Both method syntax and package functions are supported:

func (h *Handlers) GetItem(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
    // Method syntax (preferred)
    ctx.Log().Info("fetching item")
    ctx.Span().AddEvent("fetching item")
    if lwa := ctx.LWA(); lwa != nil {
        // running in Lambda
    }

    // Package function syntax (equivalent)
    blwa.Log(ctx).Info("fetching item")
    blwa.Span(ctx).AddEvent("fetching item")

    env := h.rt.Env() // from Runtime, not context
    // ...
}

Available methods and functions:

Tracing

OpenTelemetry tracing is configured automatically based on BW_OTEL_EXPORTER:

  • "stdout" (default): Pretty-printed spans for local development
  • "xrayudp": X-Ray UDP exporter for Lambda with proper trace ID format

The tracer provider and propagator are injected explicitly (no globals), allowing for proper testing and isolation.

When BW_GATEWAY_ACCESS_LOG_GROUP is set (injected automatically by bwcdkrestgateway), the log group is added to trace segments via the aws.log.group.names resource attribute. This enables X-Ray's "View Logs" feature to query API Gateway access logs alongside Lambda function logs.

AWS Clients

AWS SDK v2 clients are registered with WithAWSClient and injected directly into handler constructors via fx. This eliminates reflection and makes dependencies explicit in the type system.

Local Region Clients (Default)

For clients that should use the Lambda's local region (AWS_REGION), register the client factory directly. The client type is injected as-is:

// Registration
blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
    return dynamodb.NewFromConfig(cfg)
})

// Injection - receives *dynamodb.Client directly
func NewHandlers(dynamo *dynamodb.Client) *Handlers {
    return &Handlers{dynamo: dynamo}
}

Primary Region Clients

For clients that must target the primary deployment region (BW_PRIMARY_REGION), wrap the client with Primary to make the region explicit in the type:

// Registration
blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

// Injection - receives *blwa.Primary[ssm.Client]
func NewHandlers(ssm *blwa.Primary[ssm.Client]) *Handlers {
    return &Handlers{ssm: ssm}
}

// Usage - access via .Client field
h.ssm.Client.GetParameter(ctx, ...)

Common use cases for primary region clients:

  • Generating S3 presigned URLs that work across all regions
  • Publishing to centralized SQS queues or SNS topics
  • Accessing primary-region-only resources (e.g., certain AWS services)

Fixed Region Clients

For clients that must target a specific region, wrap with InRegion:

// Registration
blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[s3.Client] {
    return blwa.NewInRegion(s3.NewFromConfig(cfg), "eu-central-1")
}, blwa.ForRegion("eu-central-1"))

// Injection - receives *blwa.InRegion[s3.Client]
func NewHandlers(s3 *blwa.InRegion[s3.Client]) *Handlers {
    return &Handlers{s3: s3}
}

// Usage - access client and region via fields
h.s3.Client.PutObject(ctx, ...)
log.Info("uploading", zap.String("region", h.s3.Region))

Common use cases for fixed region clients:

  • Accessing S3 buckets in specific regions
  • Targeting SQS queues in particular regions
  • Cross-region replication operations

Multiple Region Types Together

A handler can inject clients for different regions simultaneously:

type Handlers struct {
    dynamo *dynamodb.Client               // local region
    ssm    *blwa.Primary[ssm.Client]     // primary region
    s3     *blwa.InRegion[s3.Client]     // fixed region
}

func NewHandlers(
    dynamo *dynamodb.Client,
    ssm *blwa.Primary[ssm.Client],
    s3 *blwa.InRegion[s3.Client],
) *Handlers {
    return &Handlers{dynamo: dynamo, ssm: ssm, s3: s3}
}

Health Checks

A health endpoint is automatically registered at AWS_LWA_READINESS_CHECK_PATH (required env var). Lambda Web Adapter uses this to determine readiness. Customize with WithHealthHandler.

Dependency Injection

blwa uses go.uber.org/fx for dependency injection. Add custom providers with WithFx:

blwa.WithFx(
    fx.Provide(NewHandlers),
    fx.Provide(NewRepository),
)
Example

Example demonstrates a complete blwa application with local region AWS clients. AWS clients are injected directly into handler constructors via fx.

package main

import (
	"encoding/json"
	"net/http"

	"github.com/advdv/bhttp"
	"github.com/advdv/bhttp/blwa"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

// Env defines the environment variables for the application.
// Embed blwa.BaseEnvironment to get the required LWA fields.
type Env struct {
	blwa.BaseEnvironment
	MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

// ItemHandlers contains the HTTP handlers for item operations.
// Dependencies are injected via the constructor, including AWS clients.
type ItemHandlers struct {
	rt     *blwa.Runtime[Env]
	dynamo *dynamodb.Client
}

func NewItemHandlers(rt *blwa.Runtime[Env], dynamo *dynamodb.Client) *ItemHandlers {
	return &ItemHandlers{rt: rt, dynamo: dynamo}
}

// ListItems returns all items from the database.
// Demonstrates: ctx.Log() method for trace-correlated logging, Runtime.Env for configuration access.
func (h *ItemHandlers) ListItems(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
	env := h.rt.Env()

	ctx.Log().Info("listing items from table",
		zap.String("table", env.MainTableName))

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]any{
		"table": env.MainTableName,
		"items": []string{"item-1", "item-2"},
	})
}

// GetItem returns a single item by ID.
// Demonstrates: ctx.Span() method for adding trace events, Runtime.Reverse for URL generation.
func (h *ItemHandlers) GetItem(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
	id := r.PathValue("id")

	ctx.Span().AddEvent("fetching item")

	selfURL, _ := h.rt.Reverse("get-item", id)

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]any{
		"id":   id,
		"self": selfURL,
	})
}

// CreateItem creates a new item in DynamoDB.
// Demonstrates: Direct AWS client injection, ctx.LWA() for Lambda context.
func (h *ItemHandlers) CreateItem(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {

	if lwa := ctx.LWA(); lwa != nil {
		ctx.Log().Info("lambda context",
			zap.String("request_id", lwa.RequestID),
			zap.Duration("remaining", lwa.RemainingTime()),
		)
	}

	_ = h.dynamo

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	return json.NewEncoder(w).Encode(map[string]string{
		"id":     "new-item-123",
		"status": "created",
	})
}

func main() {
	blwa.NewApp[Env](
		func(m *blwa.Mux, h *ItemHandlers) {
			m.HandleFunc("GET /items", h.ListItems)
			m.HandleFunc("GET /items/{id}", h.GetItem, "get-item")
			m.HandleFunc("POST /items", h.CreateItem)
		},
		// Local region DynamoDB client - injected directly as *dynamodb.Client
		blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
			return dynamodb.NewFromConfig(cfg)
		}),
		blwa.WithFx(fx.Provide(NewItemHandlers)),
	).Run()
}
Example (FixedRegion)

Example_fixedRegion demonstrates fixed region AWS client injection. Use InRegion[T] wrapper when you need to access resources in a specific region (e.g., S3 buckets that must be in a particular region).

package main

import (
	"encoding/json"
	"net/http"

	"github.com/advdv/bhttp"
	"github.com/advdv/bhttp/blwa"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

// Env defines the environment variables for the application.
// Embed blwa.BaseEnvironment to get the required LWA fields.
type Env struct {
	blwa.BaseEnvironment
	MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

// UploadHandlers demonstrates fixed region client injection.
type UploadHandlers struct {
	rt *blwa.Runtime[Env]
	s3 *blwa.InRegion[s3.Client]
}

func NewUploadHandlers(rt *blwa.Runtime[Env], s3 *blwa.InRegion[s3.Client]) *UploadHandlers {
	return &UploadHandlers{rt: rt, s3: s3}
}

// Upload uploads a file to a fixed-region S3 bucket.
// Demonstrates: Fixed region client injection using InRegion[T] wrapper.
func (h *UploadHandlers) Upload(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
	ctx.Log().Info("uploading to fixed region S3",
		zap.String("region", h.s3.Region))

	_ = h.s3.Client

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]string{
		"status": "uploaded",
		"region": h.s3.Region,
	})
}

func main() {
	blwa.NewApp[Env](
		func(m *blwa.Mux, h *UploadHandlers) {
			m.HandleFunc("POST /upload", h.Upload)
		},
		// Fixed region S3 client - wrapped with InRegion[T]
		blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[s3.Client] {
			return blwa.NewInRegion(s3.NewFromConfig(cfg), "eu-central-1")
		}, blwa.ForRegion("eu-central-1")),
		blwa.WithFx(fx.Provide(NewUploadHandlers)),
	).Run()
}
Example (MultiRegion)

Example_multiRegion demonstrates using all three region types together. This is a common pattern where you need: - Local region clients for low-latency data access - Primary region clients for shared configuration - Fixed region clients for specific resources

package main

import (
	"encoding/json"
	"net/http"

	"github.com/advdv/bhttp"
	"github.com/advdv/bhttp/blwa"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/ssm"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

// Env defines the environment variables for the application.
// Embed blwa.BaseEnvironment to get the required LWA fields.
type Env struct {
	blwa.BaseEnvironment
	MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

// MultiRegionHandlers demonstrates all three region types in one handler.
type MultiHandlers struct {
	rt     *blwa.Runtime[Env]
	dynamo *dynamodb.Client
	ssm    *blwa.Primary[ssm.Client]
	s3     *blwa.InRegion[s3.Client]
}

func NewMultiHandlers(
	rt *blwa.Runtime[Env],
	dynamo *dynamodb.Client,
	ssm *blwa.Primary[ssm.Client],
	s3 *blwa.InRegion[s3.Client],
) *MultiHandlers {
	return &MultiHandlers{rt: rt, dynamo: dynamo, ssm: ssm, s3: s3}
}

func (h *MultiHandlers) Process(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
	ctx.Log().Info("processing with multi-region clients",
		zap.String("s3_region", h.s3.Region))

	_ = h.dynamo
	_ = h.ssm.Client
	_ = h.s3.Client

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]string{
		"status":    "processed",
		"s3_region": h.s3.Region,
	})
}

func main() {
	blwa.NewApp[Env](
		func(m *blwa.Mux, h *MultiHandlers) {
			m.HandleFunc("POST /process", h.Process)
		},
		// Local region DynamoDB - direct injection
		blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
			return dynamodb.NewFromConfig(cfg)
		}),
		// Primary region SSM - wrapped with Primary[T]
		blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
			return blwa.NewPrimary(ssm.NewFromConfig(cfg))
		}, blwa.ForPrimaryRegion()),
		// Fixed region S3 - wrapped with InRegion[T]
		blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[s3.Client] {
			return blwa.NewInRegion(s3.NewFromConfig(cfg), "eu-central-1")
		}, blwa.ForRegion("eu-central-1")),
		blwa.WithFx(fx.Provide(NewMultiHandlers)),
	).Run()
}
Example (PrimaryRegion)

Example_primaryRegion demonstrates primary region AWS client injection. Use Primary[T] wrapper when you need to access resources in the primary deployment region (e.g., shared config in SSM Parameter Store).

package main

import (
	"encoding/json"
	"net/http"

	"github.com/advdv/bhttp"
	"github.com/advdv/bhttp/blwa"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/ssm"
	"go.uber.org/fx"
)

// Env defines the environment variables for the application.
// Embed blwa.BaseEnvironment to get the required LWA fields.
type Env struct {
	blwa.BaseEnvironment
	MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

// ConfigHandlers demonstrates primary region client injection.
type ConfigHandlers struct {
	rt  *blwa.Runtime[Env]
	ssm *blwa.Primary[ssm.Client]
}

func NewConfigHandlers(rt *blwa.Runtime[Env], ssm *blwa.Primary[ssm.Client]) *ConfigHandlers {
	return &ConfigHandlers{rt: rt, ssm: ssm}
}

// GetConfig fetches configuration from the primary region SSM Parameter Store.
// Demonstrates: Primary region client injection using Primary[T] wrapper.
func (h *ConfigHandlers) GetConfig(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {
	ctx.Log().Info("fetching config from primary region SSM")

	_ = h.ssm.Client

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]string{
		"config": "value-from-primary-region",
	})
}

func main() {
	blwa.NewApp[Env](
		func(m *blwa.Mux, h *ConfigHandlers) {
			m.HandleFunc("GET /config", h.GetConfig)
		},
		// Primary region SSM client - wrapped with Primary[T]
		blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
			return blwa.NewPrimary(ssm.NewFromConfig(cfg))
		}, blwa.ForPrimaryRegion()),
		blwa.WithFx(fx.Provide(NewConfigHandlers)),
	).Run()
}
Example (Secrets)

Example_secrets demonstrates retrieving secrets from AWS Secrets Manager. Use Runtime.Secret to fetch raw string secrets or extract values from JSON secrets.

package main

import (
	"encoding/json"
	"net/http"

	"github.com/advdv/bhttp"
	"github.com/advdv/bhttp/blwa"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

// Env defines the environment variables for the application.
// Embed blwa.BaseEnvironment to get the required LWA fields.
type Env struct {
	blwa.BaseEnvironment
	MainTableName string `env:"MAIN_TABLE_NAME,required"`
}

// SecretHandlers demonstrates secret retrieval from AWS Secrets Manager.
type SecretHandlers struct {
	rt *blwa.Runtime[Env]
}

func NewSecretHandlers(rt *blwa.Runtime[Env]) *SecretHandlers {
	return &SecretHandlers{rt: rt}
}

// Connect demonstrates retrieving secrets from AWS Secrets Manager.
// Demonstrates: Runtime.Secret for raw string secrets and JSON path extraction.
func (h *SecretHandlers) Connect(ctx *blwa.Context, w bhttp.ResponseWriter, r *http.Request) error {

	apiKey, err := h.rt.Secret(ctx, "my-api-key-secret")
	if err != nil {
		return err
	}

	dbPassword, err := h.rt.Secret(ctx, "my-db-credentials", "database.password")
	if err != nil {
		return err
	}

	ctx.Log().Info("retrieved secrets",
		zap.Int("api_key_len", len(apiKey)),
		zap.Int("password_len", len(dbPassword)))

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(map[string]string{
		"status": "connected",
	})
}

func main() {
	blwa.NewApp[Env](
		func(m *blwa.Mux, h *SecretHandlers) {
			m.HandleFunc("POST /connect", h.Connect)
		},
		blwa.WithFx(fx.Provide(NewSecretHandlers)),
	).Run()
}

Index

Examples

Constants

View Source
const LambdaMaxResponsePayloadBytes = 6*1024*1024 - 1024

LambdaMaxResponsePayloadBytes is AWS Lambda's 6 MiB limit minus 1 KiB headroom for JSON/API Gateway overhead.

Variables

This section is empty.

Functions

func AWSClientProvider

func AWSClientProvider[T any](factory func(aws.Config) T, opts ...ClientOption) fx.Option

AWSClientProvider creates an fx.Option that provides an AWS client for injection. The factory receives an aws.Config with the region already configured.

For local region clients (default), the factory returns *T directly:

blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
    return dynamodb.NewFromConfig(cfg)
})

For primary region clients, wrap with Primary[T]:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

For fixed region clients, wrap with InRegion[T]:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[sqs.Client] {
    return blwa.NewInRegion(sqs.NewFromConfig(cfg), "us-east-1")
}, blwa.ForRegion("us-east-1"))

func Log

func Log(ctx context.Context) *zap.Logger

Log returns a trace-correlated zap logger from the context.

func NewAWSConfig

func NewAWSConfig(ctx context.Context) (aws.Config, error)

NewAWSConfig loads the default AWS SDK v2 configuration.

func NewLogger

func NewLogger(env Environment) (*zap.Logger, error)

NewLogger creates a zap logger configured from the environment. Uses JSON encoding suitable for CloudWatch. LOG_LEVEL controls the level (debug, info, warn, error).

func NewPropagator

func NewPropagator(env Environment) propagation.TextMapPropagator

NewPropagator creates a TextMapPropagator based on the exporter type. For xrayudp: uses X-Ray propagator for AWS Lambda environments. For stdout/default: uses W3C TraceContext + Baggage composite propagator.

func NewServer

func NewServer(params ServerParams, cfg ServerConfig) *http.Server

NewServer creates an HTTP server with all middleware and routing configured.

func NewTracerProvider

func NewTracerProvider(lc fx.Lifecycle, env Environment) (trace.TracerProvider, error)

NewTracerProvider creates and configures the OpenTelemetry TracerProvider. Supported exporters via OTEL_EXPORTER env var: "stdout" (default), "xrayudp" (Lambda). Shutdown is handled automatically via fx.Lifecycle.

func ParseEnv

func ParseEnv[E Environment]() func() (E, error)

ParseEnv parses environment variables into the given Environment type.

func Span

func Span(ctx context.Context) trace.Span

Span returns the current trace span from the context.

Types

type AWSSecretReader

type AWSSecretReader struct {
	// contains filtered or unexported fields
}

AWSSecretReader implements SecretReader using AWS Secrets Manager caching client.

func NewAWSSecretReader

func NewAWSSecretReader(cfg aws.Config) (*AWSSecretReader, error)

NewAWSSecretReader creates a new AWSSecretReader using the provided AWS config.

func (*AWSSecretReader) GetSecretString

func (r *AWSSecretReader) GetSecretString(ctx context.Context, secretID string) (string, error)

GetSecretString retrieves a secret value from AWS Secrets Manager with caching.

type App

type App struct {
	// contains filtered or unexported fields
}

App wraps an fx.App for lifecycle management.

func NewApp

func NewApp[E Environment](routing any, opts ...Option) *App

NewApp creates a batteries-included app with dependency injection.

The routing function can request any types that are provided via fx options. At minimum, it should accept *Mux for routing.

Example:

blwa.NewApp[Env](func(m *blwa.Mux, h *Handlers) {
    m.HandleFunc("GET /items", h.ListItems, "list-items")
},
    blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
        return dynamodb.NewFromConfig(cfg)
    }),
    blwa.WithFx(fx.Provide(NewHandlers)),
).Run()

func (*App) Run

func (a *App) Run()

Run starts the application and blocks until interrupted.

func (*App) Start

func (a *App) Start(ctx context.Context) error

Start starts the application with the given context.

type AppConfig

type AppConfig struct {
	ServerConfig
	FxOptions []fx.Option
}

AppConfig holds configuration for the app.

type BaseEnvironment

type BaseEnvironment struct {
	Port               int           `env:"AWS_LWA_PORT,required"`
	ServiceName        string        `env:"BW_SERVICE_NAME,required"`
	ReadinessCheckPath string        `env:"AWS_LWA_READINESS_CHECK_PATH,required"`
	LogLevel           zapcore.Level `env:"BW_LOG_LEVEL" envDefault:"info"`
	OtelExporter       string        `env:"BW_OTEL_EXPORTER" envDefault:"stdout"`
	AWSRegion          string        `env:"AWS_REGION,required"`
	PrimaryRegion      string        `env:"BW_PRIMARY_REGION,required"`
	// GatewayAccessLogGroup is the CloudWatch Log Group name for API Gateway
	// access logs. When set, traces include this log group for X-Ray log
	// correlation. Injected automatically by bwcdkrestgateway.
	GatewayAccessLogGroup string `env:"BW_GATEWAY_ACCESS_LOG_GROUP"`
}

BaseEnvironment contains the required LWA environment variables. Embed this in your custom environment struct.

type ClientOption

type ClientOption func(*clientOptions)

ClientOption configures AWS client registration.

func ForPrimaryRegion

func ForPrimaryRegion() ClientOption

ForPrimaryRegion configures the client to use the PRIMARY_REGION env var. Use this for cross-region operations that must target the primary deployment region.

The factory should return *blwa.Primary[T] to make the region explicit in the type:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

func ForRegion

func ForRegion(region string) ClientOption

ForRegion configures the client to use a specific fixed region.

The factory should return *blwa.InRegion[T] to make the region explicit in the type:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[sqs.Client] {
    return blwa.NewInRegion(sqs.NewFromConfig(cfg), "us-east-1")
}, blwa.ForRegion("us-east-1"))

type Context added in v0.4.0

type Context struct {
	context.Context
}

Context is blwa's custom context type that provides method access to request-scoped values. It embeds context.Context and adds convenience methods for accessing logging, tracing, and Lambda execution context.

Handlers receive *Context and can use either method syntax or package functions:

// Method syntax
ctx.Log().Info("processing request")
ctx.Span().AddEvent("fetching data")

// Package function syntax (equivalent)
blwa.Log(ctx).Info("processing request")
blwa.Span(ctx).AddEvent("fetching data")

func (*Context) LWA added in v0.4.0

func (c *Context) LWA() *LWAContext

LWA returns the Lambda execution context, or nil if not running in Lambda.

func (*Context) Log added in v0.4.0

func (c *Context) Log() *zap.Logger

Log returns a trace-correlated zap logger.

func (*Context) Span added in v0.4.0

func (c *Context) Span() trace.Span

Span returns the current OpenTelemetry trace span.

type Environment

type Environment interface {
	// contains filtered or unexported methods
}

Environment defines the interface that all environment configurations must implement. Embed BaseEnvironment in your struct to satisfy this interface.

type InRegion

type InRegion[T any] struct {
	Client *T
	Region string
}

InRegion wraps an AWS client configured for a specific fixed region. Use this when registering and injecting clients that must target a specific region.

Registration:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[sqs.Client] {
    return blwa.NewInRegion(sqs.NewFromConfig(cfg), "us-east-1")
}, blwa.ForRegion("us-east-1"))

Injection:

func NewHandlers(sqs *blwa.InRegion[sqs.Client]) *Handlers

Usage:

h.sqs.Client.SendMessage(ctx, ...)
region := h.sqs.Region // "us-east-1"

func NewInRegion

func NewInRegion[T any](client *T, region string) *InRegion[T]

NewInRegion creates an InRegion wrapper for an AWS client configured for a fixed region. Use this in your client factory when registering with ForRegion():

blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[sqs.Client] {
    return blwa.NewInRegion(sqs.NewFromConfig(cfg), "us-east-1")
}, blwa.ForRegion("us-east-1"))

type LWAContext

type LWAContext struct {
	RequestID          string       `json:"request_id"`
	Deadline           int64        `json:"deadline"`
	InvokedFunctionARN string       `json:"invoked_function_arn"`
	XRayTraceID        string       `json:"xray_trace_id"`
	EnvConfig          LWAEnvConfig `json:"env_config"`
}

LWAContext contains Lambda execution context from the x-amzn-lambda-context header.

func LWA

func LWA(ctx context.Context) *LWAContext

LWA retrieves the LWAContext from the request context. Returns nil if not running in a Lambda environment.

func (*LWAContext) DeadlineTime

func (lc *LWAContext) DeadlineTime() time.Time

DeadlineTime returns the Lambda invocation deadline as a time.Time.

func (*LWAContext) RemainingTime

func (lc *LWAContext) RemainingTime() time.Duration

RemainingTime returns the duration until the Lambda invocation deadline.

type LWAEnvConfig

type LWAEnvConfig struct {
	FunctionName string `json:"function_name"`
	Memory       int    `json:"memory"`
	Version      string `json:"version"`
	LogGroup     string `json:"log_group"`
	LogStream    string `json:"log_stream"`
}

LWAEnvConfig contains Lambda function environment configuration.

type Mux

type Mux = bhttp.ServeMux[*Context]

Mux is an alias for bhttp.ServeMux with blwa's custom Context type. Handlers registered on this mux receive *Context, which provides method access to request-scoped values like logging, tracing, and Lambda execution context.

func NewMux

func NewMux() *Mux

NewMux creates a new Mux with sensible defaults.

type Option

type Option func(*AppConfig)

Option configures the App.

func WithAWSClient

func WithAWSClient[T any](factory func(aws.Config) T, opts ...ClientOption) Option

WithAWSClient registers an AWS SDK v2 client for dependency injection. Clients are injected directly into handler constructors via fx.

By default, clients target the local region (AWS_REGION env var):

blwa.WithAWSClient(func(cfg aws.Config) *dynamodb.Client {
    return dynamodb.NewFromConfig(cfg)
})

For primary region, wrap with Primary[T] and use ForPrimaryRegion():

blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

For fixed region, wrap with InRegion[T] and use ForRegion():

blwa.WithAWSClient(func(cfg aws.Config) *blwa.InRegion[sqs.Client] {
    return blwa.NewInRegion(sqs.NewFromConfig(cfg), "eu-west-1")
}, blwa.ForRegion("eu-west-1"))

func WithFx

func WithFx(fxOpts ...fx.Option) Option

WithFx adds fx options for dependency injection.

func WithHealthHandler

func WithHealthHandler(h func(http.ResponseWriter, *http.Request)) Option

WithHealthHandler sets a custom health check handler. If not set, a default handler returning 200 OK is used.

type Primary

type Primary[T any] struct {
	Client *T
}

Primary wraps an AWS client for the primary deployment region. Use this when registering and injecting clients that must target PRIMARY_REGION.

Registration:

blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

Injection:

func NewHandlers(ssm *blwa.Primary[ssm.Client]) *Handlers

Usage:

h.ssm.Client.GetParameter(ctx, ...)

func NewPrimary

func NewPrimary[T any](client *T) *Primary[T]

NewPrimary creates a Primary wrapper for an AWS client configured for the primary region. Use this in your client factory when registering with ForPrimaryRegion():

blwa.WithAWSClient(func(cfg aws.Config) *blwa.Primary[ssm.Client] {
    return blwa.NewPrimary(ssm.NewFromConfig(cfg))
}, blwa.ForPrimaryRegion())

type Region

type Region interface {
	// contains filtered or unexported methods
}

Region represents a target AWS region for client creation.

func FixedRegion

func FixedRegion(region string) Region

FixedRegion returns a Region that uses a specific region string.

func LocalRegion

func LocalRegion() Region

LocalRegion returns a Region that uses the Lambda's AWS_REGION.

func PrimaryRegion

func PrimaryRegion() Region

PrimaryRegion returns a Region that uses the PRIMARY_REGION env var. Use this for cross-region operations that must target the primary deployment region.

type Runtime

type Runtime[E Environment] struct {
	// contains filtered or unexported fields
}

Runtime provides access to app-scoped dependencies. Inject this into handler constructors via fx instead of pulling from context.

Example:

type Handlers struct {
    rt     *blwa.Runtime[Env]
    dynamo *dynamodb.Client
}

func NewHandlers(rt *blwa.Runtime[Env], dynamo *dynamodb.Client) *Handlers {
    return &Handlers{rt: rt, dynamo: dynamo}
}

func (h *Handlers) GetItem(ctx context.Context, w bhttp.ResponseWriter, r *http.Request) error {
    env := h.rt.Env()
    url, _ := h.rt.Reverse("get-item", id)
    h.dynamo.GetItem(ctx, ...)
    // ...
}

func NewRuntime

func NewRuntime[E Environment](env E, mux *Mux, params RuntimeParams) *Runtime[E]

NewRuntime creates a new Runtime with the given dependencies.

func (*Runtime[E]) Env

func (r *Runtime[E]) Env() E

Env returns the environment configuration.

func (*Runtime[E]) Reverse

func (r *Runtime[E]) Reverse(name string, params ...string) (string, error)

Reverse returns the URL for a named route with the given parameters. The route must have been registered with a name using Handle/HandleFunc.

func (*Runtime[E]) Secret

func (r *Runtime[E]) Secret(ctx context.Context, secretID string, jsonPath ...string) (string, error)

Secret retrieves a secret value from AWS Secrets Manager.

The secretID is the secret name or ARN to read from (required). If jsonPath is provided, the secret is parsed as JSON and the path is extracted using gjson syntax (e.g., "database.password", "api.keys.0"). If jsonPath is omitted, the raw secret string is returned.

Secrets are cached but fetched per-request to support rotation without redeployment.

Example:

// Raw string secret
apiKey, err := h.rt.Secret(ctx, "my-api-key-secret")

// JSON secret with path extraction
password, err := h.rt.Secret(ctx, "my-db-credentials", "password")

type RuntimeParams

type RuntimeParams struct {
	SecretReader SecretReader
}

RuntimeParams holds optional dependencies for Runtime.

type SecretReader

type SecretReader interface {
	GetSecretString(ctx context.Context, secretID string) (string, error)
}

SecretReader abstracts secret retrieval for testability and flexibility.

type ServerConfig

type ServerConfig struct {
	HealthHandler func(http.ResponseWriter, *http.Request)
}

ServerConfig holds optional configuration for the HTTP server.

type ServerParams

type ServerParams struct {
	fx.In

	Env        Environment
	Mux        *Mux
	Logger     *zap.Logger
	TracerProv trace.TracerProvider
	Propagator propagation.TextMapPropagator
}

ServerParams holds the dependencies for creating an HTTP server.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL