Skip to content

onerciller/full-stack-golang-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fullstack Golang Template

A modern, well-structured template for building Go applications using Fiber framework.

Project Structure

.
├── 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

Features

  • 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

Getting Started

Prerequisites

  • Go 1.21 or higher
  • PostgreSQL with pgvector extension

Installation

  1. Clone the repository:

    git clone https://github.com/yourusername/fullstack-golang-template.git
  2. Install dependencies:

    go mod download
  3. Start the database:

    docker-compose up -d
  4. Run the application:

    go run main.go

The server will start on port 3000 by default.

API Endpoints

  • GET /health - Health check endpoint
  • GET /api/v1/users - List users (example endpoint)
  • POST /rag/upload - Upload a document for RAG system
  • GET /rag/documents - List all documents in RAG system
  • POST /rag/query - Query documents using natural language

Configuration

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"

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Simplified Dependency Injection Framework

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.

Table of Contents

Overview

The dependency injection (DI) framework in this codebase simplifies the use of Uber's fx library by providing:

  1. A clean, modular approach to organizing application components
  2. Type-safe dependency management using Go's generics
  3. Simplified registration of components that implement the same interface
  4. 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.

Key Components

The di Package

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

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

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
}

Module Structure

The application is organized into several modules, each encapsulating a specific piece of functionality. All modules follow a consistent pattern:

  1. Define type groups at the top of the file
  2. Create a Module() function that returns a configured module
  3. Register providers and invokers using the module's methods

Server Module

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
}

Client Module

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
}

OTEL Module

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)
}

Swagger Module

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
}

Usage Examples

Creating a Module

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
}

Working with Type Groups

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"),
)

Configuring the Application

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())
}

Best Practices

  1. Organize by Feature: Create modules around features or domains, not technical concerns
  2. Use Type Groups: Use the as package to group similar components
  3. Keep Modules Small: Focus each module on a specific responsibility
  4. Use Meaningful Names: Name your modules, groups, and components clearly
  5. Document Dependencies: Document the dependencies between your modules
  6. Test in Isolation: Each module should be testable in isolation
  7. Avoid Circular Dependencies: Design your modules to avoid circular dependencies
  8. Use Options for Configuration: Use option functions for module configuration
  9. Register Lifecycle Hooks: Use fx.Lifecycle to handle startup and shutdown correctly
  10. Use Error Groups: Use errgroup.Group for coordinating concurrent components

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages