Example 01 - Basic Configuration
Simple single-file YAML configuration demonstrating core concepts.
What’s Demonstrated
- ✅ Single-file YAML configuration
- ✅ Service registration with @Handler annotation
- ✅ Dependency injection (service → repository)
- ✅ Router auto-generation from published services
- ✅ Middleware registration and usage
- ✅ In-memory repository implementation
- ✅ Complete runnable example
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:
@Handler- Marks this as a service with auto-generated routername- Service name for registrationprefix- URL prefix for all routesmiddlewares- Middleware applied to all routes
@Inject- Declares dependency injection@Route- Defines HTTP method and path for each handler
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:
@Handlerannotation (service metadata)- Service type registration in code
- Convention over configuration
How It Works
Bootstrap Flow
lokstra.Bootstrap()scans for@Handlerannotations- Generates
zz_generated.lokstra.gowith service metadata - Auto-registers service type:
user-service→UserServiceFactory
Service Registration Flow
- Repository factory registered:
RegisterServiceType("user-repository-factory", NewUserRepository, nil) RegisterLazyService("user-repository", "user-repository-factory", nil) - Service factory auto-registered by
lokstra.Bootstrap():- Service name:
user-service - Dependencies:
user-repository(from@Inject) - Router metadata: prefix, middlewares, routes
- Service name:
Router Auto-Generation
When published-services: [user-service] is declared:
- Lokstra looks up
user-servicemetadata - Creates router with prefix
/api/users - Registers routes from
@Routeannotations:GET /{id}→GetByIDGET /→ListPOST /→Create
- 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:
recovery- Panic recoveryrequest-logger- Request/response logging
Key Concepts
1. Convention Over Configuration
Minimal YAML config because:
- Routes defined by
@Routeannotations - Service metadata from
@Handler - Dependencies from
@Inject
2. Auto-Generation
lokstra.Bootstrap() generates:
- Service factory registration
- Route metadata
- Dependency injection setup
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
- See 02-multi-file for environment-specific configs
- See 06-handlers for reverse proxy, SPA, static files
- See 07-db-pools for database configuration
Summary
This example demonstrates:
- ✅ Annotation-driven service development
- ✅ Minimal YAML configuration
- ✅ Auto-generated routers
- ✅ Dependency injection
- ✅ Custom middleware
- ✅ Complete runnable project
All with just one config file and annotations! 🎉