Skip to content

Commit dc185ec

Browse files
committed
feat: major improvements and bug fixes
- Fix nil pointer dereference panic in OpenTelemetry middleware - Add Redis configuration variables to .env.example - Optimize application.go with DRY principles and better structure - Add comprehensive health check endpoints with cache monitoring - Implement proper middleware initialization and error handling - Add nil checks in OpenTelemetry middleware for graceful degradation - Improve application lifecycle management with structured shutdown - Add cache package with Redis implementation and health checks - Update dependencies and improve code organization - All changes pass linting with 0 issues Resolves: OpenTelemetry middleware panic issue Enhances: Application stability and maintainability
1 parent fe72d3b commit dc185ec

File tree

21 files changed

+1429
-179
lines changed

21 files changed

+1429
-179
lines changed

.env.example

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
1-
# Database Configuration
1+
# ───────────────────────────────────────────────────────────
2+
# 🌟 APPLICATION CONFIGURATION
3+
# ───────────────────────────────────────────────────────────
4+
DEBUG=false
5+
DISABLE_LOGS=false
6+
LOG_FORMAT=json
7+
LOG_CALLER=true
8+
LOG_STACKTRACE=false
9+
10+
# ───────────────────────────────────────────────────────────
11+
# 🌟 SERVER CONFIGURATION
12+
# ───────────────────────────────────────────────────────────
13+
SERVER_ADDR=
14+
SERVER_PORT=8080
15+
16+
# ───────────────────────────────────────────────────────────
17+
# 🌟 DATABASE CONFIGURATION (MySQL)
18+
# ───────────────────────────────────────────────────────────
219
DB_HOST=localhost
320
DB_PORT=3306
421
DB_USER=user
522
DB_PASSWORD=password
623
DB_NAME=mydatabase
724

8-
# Server Configuration
9-
SERVER_ADDR=
10-
SERVER_PORT=8080
25+
# ───────────────────────────────────────────────────────────
26+
# 🌟 REDIS CACHE CONFIGURATION
27+
# ───────────────────────────────────────────────────────────
28+
REDIS_HOST=localhost
29+
REDIS_PORT=6379
30+
REDIS_PASSWORD=your_redis_password
31+
REDIS_DB=0
1132

12-
# Jaeger Tracing Configuration
33+
# ───────────────────────────────────────────────────────────
34+
# 🌟 JAEGER TRACING CONFIGURATION
35+
# ───────────────────────────────────────────────────────────
1336
JAEGER_AGENT_HOST=localhost
1437
JAEGER_AGENT_PORT=6831
1538

