Why Lokstra?

Understanding the problems Lokstra solves and when to use it


πŸ€” The Problem

Building REST APIs in Go, you typically face these challenges:

1. Standard Library is Too Low-Level

// stdlib net/http - verbose boilerplate
func main() {
    mux := http.NewServeMux()
    
    // Manual routing
    mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        if r.Method != "GET" {
            http.Error(w, "Method not allowed", 405)
            return
        }
        
        // Manual JSON parsing
        var users []User
        // ... database code ...
        
        // Manual JSON encoding
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(users)
    })
    
    http.ListenAndServe(":8080", mux)
}

Problems:


Gin / Echo / Chi

Great frameworks, but:

// Handler signature is fixed
func GetUsers(c *gin.Context) {
    // Must use c.JSON(), c.Bind(), etc
    // Locked into framework-specific types
}

Problems:


3. Enterprise Frameworks Too Heavy

Complex frameworks with:


πŸ’‘ The Lokstra Solution

Lokstra addresses these problems with a balanced approach:

1. Flexible Handler Signatures

Problem: Most frameworks force one pattern

Lokstra: Write handlers that make sense for your use case

// Simple - no params, no errors
r.GET("/ping", func() string {
    return "pong"
})

// With error handling
r.GET("/users", func() ([]User, error) {
    return db.GetAllUsers()
})

// With request binding
r.POST("/users", func(req *CreateUserRequest) (*User, error) {
    return db.CreateUser(req)
})

// Full control
r.GET("/complex", func(ctx *request.Context) (*response.Response, error) {
    // Access headers, cookies, etc.
    return response.Success(data), nil
})

29 different handler forms supported! Use what fits your needs.


2. Service-First Architecture

Problem: Business logic scattered in handlers

Lokstra: Services are first-class citizens

// Define service
type UserService struct {
    DB *Database
}

// Business logic in service
func (s *UserService) GetAll() ([]*User, error) {
    return s.DB.FindAll()
}

// ❌ Not optimal - looks up service in map on EVERY request
r.GET("/users", func() ([]*User, error) {
    users := lokstra_registry.GetService[*UserService]("users")
    return users.GetAll()
})

// βœ… Optimal - service-level lazy loading (recommended)
var userService = service.LazyLoad[*UserService]("users")

r.GET("/users", func() ([]*User, error) {
    return userService.MustGet().GetAll()
})
// First call: Creates service & resolves dependencies
// Subsequent calls: Returns cached instance (fast!)

// OR: Auto-generate router from service!
router := lokstra_registry.NewRouterFromServiceType("user-service")
// Creates routes automatically using conventions + metadata

3. Built-in Dependency Injection

Problem: Manual dependency wiring or external DI containers

Lokstra: Simple, built-in DI with lazy loading

Simple Registration (No Dependencies)

// Register simple service
lokstra_registry.RegisterServiceType("db-factory", 
    NewDatabase, nil)

lokstra_registry.RegisterLazyService("db", 
    "db-factory", nil)

With Dependencies (Factory Pattern)

// Register service with dependencies
lokstra_registry.RegisterServiceFactory("users-factory", 
    func() any {
        return &UserService{
            DB: service.LazyLoad[*Database]("db"),
        }
    })

lokstra_registry.RegisterLazyService("users",
    "users-factory", 
    map[string]any{
        "depends-on": []string{"db"},
    })

Use in Handlers with Lazy Loading

// ❌ Not optimal: Registry lookup on every request
r.GET("/users", func() ([]User, error) {
    users := lokstra_registry.GetService[*UserService]("users")
    return users.GetAll()  // Map lookup happens on EVERY request
})

// βœ… Optimal: Cached service resolution (recommended)
var userService = service.LazyLoad[*UserService]("users")

r.GET("/users", func() ([]User, error) {
    return userService.MustGet().GetAll()  // Cached after first access
})

Why service.LazyLoad is faster:

  1. First call: Looks up service, caches reference
  2. Subsequent calls: Returns cached instance (zero map lookup)
  3. Thread-safe: Safe for concurrent requests
  4. Zero allocation: No repeated registry lookups

Benefits:


4. Deploy Anywhere Without Code Changes

Problem: Monolith β†’ Microservices requires rewrite

Lokstra: Configure deployment, not code

# Same code, different deployment!

deployments:
  # Monolith: All services in one server
  monolith:
    servers:
      api-server:
        addr: ":8080"
        published-services:
          - users
          - orders
          - payments

  # Microservices: Separate servers per service
  microservices:
    servers:
      user-server:
        base-url: "http://localhost"
        addr: ":8001"
        published-services: [users]
      
      order-server:
        base-url: "http://localhost"
        addr: ":8002"
        published-services: [orders, payments]

Change deployment with just a flag:

./app -server "monolith.api-server"           # Run as monolith
./app -server "microservices.user-server"     # User microservice
./app -server "microservices.order-server"    # Order microservice

One binary, infinite architectures!


5. Annotation-Driven Development (Zero Boilerplate)

Problem: Setting up services with DI and routing requires tons of boilerplate

