ligo

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT Imports: 9 Imported by: 0

README

Ligo

A modular Go framework with lightweight dependency injection, inspired by NestJS.

Go Version License Tests Coverage

Note: Ligo v1.0 is ready. All requirements completed including comprehensive documentation, integration tests, performance benchmarks, and stability guarantees.

Features

  • Modular Architecture - Self-contained modules with providers, controllers, and middleware
  • Dependency Injection - Automatic dependency resolution with zero boilerplate
  • Lifecycle Hooks - OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, OnApplicationShutdown
  • HTTP Routing - Adapter-agnostic router interface with Echo v5 adapter
  • Guards - Authorization with composable guard functions
  • Pipes - Validation and transformation with composable pipes
  • Interceptors - Logging, caching, and response transformation
  • Exception Filters - Error handling and HTTP response conversion
  • Type-Safe - Full type safety with generics

Installation

go get github.com/linkeunid/ligo

Quick Start

package main

import (
    "github.com/linkeunid/ligo"
    "github.com/linkeunid/ligo/adapters/echo"
)

func main() {
    router := echo.NewAdapter()
    app := ligo.New(
        ligo.WithRouter(router),
        ligo.WithAddr(":8080"),
    )

    app.Register(
        ligo.NewModule("hello",
            ligo.Providers(ligo.Value("Hello, World!")),
            ligo.Controllers(NewHelloController),
        ),
    )

    app.Run()
}

type helloController struct { msg string }

func NewHelloController(msg string) *helloController {
    return &helloController{msg: msg}
}

func (c *helloController) Routes(r ligo.Router) {
    cr := ligo.NewChainRouter(r)
    cr.GET("/", c.Hello).Handle()
}

func (c *helloController) Hello(ctx ligo.Context) error {
    return ctx.OK(map[string]string{"message": c.msg})
}

Documentation

Guides:

Features:

Roadmap

  • Current Version: 1.0 (All requirements completed)
  • Test Coverage: 208 tests passing, 50.4% coverage
  • Documentation: Complete (API guides, migration, best practices, performance, deployment, stability, microservices)
  • Next: github.com/linkeunid/ligo/microservices - Message brokers, event-driven architecture

See Roadmaps for:

Examples

See the ligo-boilerplate repository for complete examples:

  • REST API - Full CRUD operations with response helpers
  • Authentication - JWT-style auth with guards and role-based access
  • Authorization - Custom guards, roles guard, admin-only endpoints
  • File Upload - Multipart file upload with streaming downloads

See Examples Guide for detailed documentation and API usage.

Note: Database integration and microservices will be provided as separate packages (like @nestjs/typeorm and @nestjs/microservices). See Microservices Guide for implementation details.

License

MIT License - see LICENSE for details.

Documentation

Index

Constants

View Source
const (
	// LoggerText enables human-readable text logging.
	LoggerText = logger.TypeText
	// LoggerJSON enables structured JSON logging.
	LoggerJSON = logger.TypeJSON
)
View Source
const ValidatedBodyKey = http.ValidatedBodyKey

ValidatedBodyKey is the context key where ValidationPipe stores the validated body. Prefer ValidatedBody[T] over accessing this key directly.

Variables

View Source
var ErrBadRequest = http.ErrBadRequest

ErrBadRequest is wrapped by param-parsing pipes (UUIDPipe, ParseIntPipe, ParseBoolPipe) when a path parameter is invalid. Detect it with errors.Is(err, ligo.ErrBadRequest).

Functions

func Controllers

func Controllers(constructors ...any) module.ModuleOption

Controllers adds controller constructors that receive dependencies via DI. Each constructor is called with resolved dependencies and must return a Controller.

Example:

ligo.Controllers(
    func(svc *UserService) ligo.Controller {
        return &UserController{service: svc}
    },
)

func Dynamic

func Dynamic(factory func(...any) module.Module, opts ...any) module.ModuleOption

Dynamic creates a module option for dynamic modules with configuration options. The factory function receives the options and returns a configured module. This is useful for creating modules that need runtime configuration.

Example:

func RegisterConfigModule(folder string) ligo.Module {
    return ligo.NewModule("config",
        ligo.Dynamic(
            NewConfigModule,
            folder,
        ),
    )
}

func Get added in v0.3.0

func Get[T any](ctx Context, key string) T

Get retrieves a context value set by a pipe and asserts it to type T. Returns the zero value of T if the key is missing or the type does not match. Use this instead of ctx.Get(key).(T) to avoid a manual type assertion.

Example:

func (c *UserController) GetByID(ctx ligo.Context) error {
    id     := ligo.Get[int](ctx, "id")     // set by ParseIntPipe("id")
    active := ligo.Get[bool](ctx, "active") // set by ParseBoolPipe("active")
    uuid   := ligo.Get[string](ctx, "id")   // set by UUIDPipe("id")
}

func Imports

func Imports(modules ...module.Module) module.ModuleOption

Imports adds child modules to this module. Child modules can access exported providers from this module.

Example:

ligo.Imports(
    database.Module(),
    auth.Module(),
)

func Middlewares

func Middlewares(constructors ...any) module.ModuleOption

Middlewares adds middleware constructors that receive dependencies via DI. Each constructor is called with resolved dependencies and must return a Middleware.

Example:

ligo.Middlewares(
    func(logger *Logger) ligo.Middleware {
        return LoggingMiddleware(logger)
    },
)

func NewModule

func NewModule(name string, opts ...module.ModuleOption) module.Module

NewModule creates a new module with the given name and options. The name should be unique and descriptive (e.g., "user", "auth", "database").

Example:

func Module() ligo.Module {
    return ligo.NewModule("user",
        ligo.Providers(...),
        ligo.Controllers(...),
    )
}

func OnModuleDestroy

func OnModuleDestroy(fn func() error) module.ModuleOption

OnModuleDestroy adds a hook to run when the module is destroyed. Hooks are executed in reverse order during application shutdown.

Example:

ligo.OnModuleDestroy(func() error {
    return database.Close()
})

func OnModuleInit

func OnModuleInit(fn func() error) module.ModuleOption

OnModuleInit adds a hook to run when the module is initialized. Hooks are executed after all providers are registered but before the server starts.

Example:

ligo.OnModuleInit(func() error {
    return database.Connect()
})

func Providers

func Providers(providers ...any) module.ModuleOption

Providers adds providers to the module. Providers can be Values, Factories, or Transients that are registered in the DI container for this module.

Example:

ligo.Providers(
    ligo.Value("config-value"),
    ligo.Factory[*UserService](NewUserService),
    ligo.Transient[*RequestContext](NewRequestContext),
)

func ValidatedBody

func ValidatedBody[T any](ctx Context) *T

ValidatedBody retrieves the validated body stored by ValidationPipe[T]. Panics with a clear message if ValidationPipe was not added to the route.

Example:

func (c *UserController) Create(ctx ligo.Context) error {
    input := ligo.ValidatedBody[CreateUserInput](ctx)
    // input is *CreateUserInput, guaranteed non-nil
}

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App represents a Ligo application with dependency injection, module management, and HTTP server capabilities.

func New

func New(opts ...Option) *App

New creates a new Ligo application with the given options. Options include WithRouter, WithAddr, WithMiddleware, OnStart, and OnStop.

Example:

app := ligo.New(
    ligo.WithRouter(echo.NewAdapter()),
    ligo.WithAddr(":8080"),
)

func (*App) Container

func (a *App) Container() *container.Container

func (*App) Provide

func (a *App) Provide(providers ...Provider)

Provide registers global providers that are available across all modules. Providers must be registered before calling Run(). Panics if called after the application has started.

Example:

app.Provide(
    ligo.Value("config-value"),
    ligo.Factory[*Config](NewConfig),
)

func (*App) Register

func (a *App) Register(modules ...module.Module)

Register registers one or more modules with the application. Modules must be registered before calling Run(). Panics if called after the application has started.

Example:

app.Register(
    user.Module(),
    auth.Module(),
)

func (*App) Run

func (a *App) Run() error

Run starts the HTTP server and blocks until the server is shut down. It builds the DI container, registers all modules and providers, executes OnModuleInit hooks, starts the server, and waits for shutdown. On shutdown, it executes OnModuleDestroy and OnStop hooks.

Example:

if err := app.Run(); err != nil {
    log.Fatal(err)
}

type ChainRouter

type ChainRouter = http.ChainRouter

ChainRouter provides fluent chain methods for building routes. It allows chaining method calls for configuring routes.

func NewChainRouter

func NewChainRouter(r Router) ChainRouter

NewChainRouter wraps a Router with chain methods. Example:

cr := ligo.NewChainRouter(router)
cr.GET("/", handler).Guard(authGuard).Handle()

type Context

type Context = http.Context

Context wraps HTTP request/response for handlers, providing methods for accessing request data, binding bodies, and sending responses.

type Controller

type Controller = http.Controller

Controller defines how HTTP routes are registered for a module. Controllers receive dependencies via DI and register routes using the Router.

type DIError

type DIError = container.DIError

DIError is a general error type for dependency injection operations.

type ErrAppAlreadyStarted

type ErrAppAlreadyStarted struct{}

ErrAppAlreadyStarted is returned when trying to modify an app after Run() has been called. This includes calling Register() or Provide() after the application has started.

func (*ErrAppAlreadyStarted) Error

