This guide will walk you through the complete process of cloning this repository as a template, setting it up, and adding a new module from scratch.
- Prerequisites
- Step 1: Clone the Repository
- Step 2: Setup Project
- Step 3: Configure the Project
- Step 4: Start Infrastructure
- Step 5: Add a New Module
- Step 6: Register the Module
- Step 7: Generate Code
- Step 8: Run Migrations
- Step 9: Test Your Module
- Step 10: Run the Server
Before you begin, ensure you have the following installed:
- Go 1.24+ - Download Go
- Docker & Docker Compose - Install Docker
- Git - For cloning the repository
Clone this repository to use it as a template for your project:
# Clone the repository
git clone https://github.com/LoopContext/go-modulith-template.git my-project
cd my-project
# Remove the existing git history (optional, if you want a fresh start)
rm -rf .git
git init
git add .
git commit -m "Initial commit from go-modulith-template"Note: If you're using this as a GitHub template, you can use the "Use this template" button on GitHub, which will create a new repository with a clean history.
For the fastest setup, use the automated quickstart script:
just quickstartThis will automatically:
- Validate your environment
- Install missing development tools
- Start Docker infrastructure
- Run database migrations
- Optionally run seed data
Install all required development tools:
just install-depsThis will install:
migrate- Database migration toolsqlc- Type-safe SQL code generatorbuf- Protocol buffer compilerair- Hot reload tool for developmentgolangci-lint- Go lintergqlgen- GraphQL code generator (optional)mockgen- Mock generator for testing
Verify installations:
# Check that tools are installed
which migrate sqlc buf air golangci-lint
# Or run validation
just validate-setupIf you want to change the Go module name, update go.mod:
# Edit go.mod and change the module path
# From: module github.com/LoopContext/go-modulith-template
# To: module github.com/your-org/your-projectThen update all imports in the codebase. You can use a find-and-replace tool or script.
The project uses a flexible configuration system with the following priority:
- Environment variables (highest priority)
.envfileconfigs/server.yaml(default configuration)
Edit configs/server.yaml:
env: dev
log_level: debug # debug, info, warn, error
http_port: 8000
grpc_port: 9000
service_name: modulith-server
db_dsn: postgres://postgres:postgres@localhost:5432/modulith_demo?sslmode=disable
# Database connection pool settings
db_max_open_conns: 25
db_max_idle_conns: 25
db_conn_max_lifetime: 5m
db_connect_timeout: 10s
# Timeouts
read_timeout: 5s # HTTP server read timeout
write_timeout: 10s # HTTP server write timeout
shutdown_timeout: 30s # Graceful shutdown timeout
auth:
jwt_secret: your-secret-key-at-least-32-bytes-long-change-thisCreate a .env file (optional):
# Copy example if available, or create new
cat > .env <<EOF
ENV=dev
LOG_LEVEL=debug
HTTP_PORT=8000
GRPC_PORT=9000
DB_DSN=postgres://postgres:postgres@localhost:5432/modulith_demo?sslmode=disable
JWT_SECRET=your-secret-key-at-least-32-bytes-long-change-this
EOFImportant: Change the
JWT_SECRETto a secure random string (at least 32 bytes) before deploying to production.
Note: If you used
just quickstart, this step is already complete. Skip to Step 5.
Start the required infrastructure services (PostgreSQL, Valkey, etc.):
just docker-upThis starts:
- PostgreSQL - Main database (port 5432)
- Valkey - Cache and session storage (port 6379)
- Jaeger - Distributed tracing UI http://localhost:16686
- Prometheus - Metrics collection http://localhost:9090
- Grafana - Visualization dashboards (http://localhost:3000, user:
admin, password:admin)
Verify services are running:
docker-compose ps
# Or run comprehensive diagnostics
just doctorYou should see all services in "Up" status.
Tip: To start only the database and Valkey (faster startup), use
just docker-up-minimal.
Now let's add a new module. For this example, we'll create an "order" module:
just new-module orderThis command will:
- Create the module directory structure
- Generate boilerplate code from templates
- Create migration files
- Create proto definitions
- Generate Air configuration for hot reload
- Update
sqlc.yamlwith the new module
Generated structure:
modules/order/
├── module.go # Module implementation
├── internal/
│ ├── service/
│ │ └── service.go # Business logic
│ ├── repository/
│ │ └── repository.go # Data access layer
│ └── db/
│ └── query/
│ └── order.sql # SQL queries
└── resources/
└── db/
├── migration/ # Database migrations
└── seed/ # Seed data
proto/order/v1/
└── order.proto # gRPC service definition
cmd/order/
└── main.go # Standalone service entry point
configs/
└── order.yaml # Module configuration
.air.order.toml # Hot reload config for this moduleWhat was generated:
- Module structure - Complete module with service, repository, and database layers
- gRPC service - Protocol buffer definition for the module
- Database migrations - Initial schema migration files
- SQL queries - Template SQL queries for sqlc
- Configuration - Module-specific YAML configuration
- Standalone binary - Entry point to run the module independently
After scaffolding, you need to register the module in the main server. Edit cmd/server/setup/registry.go:
Find the RegisterModules function:
// RegisterModules registers all modules with the registry.
func RegisterModules(reg *registry.Registry) {
// Register all modules here
reg.Register(auth.NewModule())
// Add more modules as needed:
// reg.Register(order.NewModule())
// reg.Register(payment.NewModule())
}Add your new module:
// RegisterModules registers all modules with the registry.
func RegisterModules(reg *registry.Registry) {
// Register all modules here
reg.Register(auth.NewModule())
reg.Register(order.NewModule()) // Add this line
// Add more modules as needed:
// reg.Register(payment.NewModule())
}Add the import at the top of the file:
import (
// ... existing imports ...
"github.com/LoopContext/go-modulith-template/modules/auth"
"github.com/LoopContext/go-modulith-template/modules/order" // Add this line
// ... rest of imports ...
)Now generate the code from your proto definitions and SQL queries:
Generate Go code from your Protocol Buffer definitions:
just protoThis will:
- Generate gRPC service code from
proto/order/v1/order.proto - Create client and server stubs
- Generate OpenAPI/Swagger documentation in
gen/openapiv2/
Generate type-safe Go code from your SQL queries:
just sqlcThis will:
- Generate Go code from SQL queries in
modules/order/internal/db/query/ - Create type-safe database access code in
modules/order/internal/db/store/ - Generate repository interfaces
Verify generated code:
# Check that files were generated
ls -la gen/go/proto/order/v1/
ls -la modules/order/internal/db/store/Run database migrations to create the schema for your new module:
just migrateOr run migrations manually using the subcommand:
go run cmd/server/main.go migrateOr using the flag:
go run cmd/server/main.go -migrateThis will:
- Discover all modules with migrations
- Run migrations in order
- Create database tables for your new module
Verify migrations:
# Connect to the database
psql postgres://postgres:postgres@localhost:5432/modulith_demo
# List tables
\dt
# Check migration versions (each module has its own migration tracking)
SELECT * FROM schema_migrations;You should see tables for your new module (e.g., orders table if you created an order module).
Note: Migrations run automatically when you start the server. The modulith discovers and applies migrations for all registered modules.
Run tests to verify everything works:
just testOr run tests for a specific module:
go test ./modules/order/...If you're writing tests that require mocks:
just generate-mocksThis generates mocks for all interfaces in your modules.
Run the linter to ensure code quality:
just lintFix any issues reported by the linter.
Now you're ready to run the server with your new module!
Run the monolith server with hot reload:
just devThis will:
- Start the gRPC server (port 9000 by default, configurable)
- Start the HTTP gateway (port 8000 by default, configurable)
- Automatically reload on code changes
- Monitor changes in
.go,.yaml,.env,.proto, and.sqlfiles - Run migrations automatically on startup
Run your module as a standalone service:
just dev-module orderThis runs only the order module with hot reload.
Build and run without hot reload:
# Build the server
just build
# Run it
./bin/server# Liveness probe
curl http://localhost:8000/livez
# Readiness probe (checks all dependencies)
curl http://localhost:8000/readyz
# WebSocket health
curl http://localhost:8000/healthz/wsIf you have grpcurl installed:
# List services
grpcurl -plaintext localhost:9000 list
# List methods for your service
grpcurl -plaintext localhost:9000 list order.v1.OrderServiceIn development mode, Swagger UI is available at:
http://localhost:8000/swagger-ui/You can explore and test your API endpoints here.
The server logs will show:
- Module initialization
- Migration status
- Server startup
- Request logs
Look for messages like:
Starting application version=...
Module 'order' initialized successfully
✅ Migrations completed successfully
Starting gRPC server port=9000
Starting HTTP Gateway port=8000Write unit tests for your service and repository layers:
# Run unit tests for your module
go test ./modules/order/... -v
# Run with coverage
go test ./modules/order/... -coverFor integration tests that require a real database, use testcontainers:
# Run integration tests (requires Docker)
just test-integration
# Or run specific integration tests
go test -v -run Integration ./examples/...Example Integration Test:
See examples/integration_test_example.go for a complete example showing:
- Setting up PostgreSQL with testcontainers
- Running migrations in tests
- Testing service methods end-to-end
- Verifying event bus integration
- Testing repository layer with real database
Key Points:
- Integration tests should be marked with
-run Integrationflag - Use
testing.Short()to skip integration tests in short mode - Always clean up test data and containers in
deferblocks - Use the
testutilpackage for testcontainer setup
# Generate coverage report
just coverage-report
# View HTML coverage report
just coverage-htmlNow that you have a working module, you can:
-
Customize the Module
- Edit
modules/order/internal/service/service.goto add business logic - Update
modules/order/internal/repository/repository.gofor data access - Add more SQL queries in
modules/order/internal/db/query/
- Edit
-
Add More Methods
- Update
proto/order/v1/order.prototo add new RPC methods - Run
just prototo regenerate code - Implement the methods in your service
- Update
-
Add Database Migrations
just migrate-create MODULE=order NAME=add_indexes
This creates new migration files in
modules/order/resources/db/migration/ -
Add Seed Data
- Edit
modules/order/resources/db/seed/001_example_data.sql - Run
just seedorgo run cmd/server/main.go seed - Note: The module must implement
SeedPath()method inmodule.go(automatically included when usingjust new-module)
- Edit
-
Add Tests
- Write unit tests in
modules/order/internal/service/service_test.go - Write integration tests using testcontainers
- Write unit tests in
-
Configure Module Settings
- Edit
configs/order.yamlfor module-specific configuration - Access configuration in your module via
registry.Config()
- Edit
Before troubleshooting, run diagnostic tools:
# Comprehensive environment diagnostics
just doctor
# Validate setup and prerequisites
just validate-setupSolution: Make sure you:
- Added the import for your module in
cmd/server/setup/registry.go - Called
reg.Register(order.NewModule())inRegisterModulesfunction - Ran
go mod tidyto update dependencies - The module path in the import matches your actual module path
Solution:
- Check database connection string in
configs/server.yaml - Ensure PostgreSQL is running:
docker-compose psorjust doctor - Check migration files are valid SQL
- Verify database container is healthy:
docker ps
Solution:
- Verify
bufis installed:which buforjust validate-setup - Check
buf.yamlandbuf.gen.yamlare correct - Ensure proto files are valid:
buf lint
Solution:
- Check
sqlc.yamlhas correct paths - Verify SQL queries are valid
- Ensure migration files exist for schema
Solution:
- Run
just doctorto check environment health - Check logs for specific errors
- Verify all required environment variables are set
- Ensure database is accessible:
just doctorwill check this - Check ports 8000 and 9000 are not in use:
just validate-setupshows port status - Verify Docker containers are running:
docker-compose ps
Solution:
- Run
just doctorto identify which ports are in use - Stop conflicting services or change ports in
configs/server.yaml - Check
docker-compose psfor running containers
You've successfully:
- ✅ Cloned the repository
- ✅ Installed all dependencies
- ✅ Configured the project
- ✅ Started infrastructure
- ✅ Created a new module
- ✅ Registered the module
- ✅ Generated code (proto + sqlc)
- ✅ Run migrations
- ✅ Tested the module
- ✅ Started the server
Your modulith application is now running with your new module! 🎉
For more information, see: