Example 01 - Basic Configuration

Simple single-file YAML configuration demonstrating core concepts.

What’s Demonstrated

File Structure

01-basic-config/
├── main.go              # Application entry point + registration
├── user_service.go      # Service with @Handler annotation
├── user_repository.go   # Repository implementation
├── middleware.go        # Custom middleware
├── config.yaml          # YAML configuration
├── go.mod               # Go module file
├── test.http            # HTTP tests
├── README.md            # This file
└── zz_generated.lokstra.go  # Auto-generated by lokstra.Bootstrap()

Code Walkthrough

1. Service with @Handler Annotation

user_service.go:

// @Handler name="user-service", prefix="/api/users", middlewares=["recovery", "request-logger"]
type UserService struct {
    // @Inject "user-repository"
    UserRepo UserRepository
}

// @Route "GET /{id}"
func (s *UserService) GetByID(p *GetUserRequest) (*User, error) {
    return s.UserRepo.GetByID(p.ID)
}

// @Route "GET /"
func (s *UserService) List(p *ListUsersRequest) ([]*User, error) {
    return s.UserRepo.List()
}

// @Route "POST /"
func (s *UserService) Create(p *CreateUserRequest) (*User, error) {
    // ...
}

Annotations explained:

2. Repository Factory

user_repository.go:

func NewUserRepository(deps map[string]any, config map[string]any) any {
    repo := &UserRepositoryImpl{
        users:  make(map[int]*User),
        nextID: 1,
    }
    // Seed data
    repo.Create(&User{Name: "John Doe", Email: "john@example.com"})
    return repo
}

Service factory signature: func(deps, config map[string]any) any

3. Main Registration Flow

main.go:

func main() {
    // STEP 1: Bootstrap - generates code from annotations
    lokstra.Bootstrap()

    // STEP 2: Load Config - loads YAML configuration
    lokstra_registry.LoadConfig("config.yaml")

    // STEP 3: Register Service Types - map factory names to functions
    registerServiceTypes()

    // STEP 4: Register Middleware Types
    registerMiddlewareTypes()

    // STEP 5: Initialize and Run Server
    lokstra.InitAndRunServer()
}

4. YAML Configuration

config.yaml:

deployments:
  development:
    servers:
      api:
        base-url: "http://localhost"
        addr: ":8080"
        published-services: [user-service]

Minimal config! Everything else comes from:

How It Works

Bootstrap Flow

  1. lokstra.Bootstrap() scans for @Handler annotations
  2. Generates zz_generated.lokstra.go with service metadata
  3. Auto-registers service type: user-serviceUserServiceFactory

Service Registration Flow

  1. Repository factory registered:
    RegisterServiceType("user-repository-factory", NewUserRepository, nil)
    RegisterLazyService("user-repository", "user-repository-factory", nil)
    
  2. Service factory auto-registered by lokstra.Bootstrap():
    • Service name: user-service
    • Dependencies: user-repository (from @Inject)
    • Router metadata: prefix, middlewares, routes

Router Auto-Generation

When published-services: [user-service] is declared:

  1. Lokstra looks up user-service metadata
  2. Creates router with prefix /api/users
  3. Registers routes from @Route annotations:
    • GET /{id}GetByID
    • GET /List
    • POST /Create
  4. Applies middlewares: recovery, request-logger

Run

# Install dependencies
go mod download

# Run the application
go run .

First run will generate zz_generated.lokstra.go

Test

# Get all users
curl http://localhost:8080/api/users

# Get user by ID
curl http://localhost:8080/api/users/1

# Create new user
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Bob","email":"bob@example.com"}'

Or use the test.http file in VS Code with REST Client extension.

Generated Routes

GET    /api/users          # List all users
GET    /api/users/{id}     # Get user by ID
POST   /api/users          # Create new user

All routes have these middleware applied:

  1. recovery - Panic recovery
  2. request-logger - Request/response logging

Key Concepts

1. Convention Over Configuration

Minimal YAML config because:

2. Auto-Generation

lokstra.Bootstrap() generates:

3. Factory Pattern

Services and repositories use factory functions:

func Factory(deps map[string]any, config map[string]any) any {
    return &Implementation{}
}

4. Dependency Injection

// In annotation
// @Inject "user-repository"
UserRepo UserRepository

// Runtime resolution (automatic)
service.UserRepo = deps["user-repository"].(UserRepository)

Next Steps

Summary

This example demonstrates:

All with just one config file and annotations! 🎉