func (e *ErrAppAlreadyStarted) Error() string

type ErrCircularDependency

type ErrCircularDependency = container.ErrCircularDependency

ErrCircularDependency is returned when a circular dependency is detected in the provider graph.

type ErrControllerBinding

type ErrControllerBinding = http.ErrControllerBinding

ErrControllerBinding is returned when a controller's dependency chain cannot be fully resolved.

type ErrDuplicateProvider

type ErrDuplicateProvider = container.ErrDuplicateProvider

ErrDuplicateProvider is returned when a provider for a type is already registered. The first provider is used, and subsequent providers for the same type are ignored.

type ErrMissingDependency

type ErrMissingDependency = container.ErrMissingDependency

ErrMissingDependency is returned when a required dependency cannot be found in the container.

type ExceptionFilter

type ExceptionFilter = http.ExceptionFilter

ExceptionFilter handles errors and converts them to HTTP responses. ExceptionFilters are called when handlers or other components return errors.

type Guard

type Guard = http.Guard

Guard determines if a request should proceed (authorization). A Guard returns (true, nil) to allow the request, or (false, error) to deny it.

func AdminGuard

func AdminGuard(contextKey string) Guard

AdminGuard is a convenience guard that checks for admin role. Usage: cr.DELETE("/:id", c.Delete).Guard(ligo.AdminGuard("user"))

func RolesGuard

func RolesGuard(contextKey string, requiredRoles ...string) Guard

RolesGuard creates a guard that checks if the user has one of the required roles. Usage: cr.GET("", c.List).Guard(ligo.RolesGuard("user", "admin"))

func ThrottleGuard

func ThrottleGuard(identifierKey string, maxRequests int, window time.Duration) Guard

ThrottleGuard creates a rate-limiting guard. Usage: cr.POST("", c.Create).Guard(ligo.ThrottleGuard("ip", 10, time.Minute))

type HandlerFunc

type HandlerFunc = http.HandlerFunc

HandlerFunc is the standard handler signature for route handlers. It receives a Context and returns an error.

type HasRole

type HasRole = http.HasRole

HasRole is an interface that types can implement for role checking.

type Interceptor

type Interceptor = http.Interceptor

Interceptor wraps the entire request/response cycle. Interceptors can modify the request before processing and the response after.

func LoggingInterceptor

func LoggingInterceptor(logFunc func(start time.Time, ctx Context, err error)) Interceptor

LoggingInterceptor creates an interceptor that logs request details. Usage: cr.GET("", c.List).Intercept(ligo.LoggingInterceptor(func(start time.Time, ctx Context, err error) { ... }))

func TimeoutInterceptor

func TimeoutInterceptor(timeout time.Duration) Interceptor

TimeoutInterceptor creates an interceptor that enforces a timeout. Usage: cr.GET("", c.List).Intercept(ligo.TimeoutInterceptor(5 * time.Second))

type LifecycleHook

type LifecycleHook func(ctx any) error

LifecycleHook is a function called during app lifecycle events. The ctx parameter is context.Context for OnStart/OnStop hooks.

type Logger

type Logger = logger.Logger

Logger is the interface for framework logging.

func NewLogger

func NewLogger(opts ...LoggerOption) Logger

NewLogger creates a new logger. Default is text mode for development.

Example:

logger := ligo.NewLogger(
    ligo.WithLoggerJSON(),
    ligo.WithLoggerDebug(),
)

type LoggerField

type LoggerField = logger.Field

LoggerField is a key-value pair for structured logging.

type LoggerOption

type LoggerOption = logger.LoggerOption

LoggerOption configures the logger.

func WithLoggerDebug

func WithLoggerDebug() LoggerOption

WithLoggerDebug enables debug logging.

func WithLoggerJSON

func WithLoggerJSON() LoggerOption

WithLoggerJSON sets JSON output format for production.

func WithLoggerProduction

func WithLoggerProduction() LoggerOption

WithLoggerProduction enables JSON logging (alias for WithLoggerJSON).

func WithLoggerText

func WithLoggerText() LoggerOption

WithLoggerText sets text output format (default).

type LoggerType

type LoggerType = logger.Type

LoggerType represents the logger output format.

type Middleware

type Middleware = http.Middleware

Middleware is a function that wraps a handler to add pre/post processing. Middleware can modify the request, response, or short-circuit the handler chain.

type Module

type Module = module.Module

Module represents a self-contained unit of functionality that encapsulates providers, controllers, middleware, and lifecycle hooks.

type Option

type Option func(*options)

Option configures the App.

func OnStart

func OnStart(hook LifecycleHook) Option

OnStart adds a hook to run on app startup.

func OnStop

func OnStop(hook LifecycleHook) Option

OnStop adds a hook to run on app shutdown.

func WithAddr

func WithAddr(addr string) Option

WithAddr sets the server address.

func WithAutoPort

func WithAutoPort() Option

WithAutoPort enables automatic port increment if the default port is already in use.

func WithDebug

func WithDebug(debug bool) Option

WithDebug enables debug logging.

func WithGracefulShutdown

func WithGracefulShutdown(timeout time.Duration) Option

WithGracefulShutdown enables graceful shutdown on SIGINT/SIGTERM.

func WithJSON

func WithJSON() Option

WithJSON enables JSON logging mode (production).

func WithLogger

func WithLogger(l Logger) Option

WithLogger sets the logger.

func WithMiddleware

func WithMiddleware(mw ...Middleware) Option

WithMiddleware adds global middleware.

func WithRouter

func WithRouter(r Router) Option

WithRouter sets the HTTP router adapter.

type Pipe

type Pipe = http.Pipe

Pipe transforms input data before it reaches the handler. Pipes are used for validation, parsing, and data transformation.

func ParseBoolPipe

func ParseBoolPipe(param string) Pipe

ParseBoolPipe parses a string parameter to bool. Accepts: "true", "false", "1", "0" (case-insensitive).

Example:

cr.GET("/:active", c.Get).Pipe(ligo.ParseBoolPipe("active"))

func ParseIntPipe

func ParseIntPipe(param string) Pipe

ParseIntPipe parses a string parameter to int. Returns an error if the parameter cannot be parsed as an integer.

Example:

cr.GET("/:id", c.Get).Pipe(ligo.ParseIntPipe("id"))

func TrimPipe

func TrimPipe(param string) Pipe

TrimPipe removes leading and trailing whitespace from a string parameter.

Example:

cr.POST("", c.Create).Pipe(ligo.TrimPipe("name"))

func UUIDPipe

func UUIDPipe(param string) Pipe

UUIDPipe validates that a string parameter is a valid UUID format. Returns an error if the parameter is not a valid UUID.

Example:

cr.GET("/:uuid", c.Get).Pipe(ligo.UUIDPipe("uuid"))

func ValidationPipe

func ValidationPipe[T any](v *T) Pipe

ValidationPipe validates a struct using struct tags. It uses the "validate" tag and requires the go-playground/validator package.

Example:

type CreateUserInput struct {
    Name  string `validate:"required,min=3"`
    Email string `validate:"required,email"`
}

cr.POST("", c.Create).Pipe(ligo.ValidationPipe(&CreateUserInput{}))

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider represents a dependency provider that can be registered in the DI container. Providers can be eager values or factory functions.

func Export

func Export(p Provider) Provider

Export marks a provider as exported, making it visible to sibling modules. This allows providers to be shared across modules without being global.

Example:

ligo.Export(ligo.Factory[*Database](func() *Database {
    return NewDatabase()
}))

func Factory

func Factory[T any](fn any) Provider

Factory registers a factory function that produces a singleton. The function can have dependencies as parameters; they are auto-injected. The factory is called once, and the result is cached for subsequent resolutions.

Example:

ligo.Factory[*UserService](func(repo *UserRepository) *UserService {
    return NewUserService(repo)
})

func Transient

func Transient[T any](fn any) Provider

Transient registers a factory function that produces a new instance on each resolve. Unlike Factory, the factory function is called every time the type is resolved. Dependencies are still auto-injected.

Example:

ligo.Transient[*RequestContext](func() *RequestContext {
    return NewRequestContext()
})

func Value

func Value[T any](instance T) Provider

Value registers a pre-built instance as a singleton. The same instance will be returned for all resolutions of this type.

Example:

ligo.Value("config-value")
ligo.Value(&Config{Debug: true})

func (Provider) Eager

func (p Provider) Eager() any

Eager returns the eager value if set (for internal use).

func (Provider) Fn

func (p Provider) Fn() any

Fn returns the factory function (for internal use).

func (Provider) IsExported

func (p Provider) IsExported() bool

IsExported returns true if the provider is exported to sibling modules. Exported providers are visible to modules that import the module that exports them.

func (Provider) IsTransient

func (p Provider) IsTransient() bool

IsTransient returns true if the provider creates new instances per resolve.

func (Provider) Type

func (p Provider) Type() reflect.Type

Type returns the type this provider produces.

type RouteBuilder

type RouteBuilder = http.RouteBuilder

RouteBuilder provides fluent API for composing routes with Guards, Pipes, Interceptors, and ExceptionFilters using the builder pattern.

type Router

type Router = http.Router

Router is the HTTP router interface that all router adapters must implement. It provides methods for registering routes, middleware, and managing the HTTP server.

Directories

Path Synopsis
adapters
internal
app

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL