Quick Start Guide
Build your first Lokstra API in 5 minutes
π Prerequisites
Before starting, make sure you have:
# Go 1.21 or higher
go version
# go version go1.21.0 or later
π Step 1: Create Project
# Create project directory
mkdir my-first-api
cd my-first-api
# Initialize Go module
go mod init my-first-api
# Install Lokstra
go get github.com/primadi/lokstra@latest
π Step 2: Hello World (Minimal)
Create main.go:
package main
import (
"github.com/primadi/lokstra"
"time"
)
func main() {
// 1. Create router
r := lokstra.NewRouter("api")
// 2. Add route
r.GET("/ping", func() string {
return "pong"
})
// 3. Create app and run
app := lokstra.NewApp("demo", ":3000", r)
app.Run(30 * time.Second)
}
Run it:
go run main.go
# π Server starting on http://localhost:3000
Test it:
curl http://localhost:3000/ping
# "pong"
β Congratulations! Youβve built your first Lokstra API!
π― Step 3: Add More Routes
Letβs build a simple User API:
package main
import (
"fmt"
"github.com/primadi/lokstra"
"time"
)
// Data models
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// In-memory storage
var users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
var nextID = 3
func main() {
r := lokstra.NewRouter("api")
// GET /users - List all users
r.GET("/users", func() ([]User, error) {
return users, nil
})
// GET /users/{id} - Get one user
r.GET("/users/{id}", getUser)
// POST /users - Create user
r.POST("/users", createUser)
// PUT /users/{id} - Update user
r.PUT("/users/{id}", updateUser)
// DELETE /users/{id} - Delete user
r.DELETE("/users/{id}", deleteUser)
app := lokstra.NewApp("user-api", ":3000", r)
fmt.Println("π User API running on http://localhost:3000")
app.Run(30 * time.Second)
}
// Request/Response types
type GetUserRequest struct {
ID int `path:"id"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
type UpdateUserRequest struct {
ID int `path:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type DeleteUserRequest struct {
ID int `path:"id"`
}
// Handlers
func getUser(req *GetUserRequest) (*User, error) {
for _, u := range users {
if u.ID == req.ID {
return &u, nil
}
}
return nil, fmt.Errorf("user not found")
}
func createUser(req *CreateUserRequest) (*User, error) {
user := User{
ID: nextID,
Name: req.Name,
Email: req.Email,
}
nextID++
users = append(users, user)
return &user, nil
}
func updateUser(req *UpdateUserRequest) (*User, error) {
for i, u := range users {
if u.ID == req.ID {
if req.Name != "" {
users[i].Name = req.Name
}
if req.Email != "" {
users[i].Email = req.Email
}
return &users[i], nil
}
}
return nil, fmt.Errorf("user not found")
}
func deleteUser(req *DeleteUserRequest) error {
for i, u := range users {
if u.ID == req.ID {
users = append(users[:i], users[i+1:]...)
return nil
}
}
return fmt.Errorf("user not found")
}
π§ͺ Step 4: Test Your API
List all users:
curl http://localhost:3000/users
[
{"id":1,"name":"Alice","email":"alice@example.com"},
{"id":2,"name":"Bob","email":"bob@example.com"}
]
Get one user:
curl http://localhost:3000/users/1
{"id":1,"name":"Alice","email":"alice@example.com"}
Create new user:
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'
{"id":3,"name":"Charlie","email":"charlie@example.com"}
Update user:
curl -X PUT http://localhost:3000/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Smith"}'
{"id":1,"name":"Alice Smith","email":"alice@example.com"}
Delete user:
curl -X DELETE http://localhost:3000/users/2
π What Just Happened?
Letβs break down the magic:
1. Flexible Handler Signatures
// Simple return
r.GET("/users", func() ([]User, error) {
return users, nil
})
// With request binding
r.POST("/users", func(req *CreateUserRequest) (*User, error) {
return createUser(req)
})
Lokstra automatically:
- β Binds request data to structs
- β Validates with struct tags
- β Encodes response to JSON
- β Handles errors properly
2. Auto Request Binding
type GetUserRequest struct {
ID int `path:"id"` // From URL path
}
type CreateUserRequest struct {
Name string `json:"name"` // From JSON body
Email string `json:"email"`
}
Tags:
path:"id"- Extract from URL pathquery:"page"- Extract from query stringjson:"name"- Extract from JSON bodyvalidate:"required"- Validate field
3. Auto JSON Response
func getUser(req *GetUserRequest) (*User, error) {
return &user, nil // Automatically becomes JSON
}
Lokstra converts:
*Userβ JSON response with 200 OKerrorβ JSON error with 500 (or custom code)nilerror β Success response
ποΈ Step 6: Add Services & Dependency Injection
Letβs organize code with service layer and lazy loading:
package main
import (
"fmt"
"github.com/primadi/lokstra"
"github.com/primadi/lokstra/core/service"
"github.com/primadi/lokstra/lokstra_registry"
"time"
)
// Models
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Repository (data layer)
type UserRepository struct {
users map[int]*User
}
func NewUserRepository() *UserRepository {
return &UserRepository{
users: map[int]*User{
1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
2: {ID: 2, Name: "Bob", Email: "bob@example.com"},
},
}
}
func (r *UserRepository) FindAll() ([]*User, error) {
var result []*User
for _, u := range r.users {
result = append(result, u)
}
return result, nil
}
// Service (business logic layer)
type UserService struct {
Repo *service.Cached[*UserRepository]
}
func UserServiceFactory(deps map[string]any, config map[string]any) any {
return &UserService{
Repo: service.Cast[*UserRepository](deps["user-repo"]),
}
}
func (s *UserService) GetAll() ([]*User, error) {
return s.Repo.MustGet().FindAll()
}
func main() {
// 1. Register repository
lokstra_registry.RegisterServiceType("user-repo-factory",
NewUserRepository, nil)
lokstra_registry.RegisterLazyService("user-repo",
"user-repo-factory", nil)
// 2. Register service with dependency
lokstra_registry.RegisterServiceFactory("user-service-factory",
UserServiceFactory)
lokstra_registry.RegisterLazyService("user-service",
"user-service-factory",
map[string]any{
"depends-on": []string{"user-repo"},
})
// 3. Use service in handler with lazy loading
var userService = service.LazyLoad[*UserService]("user-service")
r := lokstra.NewRouter("api")
r.GET("/users", func() ([]*User, error) {
return userService.MustGet().GetAll()
})
app := lokstra.NewApp("user-api", ":3000", r)
fmt.Println("π User API with Services running on http://localhost:3000")
app.Run(30 * time.Second)
}
Whatβs happening:
- Repository - Data access layer
- Service - Business logic layer with lazy dependencies
- Registry - Service registration with factory pattern
- Lazy Loading -
service.LazyLoad[T]creates cached reference - MustGet() - Resolves service once, panics if not found
Test it:
curl http://localhost:3000/users
Benefits:
- β Separation of concerns
- β Lazy initialization (no startup order issues)
- β Thread-safe caching
- β Type-safe with generics
βοΈ Step 7: Add YAML Configuration
Scale up with configuration file:
config.yaml:
service-definitions:
user-repo:
type: user-repo-factory
user-service:
type: user-service-factory
depends-on: [user-repo]
deployments:
app:
servers:
api-server:
base-url: "http://localhost"
addr: ":3000"
published-services:
- user-service
main.go:
package main
import (
"flag"
"time"
"github.com/primadi/lokstra/lokstra_registry"
)
var server = flag.String("server", "app.api-server", "Server to run")
func main() {
flag.Parse()
// Register factories (same as before)
lokstra_registry.RegisterServiceType("user-repo-factory",
NewUserRepository, nil)
lokstra_registry.RegisterServiceFactory("user-service-factory",
UserServiceFactory)
// Load config and auto-build services + routers
lokstra_registry.LoadAndBuild([]string{"config.yaml"})
// Run server
lokstra_registry.RunServer(*server, 30*time.Second)
}
Run it:
go run main.go -server "app.api-server"
What changed:
- β No manual service registration
- β No manual router creation
- β Everything defined in YAML
- β
Services auto-wired from
depends-on - β
Routers auto-generated from
published-services
π Step 8: Multi-Deployment (Monolith β Microservices)
Same code, different deployments!
config.yaml:
service-definitions:
user-repo:
type: user-repo-factory
user-service:
type: user-service-factory
depends-on: [user-repo]
order-service:
type: order-service-factory
depends-on: [user-service] # Can be local OR remote!
deployments:
# Deployment 1: Monolith (all in one server)
monolith:
servers:
api-server:
addr: ":3000"
published-services:
- user-service
- order-service
# Deployment 2: Microservices (separate servers)
microservices:
servers:
user-server:
addr: ":3001"
published-services: [user-service]
order-server:
addr: ":3002"
published-services: [order-service]
# user-service auto-detected as remote!
Run monolith:
go run main.go -server "monolith.api-server"
# All services on :3000
Run microservices:
# Terminal 1
go run main.go -server "microservices.user-server"
# Terminal 2
go run main.go -server "microservices.order-server"
# Automatically makes HTTP calls to user-server!
Benefits:
- β Single binary
- β Zero code changes between deployments
- β Automatic remote service detection
- β Easy testing (monolith) β production (microservices)
π¨ Step 5: Add Middleware
Letβs add logging:
import (
"github.com/primadi/lokstra"
"github.com/primadi/lokstra/middleware/request_logger"
)
func main() {
r := lokstra.NewRouter("api")
// Add logging middleware
r.Use(request_logger.Middleware(nil))
// Your routes...
r.GET("/users", func() ([]User, error) {
return users, nil
})
app := lokstra.NewApp("user-api", ":3000", r)
app.Run(30 * time.Second)
}
Now every request is logged:
[INFO] GET /users 200 OK (5ms)
[INFO] POST /users 200 OK (12ms)
[INFO] GET /users/1 200 OK (3ms)
π§ Optional: Add CORS
For frontend access:
import (
"github.com/primadi/lokstra/middleware/cors"
)
func main() {
r := lokstra.NewRouter("api")
// Add CORS
corsConfig := map[string]any{
"allow_origins": []string{"*"},
"allow_methods": []string{"GET", "POST", "PUT", "DELETE"},
}
r.Use(
request_logger.Middleware(nil),
cors.Middleware(corsConfig),
)
// Routes...
app := lokstra.NewApp("user-api", ":3000", r)
app.Run(30 * time.Second)
}
π Complete Example
Hereβs the full working code:
Click to expand complete code
```go package main import ( "fmt" "github.com/primadi/lokstra" "github.com/primadi/lokstra/middleware/request_logger" "github.com/primadi/lokstra/middleware/cors" "time" ) type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` } var users = []User{ {ID: 1, Name: "Alice", Email: "alice@example.com"}, {ID: 2, Name: "Bob", Email: "bob@example.com"}, } var nextID = 3 func main() { r := lokstra.NewRouter("api") // Middleware corsConfig := map[string]any{ "allow_origins": []string{"*"}, "allow_methods": []string{"GET", "POST", "PUT", "DELETE"}, } r.Use( request_logger.Middleware(nil), cors.Middleware(corsConfig), ) // Routes r.GET("/users", func() ([]User, error) { return users, nil }) r.GET("/users/{id}", getUser) r.POST("/users", createUser) r.PUT("/users/{id}", updateUser) r.DELETE("/users/{id}", deleteUser) // Start app := lokstra.NewApp("user-api", ":3000", r) fmt.Println("π User API running on http://localhost:3000") fmt.Println("π Try: curl http://localhost:3000/users") app.Run(30 * time.Second) } // Request types type GetUserRequest struct { ID int `path:"id"` } type CreateUserRequest struct { Name string `json:"name" validate:"required"` Email string `json:"email" validate:"required,email"` } type UpdateUserRequest struct { ID int `path:"id"` Name string `json:"name"` Email string `json:"email"` } type DeleteUserRequest struct { ID int `path:"id"` } // Handlers func getUser(req *GetUserRequest) (*User, error) { for _, u := range users { if u.ID == req.ID { return &u, nil } } return nil, fmt.Errorf("user not found") } func createUser(req *CreateUserRequest) (*User, error) { user := User{ ID: nextID, Name: req.Name, Email: req.Email, } nextID++ users = append(users, user) return &user, nil } func updateUser(req *UpdateUserRequest) (*User, error) { for i, u := range users { if u.ID == req.ID { if req.Name != "" { users[i].Name = req.Name } if req.Email != "" { users[i].Email = req.Email } return &users[i], nil } } return nil, fmt.Errorf("user not found") } func deleteUser(req *DeleteUserRequest) error { for i, u := range users { if u.ID == req.ID { users = append(users[:i], users[i+1:]...) return nil } } return fmt.Errorf("user not found") } ```π― What Youβve Learned
In this guide, youβve mastered:
Basic Concepts (Steps 1-4):
- β Creating routers and routes
- β Flexible handler signatures
- β Automatic request binding
- β Automatic JSON responses
- β Error handling
- β Building REST APIs
Middleware (Step 5):
- β Adding request logging
- β CORS configuration
Advanced Features (Steps 6-8):
- β Service layer pattern
- β Dependency injection
- β Lazy loading with caching
- β YAML configuration
- β Multi-deployment (monolith β microservices)
- β Auto-wiring dependencies
You now have a production-ready foundation! π
π Next Steps
Want to Learn More?
Systematic Learning (Recommended): π Examples - 7 progressive examples (6-8 hours)
Specific Topics:
- Example 03 - CRUD API - Service layer with lazy DI
- Example 04 - Multi-Deployment - Monolith β Microservices
- Example 05 - Middleware - Custom middleware patterns
- Example 06 - External Services - Integrate third-party APIs
Architecture Deep Dive: π Architecture Guide - How Lokstra works under the hood π Why Lokstra - Philosophy and design decisions
π‘ Tips for Beginners
1. Start Simple
// Begin with this
r.GET("/users", func() ([]User, error) {
return getUsers()
})
// Add complexity as needed
r.GET("/users", func(ctx *request.Context, req *GetUsersRequest) (*response.Response, error) {
// Full control
})
2. Use Struct Tags
type Request struct {
ID int `path:"id"` // From URL
Page int `query:"page"` // From query string
Name string `json:"name"` // From body
Token string `header:"X-Token"` // From header
}
3. Lazy Loading for Services
// Package-level (cached, recommended)
var userService = service.LazyLoad[*UserService]("users")
func handler() {
users, err := userService.MustGet().GetAll()
// MustGet() panics with clear error if service not found
}
4. Print Routes for Debugging
r := lokstra.NewRouter("api")
// ... add routes ...
r.PrintRoutes() // See all registered routes
Output:
[api] GET /users -> api.GET[users]
[api] POST /users -> api.POST[users]
[api] GET /users/{id} -> api.GET[users_id]
π Having Issues?
Common Problems:
βCannot find packageβ
go get github.com/primadi/lokstra@latest
go mod tidy
βPort already in useβ
// Change port
app := lokstra.NewApp("demo", ":3001", r) // Use 3001
βHandler signature errorβ Check supported forms in Router Guide
π Resources
- Documentation: Full Docs
- Examples: Code Examples
- API Reference: API Docs
- GitHub: Report Issues
Happy coding! π