16-
# Optional: Override default values for your specific environment
17-
# DB_HOST=your-database-host
18-
# DB_PORT=3306
19-
# DB_USER=your-database-user
20-
# DB_PASSWORD=your-database-password
21-
# DB_NAME=your-database-name
22-
# SERVER_PORT=8080
39+
# ───────────────────────────────────────────────────────────
40+
# 🌟 TOOL VERSIONS (for development)
41+
# ───────────────────────────────────────────────────────────
42+
SWAG_VERSION=latest
43+
MIGRATE_VERSION=v4.16.2
44+
LINT_VERSION=v1.55.2
45+
IMPORTS_VERSION=v0.14.0
46+
VULN_VERSION=v1.0.1

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ env: ## 📝 CHECK & COPY .env IF MISSING
4646
echo -e "$(RED)⚠️ .env file not found! Creating from .env.example...$(NC)"; \
4747
cp .env.example .env; \
4848
echo -e "$(GREEN)✅ .env file created successfully!$(NC)"; \
49+
echo -e "$(YELLOW)📝 Please update Redis configuration in .env file:$(NC)"; \
50+
echo -e "$(BLUE) REDIS_HOST=localhost$(NC)"; \
51+
echo -e "$(BLUE) REDIS_PORT=6379$(NC)"; \
52+
echo -e "$(BLUE) REDIS_PASSWORD=your_redis_password$(NC)"; \
53+
echo -e "$(BLUE) REDIS_DB=0$(NC)"; \
4954
else \
5055
echo -e "$(GREEN)✅ .env file exists!$(NC)"; \
5156
fi

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ The main ones are:
4040
- [prometheus/client_golang](https://github.com/prometheus/client_golang) for metrics
4141
- [otel](https://opentelemetry.io/) for observability
4242
- [jaeger](https://www.jaegertracing.io/) for distributed tracing
43+
- [Redis](github.com/redis/go-redis/v9) for cache
4344

4445
## 🎯 Quick Start (Using Template)
4546

cmd/server/main.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package main
22

33
import (
4-
"github.com/MitulShah1/golang-rest-api-template/config"
4+
"github.com/MitulShah1/golang-rest-api-template/internal/application"
55
_ "github.com/MitulShah1/golang-rest-api-template/internal/handlers/category/model"
6-
"github.com/MitulShah1/golang-rest-api-template/package/logger"
76
)
87

98
// @title REST API Template Example
@@ -28,17 +27,16 @@ import (
2827
// @externalDocs.description OpenAPI
2928
// @externalDocs.url https://swagger.io/resources/open-api/
3029
func main() {
31-
// Initialize the logger
32-
log := logger.NewLogger(logger.DefaultOptions())
30+
// Create and initialize the application
31+
app := application.NewApplication()
3332

34-
// Initialize the configuration
35-
config := config.NewService()
36-
if err := config.Init(); err != nil {
37-
log.Fatal("error while initize app", "error", err.Error())
33+
// Initialize all application components
34+
if err := app.Initialize(); err != nil {
35+
app.GetLogger().Fatal("failed to initialize application", "error", err.Error())
3836
}
3937

40-
// Run the application
41-
if err := config.Run(); err != nil {
42-
log.Fatal("error while run app", "error", err.Error())
38+
// Run the application (this will handle graceful shutdown)
39+
if err := app.Run(); err != nil {
40+
app.GetLogger().Fatal("application failed", "error", err.Error())
4341
}
4442
}

config/config.go

Lines changed: 44 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,20 @@
44
package config
55

66
import (
7-
"context"
8-
"net/http"
97
"os"
10-
"os/signal"
118
"strconv"
12-
"syscall"
13-
"time"
149

15-
"github.com/MitulShah1/golang-rest-api-template/internal/handlers"
16-
"github.com/MitulShah1/golang-rest-api-template/package/database"
17-
"github.com/MitulShah1/golang-rest-api-template/package/logger"
18-
"github.com/MitulShah1/golang-rest-api-template/package/middleware"
1910
"github.com/joho/godotenv"
20-
tracesdk "go.opentelemetry.io/otel/sdk/trace"
2111
)
2212

23-
// Service holds application configuration and manages the application lifecycle.
13+
// Service holds application configuration.
2414
// It includes database, server, and telemetry configuration.
2515
type Service struct {
2616
Name string
27-
Logger *logger.Logger
28-
Server *handlers.Server
2917
dbEnv DBConfig
18+
redisEnv RedisConfig
3019
srvConfg ServerConf
3120
jaegerConfig JaegerConfig
32-
db *database.Database
33-
tp *tracesdk.TracerProvider
3421
}
3522

3623
type DBConfig struct {
@@ -41,6 +28,13 @@ type DBConfig struct {
4128
Name string
4229
}
4330

31+
type RedisConfig struct {
32+
Host string
33+
Port string
34+
Password string
35+
DB int
36+
}
37+
4438
type ServerConf struct {
4539
Address string
4640
Port string
@@ -57,98 +51,17 @@ func NewService() *Service {
5751
}
5852
}
5953

60-
// Init initializes the application configuration, including loading environment variables,
61-
// initializing the logger, database connection, and server. It returns an error if any
62-
// of the initialization steps fail.
63-
func (cnf *Service) Init() (err error) {
64-
// initialize logger
65-
cnf.Logger = logger.NewLogger(logger.DefaultOptions())
66-
67-
// Load environment variables
68-
if err := cnf.LoadConfig(); err != nil {
69-
return err
70-
}
71-
72-
// initialize database
73-
cnf.db, err = database.NewDatabase(&database.DBConfig{
74-
Host: cnf.dbEnv.Host,
75-
Port: cnf.dbEnv.Port,
76-
User: cnf.dbEnv.User,
77-
Password: cnf.dbEnv.Password,
78-
DBName: cnf.dbEnv.Name,
79-
})
80-
if err != nil {
81-
return err
82-
}
83-
84-
cnf.Logger.Info("Database connection successful")
85-
86-
agentPort, _ := strconv.Atoi(cnf.jaegerConfig.AgentPort)
87-
tmCnfg := middleware.TelemetryConfig{
88-
Host: cnf.jaegerConfig.AgentHost,
89-
Port: agentPort,
90-
ServiceName: "go-rest-api-template",
91-
}
92-
93-
// initialize tracer
94-
cnf.tp, err = tmCnfg.InitTracer()
95-
if err != nil {
96-
return err
97-
}
98-
99-
cnf.Logger.Info("Tracer initialized")
100-
101-
// initialize server
102-
serverAddr := cnf.srvConfg.Address + ":" + cnf.srvConfg.Port
103-
if cnf.Server, err = handlers.NewServer(serverAddr, cnf.Logger, cnf.db, &tmCnfg); err != nil {
104-
return err
105-
}
106-
107-
return nil
108-
}
109-
110-
// Run starts the server and listens for termination signals.
111-
// It runs the server in a goroutine and waits for a termination signal (SIGINT or SIGTERM).
112-
// When a termination signal is received, it gracefully shuts down the server.
113-
func (cnf *Service) Run() error {
114-
// Channel to listen for termination signals
115-
stop := make(chan os.Signal, 1)
116-
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
117-
118-
// Run server in a goroutine
119-
go func() {
120-
cnf.Logger.Info("Starting server port: " + cnf.srvConfg.Port)
121-
if err := cnf.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
122-
cnf.Logger.Error("Server error", "error", err)
123-
os.Exit(1)
124-
}
125-
}()
126-
127-
// Wait for termination signal
128-
<-stop
129-
cnf.Logger.Info("Shutting down server...")
130-
131-
// Graceful shutdown
132-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
133-
defer cancel()
134-
135-
// Shutdown the tracer provider
136-
if err := cnf.tp.Shutdown(ctx); err != nil {
137-
cnf.Logger.Error("Failed to shutdown tracer provider", "error", err)
138-
}
139-
140-
if err := cnf.Server.ServerDown(ctx); err != nil {
141-
cnf.Logger.Error("Server shutdown failed", "error", err)
142-
} else {
143-
cnf.Logger.Info("Server exited gracefully")
144-
}
145-
return nil
54+
// Init initializes the application configuration by loading environment variables.
55+
// It returns an error if the configuration loading fails.
56+
func (cnf *Service) Init() error {
57+
return cnf.LoadConfig()
14658
}
14759

60+
// LoadConfig loads configuration from environment variables
14861
func (cnf *Service) LoadConfig() error {
14962
// loads environment variables from .env file
15063
if err := godotenv.Load(); err != nil {
151-
cnf.Logger.Warn("no .env file found, using system environment variables")
64+
return err
15265
}
15366

15467
cnf.dbEnv = DBConfig{
@@ -159,6 +72,15 @@ func (cnf *Service) LoadConfig() error {
15972
Name: getEnv("DB_NAME", "mydatabase"),
16073
}
16174

75+
// Redis config
76+
redisDB, _ := strconv.Atoi(getEnv("REDIS_DB", "0"))
77+
cnf.redisEnv = RedisConfig{
78+
Host: getEnv("REDIS_HOST", "localhost"),
79+
Port: getEnv("REDIS_PORT", "6379"),
80+
Password: getEnv("REDIS_PASSWORD", ""),
81+
DB: redisDB,
82+
}
83+
16284
// Server config
16385
cnf.srvConfg = ServerConf{
16486
Address: getEnv("SERVER_ADDR", ""),
@@ -181,3 +103,23 @@ func getEnv(key, defaultValue string) string {
181103
}
182104
return defaultValue
183105
}
106+
107+
// GetDBConfig returns the database configuration
108+
func (cnf *Service) GetDBConfig() DBConfig {
109+
return cnf.dbEnv
110+
}
111+
112+
// GetRedisConfig returns the Redis configuration
113+
func (cnf *Service) GetRedisConfig() RedisConfig {
114+
return cnf.redisEnv
115+
}
116+
117+
// GetServerConfig returns the server configuration
118+
func (cnf *Service) GetServerConfig() ServerConf {
119+
return cnf.srvConfg
120+
}
121+
122+
// GetJaegerConfig returns the Jaeger configuration
123+
func (cnf *Service) GetJaegerConfig() JaegerConfig {
124+
return cnf.jaegerConfig
125+
}

docker-compose.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,44 @@ services:
5252
VULN_VERSION: ${VULN_VERSION}
5353
JAEGER_AGENT_HOST: ${JAEGER_AGENT_HOST}
5454
JAEGER_AGENT_PORT: ${JAEGER_AGENT_PORT}
55+
REDIS_HOST: ${REDIS_HOST}
56+
REDIS_PORT: ${REDIS_PORT}
57+
REDIS_PASSWORD: ${REDIS_PASSWORD}
58+
REDIS_DB: ${REDIS_DB}
5559
container_name: go_app
5660
restart: always
5761
depends_on:
5862
db:
5963
condition: service_healthy # ✅ Waits for MySQL to be ready
64+
redis:
65+
condition: service_healthy # ✅ Waits for Redis to be ready
6066
ports:
6167
- ${SERVER_PORT}:8080
6268
env_file:
6369
- .env # ✅ Load environment variables from .env
6470
networks:
6571
- app_network
6672

73+
# ───────────────────────────────────────────────────────────
74+
# 🌟 REDIS CACHE
75+
# ───────────────────────────────────────────────────────────
76+
redis:
77+
image: redis:7-alpine
78+
container_name: redis_cache
79+
restart: always
80+
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
81+
ports:
82+
- ${REDIS_PORT}:6379
83+
volumes:
84+
- redis_data:/data
85+
healthcheck:
86+
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
87+
interval: 10s
88+
retries: 5
89+
timeout: 3s
90+
networks:
91+
- app_network
92+
6793
# ───────────────────────────────────────────────────────────
6894
# 🌟 JAEGER
6995
# ───────────────────────────────────────────────────────────
@@ -91,3 +117,4 @@ networks:
91117

92118
volumes:
93119
db_data:
120+
redis_data:

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/joho/godotenv v1.5.1
1313
github.com/prometheus/client_golang v1.23.0
1414
github.com/prometheus/client_model v0.6.2
15+
github.com/redis/go-redis/v9 v9.12.0
1516
github.com/stretchr/testify v1.10.0
1617
github.com/swaggo/http-swagger/v2 v2.0.2
1718
github.com/swaggo/swag v1.16.6
@@ -28,6 +29,7 @@ require (
2829
github.com/beorn7/perks v1.0.1 // indirect
2930
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3031
github.com/davecgh/go-spew v1.1.1 // indirect
32+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
3133
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
3234
github.com/go-logr/logr v1.4.3 // indirect
3335
github.com/go-logr/stdr v1.2.2 // indirect
@@ -37,6 +39,7 @@ require (
3739
github.com/go-openapi/swag v0.19.15 // indirect
3840
github.com/go-playground/locales v0.14.1 // indirect
3941
github.com/go-playground/universal-translator v0.18.1 // indirect
42+
github.com/go-redis/redismock/v9 v9.2.0 // indirect
4043
github.com/google/uuid v1.6.0 // indirect
4144
github.com/josharian/intern v1.0.0 // indirect
4245
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect

0 commit comments

Comments
 (0)