AI Agent Guide - Lokstra Framework
Version: 1.0.0
Last Updated: November 12, 2025
Target: AI Agents (GitHub Copilot, Claude, ChatGPT, etc.)
This guide helps AI agents understand how to assist programmers using the Lokstra Framework (github.com/primadi/lokstra). It provides structured patterns, complete code examples, and best practices for building Go web applications.
Table of Contents
- Framework Overview
- Core Concepts
- Quick Start Patterns
- Router Patterns
- Service Patterns
- Configuration YAML
- Annotation System
- Middleware Usage
- Dependency Injection
- Project Structure Templates
- Common Patterns & Idioms
- Troubleshooting
Framework Overview
What is Lokstra?
Lokstra is a versatile Go web framework with two usage modes:
- Router Mode (Like Echo, Gin, Chi)
- Simple HTTP routing
- Flexible handler signatures (29+ forms)
- Middleware support
- No DI, no config files required
- Framework Mode (Like NestJS, Spring Boot)
- Lazy dependency injection (type-safe)
- Auto-generated REST routers
- YAML-driven configuration
- Multi-deployment support (monolith → microservices)
Key Design Principles
- Type-safe DI with direct type assertions
- Service-level lazy loading (services created on first access)
- Eager dependency resolution (dependencies loaded when service created)
- Zero reflection in hot path
- Configuration-driven deployment (optional YAML)
- Flexible handler signatures (supports many parameter combinations)
Core Concepts
1. Router
HTTP request routing with flexible handler signatures.
import "github.com/primadi/lokstra"
r := lokstra.NewRouter("api")
// Various handler signatures supported
r.GET("/hello", func() string {
return "Hello, World!"
})
r.GET("/user/{id}", func(id string) (map[string]any, error) {
return map[string]any{"id": id, "name": "John"}, nil
})
r.POST("/users", func(ctx *request.Context, params *CreateUserParams) error {
// params automatically validated
return ctx.Api.Ok(params)
})
2. App
Application wrapper with graceful shutdown.
app := lokstra.NewApp("myapp", ":8080", router)
app.Run(30 * time.Second) // 30s graceful shutdown timeout
3. Service
Business logic component with dependency injection.
type UserService struct {
UserRepo UserRepository
}
func (s *UserService) GetByID(id string) (*User, error) {
return s.UserRepo.GetByID(id)
}
4. Repository
Data access layer (interface pattern).
type UserRepository interface {
GetByID(id string) (*User, error)
List() ([]*User, error)
Create(user *User) (*User, error)
Update(user *User) (*User, error)
Delete(id string) error
}
5. Domain Models
Data structures with validation tags.
type User struct {
ID string `json:"id"`
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18,max=120"`
}
Quick Start Patterns
Pattern 1: Simple Router (No DI, No Config)
Use Case: Learning, simple APIs, minimal setup
package main
import (
"github.com/primadi/lokstra"
"github.com/primadi/lokstra/middleware/cors"
"github.com/primadi/lokstra/middleware/recovery"
"time"
)
func main() {
r := lokstra.NewRouter("api")
// Middleware
r.Use(recovery.Middleware(nil))
r.Use(cors.Middleware([]string{"*"}))
// Routes
r.GET("/", func() string {
return "Hello, Lokstra!"
})
r.GET("/ping", func() string {
return "pong"
})
r.GET("/users", func() []map[string]any {
return []map[string]any{
{"id": "1", "name": "Alice"},
{"id": "2", "name": "Bob"},
}
})
// Run
app := lokstra.NewApp("simple", ":8080", r)
app.Run(30 * time.Second)
}
Pattern 2: With Annotation-Based Services (Recommended)
Use Case: Production apps, team projects, scalable architecture
File: main.go
package main
import (
"github.com/primadi/lokstra"
"github.com/primadi/lokstra/core/deploy"
"github.com/primadi/lokstra/lokstra_registry"
// Import packages with @RouterService annotations
_ "myapp/modules/user/application"
_ "myapp/modules/order/application"
)
func main() {
// Auto-generates code when @RouterService changes detected
lokstra.Bootstrap()
deploy.SetLogLevelFromEnv() // LOKSTRA_LOG_LEVEL=debug
// Services auto-registered via annotations!
lokstra_registry.RunServerFromConfig()
}
File: modules/user/application/user_service.go
package application
import (
"myapp/modules/user/domain"
"myapp/modules/user/infrastructure"
)
// @RouterService name="user-service", prefix="/api/users"
type UserServiceImpl struct {
// @Inject "user-repository"
UserRepo domain.UserRepository
}
// @Route "GET /"
func (s *UserServiceImpl) List(p *domain.ListUsersParams) ([]*domain.User, error) {
return s.UserRepo.List()
}
// @Route "GET /{id}"
func (s *UserServiceImpl) GetByID(p *domain.GetUserParams) (*domain.User, error) {
return s.UserRepo.GetByID(p.ID)
}
// @Route "POST /"
func (s *UserServiceImpl) Create(p *domain.CreateUserParams) (*domain.User, error) {
u := &domain.User{
Name: p.Name,
Email: p.Email,
}
return s.UserRepo.Create(u)
}
// @Route "PUT /{id}"
func (s *UserServiceImpl) Update(p *domain.UpdateUserParams) (*domain.User, error) {
u := &domain.User{
ID: p.ID,
Name: p.Name,
Email: p.Email,
}
return s.UserRepo.Update(u)
}
// @Route "DELETE /{id}"
func (s *UserServiceImpl) Delete(p *domain.DeleteUserParams) error {
return s.UserRepo.Delete(p.ID)
}
Generate code (automatic with Bootstrap):
func main() {
lokstra.Bootstrap() // Auto-generates on every run when changes detected
// ...
}
Manual generation (optional):
# Before build/deploy to ensure latest code
lokstra autogen .
# Or force rebuild all
go run . --generate-only
File: config.yaml
service-definitions:
user-repository:
type: user-repository-factory
config:
dsn: "memory://users"
user-service:
# Type auto-registered via @RouterService annotation
depends-on:
- user-repository
deployments:
development:
servers:
api:
base-url: "http://localhost:8080"
addr: ":8080"
published-services:
- user-service
Pattern 3: Manual Service Registration (Legacy/Advanced)
Use Case: Complex custom factories, special initialization logic
File: main.go
package main
import (
"github.com/primadi/lokstra/core/deploy"
"github.com/primadi/lokstra/lokstra_registry"
)
func main() {
deploy.SetLogLevelFromEnv() // LOKSTRA_LOG_LEVEL=debug
registerServiceTypes()
registerMiddlewareTypes()
lokstra_registry.RunServerFromConfig()
}
File: register.go
package main
import (
"github.com/primadi/lokstra/lokstra_registry"
"myapp/service"
"myapp/repository"
)
func registerServiceTypes() {
// Register repository
lokstra_registry.RegisterServiceType(
"user-repository-factory",
repository.NewUserRepositoryFactory,
nil,
)
// Register service
lokstra_registry.RegisterServiceType(
"user-service-factory",
service.UserServiceFactory,
service.UserServiceRemoteFactory,
)
}
func registerMiddlewareTypes() {
// Built-in middleware auto-registered
// Custom middleware registration here
}
File: config.yaml
service-definitions:
user-repository:
type: user-repository-factory
config:
dsn: "memory://users"
user-service:
type: user-service-factory
depends-on:
- user-repository
deployments:
development:
servers:
api:
base-url: "http://localhost:8080"
addr: ":8080"
published-services:
- user-service
Router Patterns
Handler Signature Variations
Lokstra supports 29+ handler signatures. Here are the most common:
// 1. Simple return
r.GET("/hello", func() string {
return "Hello"
})
// 2. Return with error
r.GET("/user/{id}", func(id string) (string, error) {
if id == "" {
return "", errors.New("id required")
}
return "User: " + id, nil
})
// 3. Struct response
r.GET("/user/{id}", func(id string) (*User, error) {
return &User{ID: id, Name: "John"}, nil
})
// 4. Context access
r.GET("/header", func(ctx *request.Context) error {
token := ctx.Req.Header.Get("Authorization")
return ctx.Api.Ok(map[string]string{"token": token})
})
// 5. Path parameters
r.GET("/posts/{postId}/comments/{commentId}",
func(postId, commentId string) (string, error) {
return fmt.Sprintf("Post: %s, Comment: %s", postId, commentId), nil
})
// 6. Request body binding with validation
type CreateUserParams struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
r.POST("/users", func(ctx *request.Context, params *CreateUserParams) error {
// params already validated!
return ctx.Api.Ok(params)
})
// 7. Multiple parameters
r.PUT("/users/{id}", func(ctx *request.Context, id string, params *UpdateUserParams) error {
params.ID = id
user := updateUser(params)
return ctx.Api.Ok(user)
})
// 8. Query parameters
type SearchParams struct {
Query string `query:"q" validate:"required"`
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
}
r.GET("/search", func(params *SearchParams) ([]Result, error) {
return searchResults(params.Query, params.Page, params.Limit), nil
})
Router Groups and Versioning
r := lokstra.NewRouter("api")
// Group routes
v1 := r.Group("/v1")
v1.GET("/users", listUsersV1)
v1.POST("/users", createUserV1)
v2 := r.Group("/v2")
v2.GET("/users", listUsersV2)
v2.POST("/users", createUserV2)
// Nested groups
api := r.Group("/api")
admin := api.Group("/admin")
admin.GET("/stats", getStats)
Middleware Application
import (
"github.com/primadi/lokstra/middleware/recovery"
"github.com/primadi/lokstra/middleware/request_logger"
"github.com/primadi/lokstra/middleware/cors"
)
r := lokstra.NewRouter("api")
// Global middleware (all routes)
r.Use(recovery.Middleware(nil))
r.Use(request_logger.Middleware(nil))
// Group middleware
authorized := r.Group("/admin")
authorized.Use(authMiddleware)
authorized.GET("/users", listUsers)
// Route-specific middleware
r.GET("/public", publicHandler)
r.GET("/private", privateHandler, authMiddleware, logMiddleware)
Service Patterns
Service Factory Pattern
Service implementation:
package service
import (
"github.com/primadi/lokstra/core/service"
"myapp/domain/user"
)
type UserServiceImpl struct {
UserRepo user.UserRepository
}
var _ user.UserService = (*UserServiceImpl)(nil)
func (s *UserServiceImpl) GetByID(p *user.GetUserParams) (*user.User, error) {
return s.UserRepo.GetByID(p.ID)
}
func (s *UserServiceImpl) List(p *user.ListUsersParams) ([]*user.User, error) {
return s.UserRepo.List()
}
func (s *UserServiceImpl) Create(p *user.CreateUserParams) (*user.User, error) {
u := &user.User{
Name: p.Name,
Email: p.Email,
}
return s.UserRepo.Create(u)
}
// Factory function for local deployment
func UserServiceFactory(deps map[string]any, config map[string]any) any {
return &UserServiceImpl{
UserRepo: deps["user-repository"].(user.UserRepository),
}
}
// Factory function for remote deployment (microservices)
func UserServiceRemoteFactory(deps map[string]any, config map[string]any) any {
proxyService, _ := config["remote"].(*proxy.Service)
return NewUserServiceRemote(proxyService)
}
Repository Factory Pattern
package repository
import "myapp/domain/user"
type UserRepositoryInMemory struct {
users map[string]*user.User
}
func (r *UserRepositoryInMemory) GetByID(id string) (*user.User, error) {
if u, ok := r.users[id]; ok {
return u, nil
}
return nil, errors.New("user not found")
}
func (r *UserRepositoryInMemory) List() ([]*user.User, error) {
result := make([]*user.User, 0, len(r.users))
for _, u := range r.users {
result = append(result, u)
}
return result, nil
}
// Factory function
func NewUserRepositoryFactory(deps map[string]any, config map[string]any) any {
return &UserRepositoryInMemory{
users: make(map[string]*user.User),
}
}
Domain Interface Pattern
File: domain/user/repository.go
package user
type UserRepository interface {
GetByID(id string) (*User, error)
List() ([]*User, error)
Create(user *User) (*User, error)
Update(user *User) (*User, error)
Delete(id string) error
}
File: domain/user/service.go
package user
type UserService interface {
GetByID(p *GetUserParams) (*User, error)
List(p *ListUsersParams) ([]*User, error)
Create(p *CreateUserParams) (*User, error)
Update(p *UpdateUserParams) (*User, error)
Delete(p *DeleteUserParams) error
}
File: domain/user/models.go
package user
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type GetUserParams struct {
ID string `path:"id" validate:"required"`
}
type ListUsersParams struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
}
type CreateUserParams struct {
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
}
type UpdateUserParams struct {
ID string `path:"id" validate:"required"`
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
}
type DeleteUserParams struct {
ID string `path:"id" validate:"required"`
}
Configuration YAML
Complete Schema Reference
# YAML Schema: https://primadi.github.io/lokstra/schema/lokstra.schema.json
# Middleware definitions (optional, built-in middleware auto-registered)
middleware-definitions:
recovery:
type: recovery
config:
enable_stack_trace: false
enable_logging: true
request-logger:
type: request-logger
config:
prefix: "API"
enable_colors: true
skip_paths: ["/health", "/metrics"]
cors:
type: cors
config:
allow_origins: ["*"]
body-limit:
type: body-limit
config:
max_size: 10485760 # 10MB
skip_on_path: ["/upload/**"]
# Service definitions
service-definitions:
# Repository layer
user-repository:
type: user-repository-factory
config:
dsn: "postgres://localhost/mydb"
# Service layer
user-service:
type: user-service-factory
depends-on:
- user-repository
config:
cache_ttl: 300
router:
path-prefix: /api/v1
middlewares:
- recovery
- request-logger
hidden: # Hide specific methods from auto-router
- InternalMethod
- PrivateHelper
# Router definitions (advanced path rewriting)
router-definitions:
user-router:
path-prefix: /api
path-rewrites:
- pattern: "^/api/v1/(.*)$"
replacement: "/api/v2/$1"
middlewares:
- cors
- body-limit
hidden:
- DeprecatedMethod
# Deployment configurations
deployments:
# Development environment (monolith)
development:
servers:
api-server:
base-url: "http://localhost:8080"
addr: ":8080"
published-services:
- user-service
- order-service
- payment-service
# Staging environment
staging:
servers:
api-server:
base-url: "https://staging-api.example.com"
addr: ":8080"
published-services:
- user-service
- order-service
# Production microservices
production:
servers:
user-api:
base-url: "https://user-api.example.com"
addr: ":8001"
published-services:
- user-service
order-api:
base-url: "https://order-api.example.com"
addr: ":8002"
published-services:
- order-service
payment-api:
base-url: "https://payment-api.example.com"
addr: ":8003"
published-services:
- payment-service
Environment-Specific Config
Development:
deployments:
development:
servers:
api:
addr: ":8080"
published-services: [user-service, order-service]
Production (Microservices):
deployments:
production:
servers:
user-api:
addr: ":8001"
base-url: "https://user.example.com"
published-services: [user-service]
order-api:
addr: ":8002"
base-url: "https://order.example.com"
published-services: [order-service]
Run specific deployment:
LOKSTRA_DEPLOYMENT=production go run .
Annotation System
@RouterService Annotation
Generate REST routers automatically from service methods.
package application
import (
"github.com/primadi/lokstra/core/service"
"myapp/domain"
)
// @RouterService name="user-service", prefix="/api", middlewares=["recovery", "request-logger"]
type UserServiceImpl struct {
// @Inject "user-repository"
UserRepo domain.UserRepository
}
// @Route "GET /users/{id}"
func (s *UserServiceImpl) GetByID(p *domain.GetUserRequest) (*domain.User, error) {
return s.UserRepo.GetByID(p.ID)
}
// @Route "GET /users"
func (s *UserServiceImpl) List(p *domain.ListUsersRequest) ([]*domain.User, error) {
return s.UserRepo.List()
}
// @Route "POST /users"
func (s *UserServiceImpl) Create(p *domain.CreateUserRequest) (*domain.User, error) {
u := &domain.User{
Name: p.Name,
Email: p.Email,
}
return s.UserRepo.Create(u)
}
// @Route "PUT /users/{id}", middlewares=["auth", "admin"]
func (s *UserServiceImpl) Update(p *domain.UpdateUserRequest) (*domain.User, error) {
u := &domain.User{
ID: p.ID,
Name: p.Name,
Email: p.Email,
}
return s.UserRepo.Update(u)
}
// @Route "DELETE /users/{id}", middlewares=["auth", "admin"]
func (s *UserServiceImpl) Delete(p *domain.DeleteUserRequest) error {
return s.UserRepo.Delete(p.ID)
}
func Register() {
// Package auto-loaded by code generation
}
Generate Code from Annotations
Recommended: Automatic with Bootstrap
func main() {
lokstra.Bootstrap() // Auto-generates when @RouterService changes detected
// App code...
}
Manual generation (before build/deploy):
# From project root
lokstra autogen .
# Or from specific folder
lokstra autegen ./modules/user/application
# Force rebuild all (useful before deployment)
go run . --generate-only
# Generated file: zz_generated.lokstra.go
Annotation Reference
| Annotation | Purpose | Example |
|---|---|---|
@RouterService |
Define service router | @RouterService name="user-service", prefix="/api" |
@Inject |
Dependency injection | @Inject "user-repository" |
@Route |
HTTP route mapping | @Route "GET /users/{id}" |
@RouterService Parameters:
name: Service name (required)prefix: URL prefix (optional, default: “/”)middlewares: Middleware list (optional)
@Route Parameters:
- HTTP method + path pattern
- Supports path parameters:
{id},{userId}, etc. - Optional
middlewaresparameter for per-route middleware:middlewares=["mw1", "mw2"]
Example with per-route middleware:
// @Route "GET /users/{id}" // No middleware
func (s *UserService) GetByID(p *GetUserParams) (*User, error) { ... }
// @Route "POST /users", middlewares=["auth"] // Requires auth
func (s *UserService) Create(p *CreateUserParams) (*User, error) { ... }
// @Route "DELETE /users/{id}", middlewares=["auth", "admin"] // Requires auth + admin
func (s *UserService) Delete(p *DeleteUserParams) error { ... }
Middleware Usage
Built-in Middleware
import (
"github.com/primadi/lokstra/middleware/recovery"
"github.com/primadi/lokstra/middleware/request_logger"
"github.com/primadi/lokstra/middleware/slow_request_logger"
"github.com/primadi/lokstra/middleware/cors"
"github.com/primadi/lokstra/middleware/body_limit"
"github.com/primadi/lokstra/middleware/gzipcompression"
)
r := lokstra.NewRouter("api")
// 1. Recovery - catch panics
r.Use(recovery.Middleware(&recovery.Config{
EnableStackTrace: false, // Disable in production
EnableLogging: true,
}))
// 2. Request Logger
r.Use(request_logger.Middleware(&request_logger.Config{
Prefix: "API",
EnableColors: true,
SkipPaths: []string{"/health"},
}))
// 3. Slow Request Logger
r.Use(slow_request_logger.Middleware(&slow_request_logger.Config{
Threshold: 500 * time.Millisecond,
EnableColors: true,
}))
// 4. CORS
r.Use(cors.Middleware([]string{"*"}))
// 5. Body Limit
r.Use(body_limit.Middleware(&body_limit.Config{
MaxSize: 10 * 1024 * 1024, // 10MB
SkipOnPath: []string{"/upload/**"},
}))
// 6. Gzip Compression
r.Use(gzipcompression.Middleware(&gzipcompression.Config{
MinSize: 1024,
CompressionLevel: gzip.BestSpeed,
}))
Custom Middleware
func CustomAuthMiddleware(cfg *AuthConfig) request.HandlerFunc {
return request.HandlerFunc(func(ctx *request.Context) error {
// Pre-processing
token := ctx.Req.Header.Get("Authorization")
if !validateToken(token) {
return ctx.Api.Unauthorized("Invalid token")
}
// Call next handler
err := ctx.Next()
// Post-processing (optional)
return err
})
}
// Usage
r.Use(CustomAuthMiddleware(&AuthConfig{
Secret: "my-secret",
}))
Middleware in YAML
middleware-definitions:
my-auth:
type: custom-auth
config:
secret: "my-secret"
skip_paths: ["/login", "/register"]
service-definitions:
user-service:
router:
middlewares:
- my-auth
Dependency Injection
Lazy Loading Pattern
Service-level lazy loading (services created on first access):
import "github.com/primadi/lokstra/core/service"
// Define lazy reference (global variable)
var userService = service.LazyLoad[*UserService]("user-service")
var dbPool = service.LazyLoad[*DBPool]("database")
func handler() {
// First call loads service (thread-safe via sync.Once)
users := userService.MustGet().GetAll()
// Subsequent calls return cached instance (zero overhead)
user := userService.MustGet().GetByID("123")
}
Dependency Injection Pattern
Important: Dependencies are always resolved eagerly when the service is created.
type UserServiceImpl struct {
// Direct dependency injection (resolved when service created)
UserRepo user.UserRepository
}
func (s *UserServiceImpl) GetByID(id string) (*user.User, error) {
// Direct access - dependency already loaded during service creation
return s.UserRepo.GetByID(id)
}
func UserServiceFactory(deps map[string]any, config map[string]any) any {
return &UserServiceImpl{
// Direct type assertion - dependency already resolved
UserRepo: deps["user-repository"].(user.UserRepository),
}
}
Service Registration
import "github.com/primadi/lokstra/lokstra_registry"
func registerServiceTypes() {
// Register with factory functions
lokstra_registry.RegisterServiceType(
"user-service-factory", // Type name (matches config.yaml)
UserServiceFactory, // Local factory (monolith)
UserServiceRemoteFactory, // Remote factory (microservices)
)
}
Project Structure Templates
Template 1: Router Only (Simple)
myapp/
├── main.go
├── handlers.go
├── middleware.go
└── go.mod
main.go:
package main
import "github.com/primadi/lokstra"
func main() {
r := lokstra.NewRouter("api")
r.GET("/users", listUsers)
r.POST("/users", createUser)
app := lokstra.NewApp("simple", ":8080", r)
app.Run(30 * time.Second)
}
Template 2: Medium System (DDD)
myapp/
├── main.go
├── register.go
├── config.yaml
├── domain/
│ ├── user/
│ │ ├── models.go
│ │ ├── repository.go
│ │ └── service.go
│ └── order/
│ ├── models.go
│ ├── repository.go
│ └── service.go
├── repository/
│ ├── user_repository.go
│ └── order_repository.go
└── service/
├── user_service.go
└── order_service.go
Template 3: Enterprise Modular (Bounded Contexts)
myapp/
├── main.go
├── register.go
├── config.yaml
├── shared/
│ ├── errors/
│ └── types/
└── modules/
├── user/
│ ├── domain/
│ │ ├── models.go
│ │ ├── repository.go
│ │ └── service.go
│ ├── application/
│ │ └── user_service.go
│ └── infrastructure/
│ └── user_repository.go
└── order/
├── domain/
├── application/
└── infrastructure/
Template 4: Enterprise with Annotations
myapp/
├── main.go
├── register.go
├── config.yaml
└── modules/
└── user/
├── domain/
│ ├── models.go
│ ├── repository.go
│ └── service.go
├── application/
│ ├── user_service.go # Contains @RouterService, @Route
│ └── zz_generated.lokstra.go # Auto-generated
└── infrastructure/
└── user_repository.go
Generate code:
lokstra autogen ./modules/user/application
Common Patterns & Idioms
Pattern: Request/Response DTOs
// Request DTO with validation
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18,max=120"`
}
// Response DTO
type UserResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
// Handler
r.POST("/users", func(ctx *request.Context, req *CreateUserRequest) error {
user := createUser(req)
resp := &UserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
CreatedAt: user.CreatedAt,
}
return ctx.Api.Created(resp)
})
Pattern: Error Handling
import "github.com/primadi/lokstra/core/request"
func handler(ctx *request.Context) error {
// Not found
return ctx.Api.NotFound("User not found")
// Bad request
return ctx.Api.BadRequest("Invalid parameters")
// Unauthorized
return ctx.Api.Unauthorized("Invalid token")
// Forbidden
return ctx.Api.Forbidden("Access denied")
// Internal error
return ctx.Api.InternalServerError("Database connection failed")
// Custom error
return ctx.Api.ErrorWithCode(422, "Validation failed", validationErrors)
}
Pattern: Context Response Helpers
func handler(ctx *request.Context) error {
// Success responses
ctx.Api.Ok(data) // 200 OK
ctx.Api.Created(data) // 201 Created
ctx.Api.NoContent() // 204 No Content
// Error responses
ctx.Api.BadRequest(message) // 400
ctx.Api.Unauthorized(message) // 401
ctx.Api.Forbidden(message) // 403
ctx.Api.NotFound(message) // 404
ctx.Api.InternalServerError(msg) // 500
// Custom response
ctx.Api.ErrorWithCode(code, msg, data)
}
Pattern: Database Integration
import "github.com/primadi/lokstra/services/dbpool_pg"
// Register database pool
func registerServiceTypes() {
lokstra_registry.RegisterServiceType(
"database",
dbpool_pg.Factory,
nil,
)
}
config.yaml:
service-definitions:
database:
type: database
config:
dsn: "postgres://user:pass@localhost/mydb?sslmode=disable"
max_open_conns: 25
max_idle_conns: 5
Usage in repository:
type UserRepository struct {
DB *pgxpool.Pool
}
func (r *UserRepository) GetByID(id string) (*User, error) {
var user User
err := r.DB.QueryRow(context.Background(),
"SELECT id, name, email FROM users WHERE id = $1", id,
).Scan(&user.ID, &user.Name, &user.Email)
return &user, err
}
Pattern: Multiple Deployments
config.yaml:
deployments:
# Development: All services in one server
development:
servers:
monolith:
addr: ":8080"
published-services: [user-service, order-service, payment-service]
# Production: Separate microservices
production:
servers:
user-api:
addr: ":8001"
base-url: "https://user-api.example.com"
published-services: [user-service]
order-api:
addr: ":8002"
base-url: "https://order-api.example.com"
published-services: [order-service]
Run:
# Development
LOKSTRA_DEPLOYMENT=development go run .
# Production (multiple processes)
LOKSTRA_DEPLOYMENT=production LOKSTRA_SERVER=user-api go run .
LOKSTRA_DEPLOYMENT=production LOKSTRA_SERVER=order-api go run .
Troubleshooting
Common Issues
1. Service Not Found
Error:
panic: service 'user-service' not found in registry
Solution:
- For business services: Use
@RouterServiceannotation +lokstra autogen . - For infrastructure services: Check service registered:
lokstra_registry.RegisterServiceType("user-service-factory", ...) - Check config.yaml: Service name must match factory type
- Check annotation-generated file:
zz_generated.lokstra.goexists
2. Import Cycle
Error:
import cycle not allowed
Solution:
- Use domain interfaces in separate package
- Repository/Service should depend on domain, not vice versa
- Structure:
domain/→repository/→service/
3. Handler Signature Not Recognized
Error:
unsupported handler signature
Solution:
- Check handler returns error or supported type
- Use
*request.Contextfor context access - Use struct pointers for request parameters:
*CreateUserParams
4. Validation Not Working
Error:
validation tags ignored
Solution:
- Ensure struct has
validatetags:validate:"required,email" - Use pointer to struct:
func handler(params *CreateUserParams) - Import validator:
github.com/primadi/lokstra/common/validator
5. Annotation Code Not Generated
Error:
zz_generated.lokstra.go not created
Solution:
# Run code generator
lokstra autogen ./path/to/service
# Ensure annotations are correct
# @RouterService name="service-name", prefix="/api"
# @Route "GET /users/{id}"
6. Middleware Not Applied
Solution:
- Check middleware order:
recoveryshould be first - Check middleware registered: Built-in middleware auto-registered
- Verify YAML config if using config-driven middleware
Quick Reference
CLI Commands
# Install CLI
go install github.com/primadi/lokstra/cmd/lokstra@latest
# Create new project
lokstra new myapp
lokstra new myapp -template 02_app_framework/01_medium_system
# Generate code from annotations (manual)
lokstra autogen . # Current directory
lokstra autogen ./modules/user/app # Specific folder
# Force regenerate all (before deployment)
go run . --generate-only # Uses lokstra.Bootstrap()
# List templates
lokstra new --help
# Note: Use lokstra.Bootstrap() in main() for automatic generation
Import Paths
// Core
import "github.com/primadi/lokstra"
import "github.com/primadi/lokstra/core/request"
import "github.com/primadi/lokstra/core/service"
import "github.com/primadi/lokstra/core/deploy"
// Registry
import "github.com/primadi/lokstra/lokstra_registry"
// Middleware
import "github.com/primadi/lokstra/middleware/recovery"
import "github.com/primadi/lokstra/middleware/request_logger"
import "github.com/primadi/lokstra/middleware/cors"
// Services
import "github.com/primadi/lokstra/services/dbpool_pg"
import "github.com/primadi/lokstra/services/redis"
Environment Variables
# Deployment selection
LOKSTRA_DEPLOYMENT=production
# Server selection (multi-server deployment)
LOKSTRA_SERVER=api-server
# Log level
LOKSTRA_LOG_LEVEL=debug # silent, error, warn, info, debug
# Config file path
LOKSTRA_CONFIG=./config.yaml
Resources
- Documentation: https://primadi.github.io/lokstra/
- GitHub: https://github.com/primadi/lokstra
- Examples: https://primadi.github.io/lokstra/00-introduction/examples/
- Templates: https://github.com/primadi/lokstra/tree/dev2/project_templates
- Schema: https://primadi.github.io/lokstra/schema/lokstra.schema.json
AI Agent Best Practices
When Helping Programmers:
- Ask about project scale:
- Small/learning → Suggest router-only mode
- Medium/production → Suggest framework mode with annotations
- Enterprise → Suggest modular architecture with annotations
- Suggest appropriate template:
01_router/01_router_onlyfor learning02_app_framework/01_medium_systemfor production apps (2-10 entities)02_app_framework/03_enterprise_router_servicefor enterprise with annotations
- Always provide complete code:
- Include imports
- Include error handling
- Include validation tags
- Include config.yaml if using framework mode
- Use
@RouterServiceannotations for business services
- Follow project structure:
domain/for interfaces and modelsinfrastructure/for data access (repositories)application/for business logic (services with@RouterService)main.gofor bootstrap (import annotation packages)
- Use type-safe patterns:
- Direct type assertions in factory functions
service.LazyLoad[T]for lazy service loading- Prefer annotations over manual registration for business services
- Recommend annotation workflow:
- Define service with
@RouterServiceannotation - Add routes with
@Routeannotation - Run
lokstra autogen .to generate code - Manual registration only for infrastructure/custom factories
- Define service with
End of AI Agent Guide
For more details, refer to the complete documentation.