A modern, well-structured template for building Go applications using Fiber framework.
.
├── internal/
│ ├── api/ # HTTP handlers and routing
│ ├── store/ # Repository layer (database interactions)
│ ├── provider/ # Dependency injection setup
│ └── rag/ # Retrieval-Augmented Generation system
├── pkg/ # Common packages
│ └── config/ # Configuration management
├── go.mod # Go module file
├── main.go # Application entry point
└── README.md # This file
- Clean architecture with separation of concerns
- Fiber framework for high-performance HTTP routing
- Dependency injection using samber/do
- Configuration management with Viper
- Repository pattern for database operations
- Structured logging
- API versioning
- Health check endpoint
- RAG (Retrieval-Augmented Generation) system with vector search using pgvector
- Go 1.21 or higher
- PostgreSQL with pgvector extension
-
Clone the repository:
git clone https://github.com/yourusername/fullstack-golang-template.git
-
Install dependencies:
go mod download
-
Start the database:
docker-compose up -d
-
Run the application:
go run main.go
The server will start on port 3000 by default.
GET /health
- Health check endpointGET /api/v1/users
- List users (example endpoint)POST /rag/upload
- Upload a document for RAG systemGET /rag/documents
- List all documents in RAG systemPOST /rag/query
- Query documents using natural language
Configuration can be provided through a config file. Create a config.yaml
file in the configs directory:
server:
port: "3000"
db:
host: "localhost"
port: "5432"
user: "postgres"
password: "postgres"
dbname: "golang_template"
sslmode: "disable"
openai:
api_key: "your-openai-api-key-here"
embedding_model: "text-embedding-ada-002"
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
This repository contains a simplified dependency injection framework for Go applications, built on top of Uber's fx library. It provides a clean, modular approach to building applications with a focus on simplicity and maintainability.
The dependency injection (DI) framework in this codebase simplifies the use of Uber's fx library by providing:
- A clean, modular approach to organizing application components
- Type-safe dependency management using Go's generics
- Simplified registration of components that implement the same interface
- Easy composition of application modules
The framework consists of three main packages: di
, module
, and as
, which work together to provide a cohesive DI experience.
The di
package contains core components for dependency injection:
// App is a simplified wrapper around fx.App
type App struct {
options []fx.Option
}
// New creates a new App instance
func New() *App
// Provide registers constructors with the app
func (a *App) Provide(constructors ...any) *App
// Invoke registers functions to be called when the app starts
func (a *App) Invoke(functions ...any) *App
// UseModule adds a module to the app
func (a *App) UseModule(module fx.Option) *App
// Start builds and runs the application
func (a *App) Start(timeout time.Duration) error
Helper functions for annotating constructors:
// ByValue creates a constructor that returns the provided value
func ByValue[T any](t T) func() T
// AsInterface annotates a constructor to implement a specific interface
func AsInterface[T any](ctr any, group string) any
// AsGroup annotates a constructor to be part of a specific group
func AsGroup(ctr any, group string) any
// AsGroupConsumer annotates a function to consume items from a specific group
func AsGroupConsumer(ctr any, group string) any
// GroupCollector creates a collector function for a specific type and group
func GroupCollector[T any](group string) any
The module
package provides a fluent API for creating modular application components:
// Module is a helper for creating fx modules with a fluent API
type Module struct {
name string
options []fx.Option
}
// New creates a new Module with the given name
func New(name string) *Module
// Provide registers constructors with the module
func (m *Module) Provide(constructors ...any) *Module
// Invoke registers functions to be called when the application starts
func (m *Module) Invoke(functions ...any) *Module
// ImportModule imports another Module as a child module
func (m *Module) ImportModule(module *Module) *Module
// AsFxModule returns the module as an fx.Option
func (m *Module) AsFxModule() fx.Option
The as
package provides utilities for type-based dependency management:
// Interface creates a type group for interface implementations
func Interface[T any](groupName string) struct {
// Register a constructor that returns a T implementation
Register func(any) any
// Collector returns a constructor that collects all T implementations
Collector func() any
}
// Struct creates a type group for concrete types
func Struct[T any](groupName string) struct {
// Register a constructor that returns a T
Register func(any) any
// Collector returns a constructor that collects all T instances
Collector func() any
}
The application is organized into several modules, each encapsulating a specific piece of functionality. All modules follow a consistent pattern:
- Define type groups at the top of the file
- Create a
Module()
function that returns a configured module - Register providers and invokers using the module's methods
The server module handles HTTP servers using the Fiber framework.
Key components:
- Controllers for handling HTTP requests
- Middleware groups for request processing
- Validation rules for request data
- Fiber configuration and wrapper functions
// Define type groups for dependency injection
var (
controllers = as.Interface[controller.Controller]("servercontrollers")
validationRules = as.Interface[validation.Rule]("validationrules")
fiberAppWrappers = as.Struct[FiberAppWrapper]("fiberappwrappers")
middlewareGroups = as.Struct[MiddlewareGroup]("middlewaregroups")
fiberConfigWrappers = as.Struct[FiberConfigWrapper]("fiberconfigwrappers")
)
func Module(option ...Option) *module.Module {
opts := buildOptions(option...)
m := module.New("server")
// Register providers
m.Provide(
// Options
di.ByValue(opts),
// Controllers
controllers.Collector(),
di.AsGroupConsumer(parseControllers, "servercontrollers"),
// Swagger definitions
getSwaggerDefs,
// Middlewares and validation
validationRules.Collector(),
fiberAppWrappers.Collector(),
middlewareGroups.Collector(),
fiberConfigWrappers.Collector(),
// Server components
defaultFiber,
New,
)
// Register invocations
m.Invoke(
validation.Init,
startServer,
)
return m
}
The client module provides HTTP client functionality using the Resty library.
Key components:
- Factory for creating HTTP clients
- Driver wrappers for customizing client behavior
- Round-tripper wrappers for HTTP transport customization
// Define type groups for dependency injection
var (
driverWrappers = as.Struct[DriverWrapper]("driverupdater")
roundTripperWrappers = as.Struct[RoundTripperWrapper]("roundtripperwrapper")
)
func Module() *module.Module {
m := module.New("rest")
// Register providers
m.Provide(
NewFactory,
driverWrappers.Collector(),
roundTripperWrappers.Collector(),
buildRoundTripperWrapper,
// Register the context binder as a driver wrapper
driverWrappers.Register(withCtxBinder),
)
return m
}
The OpenTelemetry module provides tracing and observability.
Key components:
- Tracer provider configuration
- Client instrumentation
- Server middleware for request tracing
func Module(opts ...Option) *module.Module {
o := applyOptions(opts...)
m := module.New(ModuleName)
// Register providers
m.Provide(
di.ByValue(o),
startOtel,
)
// Import child modules if any
for _, sub := range o.subModules {
m.ImportModule(sub)
}
return m
}
The OTEL module also provides client and server instrumentation through submodules:
func WithClient() Option {
m := module.New("otel-client").Provide(newClientRoundTripperWrapper)
return WithSubModule(m)
}
func WithServer() Option {
m := module.New("otel-server").Provide(newMiddlewareGroup)
return WithSubModule(m)
}
The Swagger module handles API documentation generation.
Key components:
- Swagger document builder
- Fiber wrapper for serving Swagger UI
func Module() *module.Module {
m := module.New("swagger")
// Register providers
m.Provide(
buildDocs,
fiberWrapper,
)
return m
}
Here's how to create a new module that provides some service functionality:
package mymodule
import (
"github.com/onerciller/fullstack-golang-template/pkg/as"
"github.com/onerciller/fullstack-golang-template/pkg/di"
"github.com/onerciller/fullstack-golang-template/pkg/module"
)
// Define an interface for our service
type Service interface {
Name() string
Execute() error
}
// Define type groups
var (
services = as.Interface[Service]("myservices")
)
// Implementation of the Service interface
type MyService struct {}
func NewMyService() *MyService {
return &MyService{}
}
func (s *MyService) Name() string {
return "MyService"
}
func (s *MyService) Execute() error {
// Implementation
return nil
}
// ServiceUsers use the service collection
type ServiceOrchestrator struct {
services []Service
}
func NewServiceOrchestrator(services []Service) *ServiceOrchestrator {
return &ServiceOrchestrator{services: services}
}
func (o *ServiceOrchestrator) RunAll() error {
for _, svc := range o.services {
if err := svc.Execute(); err != nil {
return err
}
}
return nil
}
// Module function exports this component
func Module() *module.Module {
m := module.New("mymodule")
m.Provide(
// Register the service implementation
services.Register(NewMyService),
// Register the collector to collect all services
services.Collector(),
// Register the orchestrator that uses all services
NewServiceOrchestrator,
)
m.Invoke(
// Start the orchestrator when the application starts
func(orchestrator *ServiceOrchestrator) {
orchestrator.RunAll()
},
)
return m
}
Type groups are a powerful feature for collecting multiple implementations of the same interface:
// Define a handler interface
type Handler interface {
Handle(ctx context.Context, req Request) (Response, error)
}
// Create a type group for handlers
var handlers = as.Interface[Handler]("handlers")
// Register different handler implementations
m.Provide(
handlers.Register(NewUserHandler),
handlers.Register(NewProductHandler),
handlers.Register(NewOrderHandler),
handlers.Collector(), // Collects all handlers
)
// Create a function that uses all handlers
func UseAllHandlers(handlers []Handler) {
// Use all registered handlers
}
// Register the consumer with the module
m.Provide(
di.AsGroupConsumer(UseAllHandlers, "handlers"),
)
Here's how to configure and start your application:
package main
import (
"time"
"github.com/onerciller/fullstack-golang-template/modules"
"github.com/onerciller/fullstack-golang-template/pkg/app"
"github.com/onerciller/fullstack-golang-template/pkg/logger"
)
func main() {
// Create a new app
application := app.New()
// Configure the application
application.WithOption(
app.WithConfigPath("configs/config.yaml"),
app.WithTimeout(30 * time.Second),
)
// Add modules
application.UseModule(modules.GetModule("otel").AsFxModule())
application.UseModule(modules.GetModule("client").AsFxModule())
application.UseModule(modules.GetModule("server").AsFxModule())
application.UseModule(modules.GetModule("swagger").AsFxModule())
// Or alternatively, use all modules at once
// for _, option := range modules.GetAllModuleOptions() {
// application.UseModule(option)
// }
// Register additional components
application.Provide(
NewMyService,
NewMyHandler,
)
// Start the application
logger.Fatal(application.Start())
}
- Organize by Feature: Create modules around features or domains, not technical concerns
- Use Type Groups: Use the
as
package to group similar components - Keep Modules Small: Focus each module on a specific responsibility
- Use Meaningful Names: Name your modules, groups, and components clearly
- Document Dependencies: Document the dependencies between your modules
- Test in Isolation: Each module should be testable in isolation
- Avoid Circular Dependencies: Design your modules to avoid circular dependencies
- Use Options for Configuration: Use option functions for module configuration
- Register Lifecycle Hooks: Use
fx.Lifecycle
to handle startup and shutdown correctly - Use Error Groups: Use
errgroup.Group
for coordinating concurrent components