Lokstra: Annotations like NestJS decorators, but with zero runtime cost!

The Traditional Way (70+ Lines!)

// 1. Define service
type UserService struct {
    DB *Database
}

// 2. Create factory
func UserServiceFactory(deps map[string]any, config map[string]any) any {
    return &UserService{
        DB: deps["db"].(*Database),
    }
}

// 3. Register factory
lokstra_registry.RegisterServiceFactory("user-service-factory", 
    createUserServiceFactory())

// 4. Register lazy service
lokstra_registry.RegisterLazyService("user-service", 
    "user-service-factory",
    map[string]any{"depends-on": []string{"db"}})

// 5. Create router
func setupUserRouter() *lokstra.Router {
    userService := lokstra_registry.GetService[*UserService]("user-service")
    r := router.NewFromService(userService, "/api")
    return r
}

// 6. Register router
lokstra_registry.RegisterRouter("user-router", setupUserRouter())

// ... and more YAML config!

The Lokstra Annotation Way (12 Lines!)

// @RouterService name="user-service", prefix="/api"
type UserServiceImpl struct {
    // @Inject "database"
    DB *Database
}

// @Route "GET /users"
func (s *UserServiceImpl) GetAll(p *GetAllRequest) ([]User, error) {
    return s.DB.GetAllUsers()
}

// @Route "POST /users"
func (s *UserServiceImpl) Create(p *CreateUserRequest) (*User, error) {
    return s.DB.CreateUser(p)
}

// Auto-generates: factory, DI wiring, routes, remote proxy!

Results:

How It Works

# Run once to generate code
go run . --generate-only

# Or use build scripts (auto-generates before building)
./build.sh           # Linux/Mac
.\build.ps1          # Windows PowerShell
.\build.bat          # Windows CMD

Generates zz_generated.lokstra.go:

// βœ… Service factory
func init() {
    lokstra_registry.RegisterServiceFactory("user-service-factory", ...)
    lokstra_registry.RegisterLazyService("user-service", ...)
}

// βœ… Router with routes
func init() {
    r := lokstra.NewRouter("user-service")
    r.GET("/users", ...) // Auto-wired!
    lokstra_registry.RegisterRouter("user-service", r)
}

// βœ… Remote proxy for microservices
type UserServiceRemote struct { ... }

Three Powerful Annotations:

  1. @RouterService - Define service + router
    // @RouterService name="user-service", prefix="/api", mount="/api"
    type UserServiceImpl struct {}
    
  2. @Inject - Dependency injection
    // @Inject "database"
    DB *service.Cached[*Database]
    
  3. @Route - HTTP endpoints
    // @Route "GET /users/{id}"
    func (s *UserServiceImpl) GetByID(p *GetByIDRequest) (*User, error) {}
    

Comparison with Other Frameworks:

Framework Pattern Runtime Cost Boilerplate
NestJS Decorators High (reflection) Low
Spring Annotations High (reflection) Low
Lokstra Annotations Zero (codegen) Very Low

Lokstra advantage: All the DX benefits, none of the runtime cost!

πŸ“– Full guide: Example 07 - Enterprise Router Service


πŸ“Š Comparison Matrix

Feature stdlib Gin/Echo Chi Lokstra
Handler Flexibility ❌ ⚠️ 1 form ⚠️ 1 form βœ… 29 forms
Auto JSON Response ❌ βœ… ⚠️ βœ…
Service Layer ❌ ❌ ❌ βœ… Built-in
Dependency Injection ❌ ❌ ❌ βœ… Built-in
Service Caching ❌ ❌ ❌ βœ… Lazy Load
Service as Router ❌ ❌ ❌ βœ… Unique
Annotations ❌ ❌ ❌ βœ… 83% less code
Config-Driven Deploy ❌ ⚠️ Limited ❌ βœ… Full
Multi-Deployment ❌ ❌ ❌ βœ… 1 binary
Middleware System ⚠️ Basic βœ… βœ… βœ… Enhanced
Learning Curve Easy Easy Easy Medium
Boilerplate High Low Medium Very Low
Type Safety βœ… ⚠️ βœ… βœ…
Performance ⚑⚑⚑ ⚑⚑⚑ ⚑⚑⚑ ⚑⚑⚑

Legend:


βœ… When to Use Lokstra

Perfect For:

1. REST APIs (Sweet Spot)

// Build REST APIs fast with less code
r := lokstra.NewRouter("api")
r.GET("/users", getUsers)
r.POST("/users", createUser)
r.PUT("/users/{id}", updateUser)

2. Microservices Architecture

# Same code, different servers
servers:
  - name: user-service
  - name: order-service
  - name: payment-service

3. Monolith with Migration Plan

# Start as monolith
deployments:
  monolith:
    servers:
      api-server:
        addr: ":8080"
        published-services: [users, orders, payments]

# Later: Split to microservices (no code change!)
deployments:
  microservices:
    servers:
      user-server:
        addr: ":8001"
        published-services: [users]
      order-server:
        addr: ":8002"
        published-services: [orders, payments]

4. Service-Heavy Applications

// Rich business logic in services
type OrderService struct {
    Users     *UserService
    Payments  *PaymentService
    Inventory *InventoryService
}

5. Multi-Environment Deployments

# dev.yaml
deployments:
  dev:
    servers:
      api-server:
        addr: ":3000"
        published-services: [users, orders]

# prod.yaml
deployments:
  prod:
    servers:
      api-server:
        addr: ":80"
        published-services: [users, orders]

🚫 When NOT to Use Lokstra

Consider Alternatives For:

1. GraphQL-First APIs

Lokstra is optimized for REST. For GraphQL:

2. Pure gRPC Services

Lokstra focuses on HTTP/REST. For gRPC:

3. Static File Servers

// Just serving files? stdlib is enough
http.FileServer(http.Dir("./static"))

4. Learning Go

If you’re new to Go:

5. Extreme Performance Requirements

If you need absolute fastest (microseconds matter):

6. Simple CRUD with Database Only

If it’s just database CRUD with no business logic:


🎯 Lokstra’s Philosophy

Core Principles:

1. Convention over Configuration

Smart defaults, configure only when needed:

// Minimal config - just works
r := lokstra.NewRouter("api")
r.GET("/users", getUsers)

2. Service-Oriented

Business logic belongs in services:

// Not in handlers
func handler() { /* business logic */ }  // ❌

// In services
type UserService struct {}
func (s *UserService) CreateUser() {}    // βœ…

3. Flexible, Not Opinionated

Multiple ways to solve problems:

// Use what fits your needs
r.Use(middleware.Direct())     // Option 1
r.Use("middleware_name")       // Option 2

4. Production-Ready

Built for real applications:

5. Developer Experience

Make developers happy:


πŸš€ Getting Started

Convinced? Here’s what to do next:

1. Quick Evaluation (5 minutes)

πŸ‘‰ Quick Start Guide - Build your first API

2. Deep Understanding (20 minutes)

πŸ‘‰ Architecture - How Lokstra works internally

3. Learn by Doing (6-8 hours)

πŸ‘‰ Examples - 7 progressive examples from basics to production

4. Deep Dive (as needed)

πŸ‘‰ Router Guide - Comprehensive reference


πŸ’­ Still Deciding?

Common Questions:

Q: Is Lokstra mature enough for production?
A: Yes! Already used in production applications. Active development and maintenance.

Q: How’s the performance?
A: Comparable to Gin/Echo. Not as fast as raw fasthttp, but fast enough for 99% of use cases.

Q: Can I migrate from Gin/Echo?
A: Yes! Gradual migration is possible. Start with new features in Lokstra, keep existing code.

Q: What about community and support?
A: Growing community, active GitHub discussions, comprehensive docs, and responsive maintainers.

Q: Is it stable? Breaking changes?
A: API is stabilizing. We follow semantic versioning. Breaking changes only in major versions.


οΏ½ What’s Coming Next?

Lokstra is actively evolving. Here’s what’s on the horizon:

Next Release Priorities

🎨 HTMX Support - Modern Web Apps Made Easy

Build interactive web applications without complex JavaScript:

// Coming soon!
r.GET("/users", func() templ.Component {
    users := userService.GetAll()
    return views.UserList(users)  // Returns HTMX-ready component
})

r.POST("/users", func(req *CreateUserReq) templ.Component {
    user := userService.Create(req)
    return views.UserRow(user)  // Partial update
})

Features:


πŸ› οΈ CLI Tools - Developer Productivity

Speed up development with command-line tools:

# Create new project
lokstra new my-api --template=rest-api

# Generate boilerplate
lokstra generate service user
lokstra generate router api
lokstra generate middleware auth

# Development server with hot reload
lokstra dev --port 3000

# Database migrations
lokstra migrate create add_users_table
lokstra migrate up

Features:


πŸ“¦ Complete Standard Library - Production Ready

Essential middleware and services out of the box:

Middleware:

// Metrics and monitoring
r.Use(middleware.Prometheus())
r.Use(middleware.OpenTelemetry())

// Authentication
r.Use(middleware.JWT(jwtConfig))
r.Use(middleware.OAuth2(oauthConfig))
r.Use(middleware.BasicAuth(users))

// Rate limiting
r.Use(middleware.RateLimit(100, time.Minute))

// Security
r.Use(middleware.CSRF())
r.Use(middleware.SecureHeaders())

Services:

// Health checks
health := lokstra_registry.GetService[*HealthService]("health")
health.AddCheck("database", dbHealthCheck)
health.AddCheck("cache", cacheHealthCheck)

// Metrics
metrics := lokstra_registry.GetService[*MetricsService]("metrics")
metrics.RecordRequest(duration, statusCode)

// Distributed tracing
tracer := lokstra_registry.GetService[*TracingService]("tracing")
span := tracer.StartSpan(ctx, "user.create")
defer span.End()

Features:


Future Vision

Beyond Next Release:


Community & Contributions

Want to help shape Lokstra’s future?

Visit: github.com/primadi/lokstra


οΏ½πŸ“š Learn More


Ready to build better APIs? πŸš€