niyam-api / main.go
axyut
user with no auth
973ad5c
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
// IMPORTANT: Adjust these import paths to match your Go module path
// (e.g., github.com/your-username/your-repo-name/internal/...)
"github.com/axyut/niyamAPI/internal/config"
"github.com/axyut/niyamAPI/internal/db"
"github.com/axyut/niyamAPI/internal/handler"
"github.com/axyut/niyamAPI/internal/service"
"github.com/axyut/niyamAPI/internal/utils"
_ "github.com/danielgtaylor/huma/v2/formats/cbor" // Huma format package for CBOR
)
func main() {
// 1. Initialize Uptime Tracking
// Call this as early as possible in the main function to accurately track
// the application's uptime from its true starting point.
utils.InitUptime()
// 2. Load Application Configuration
// This step reads environment variables (including those from .env file via godotenv)
// and parses them into a structured AppConfig. Fatal exit if essential config is missing.
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalf("FATAL: Failed to load application configuration: %v", err)
}
log.Printf("INFO: Application environment: %s", cfg.Environment)
log.Printf("INFO: Configured API Public URL: %s", cfg.PublicURL)
// 3. Initialize Database Client
// Establish connection to MongoDB using the loaded configuration.
// This is a critical dependency, so a fatal exit occurs on failure.
dbClient, err := db.NewDBClient(cfg) // Pass configuration to DB client for connection URI
if err != nil {
log.Fatalf("FATAL: Failed to initialize database client: %v", err)
}
// Defer closing the database connection. This ensures the connection is
// gracefully closed when the main function exits (e.g., on shutdown signal).
defer func() {
if err := dbClient.Close(); err != nil {
log.Printf("ERROR: Error closing database client: %v", err)
}
}()
// 4. Initialize Services (Business Logic Layer)
// Services encapsulate the core business logic of your application. They depend on
// external resources like the database client.
svc := service.NewServices(dbClient, cfg) // Pass dbClient to the service layer
// 5. Create new Chi router
// Chi is a lightweight, idiomatic router for building HTTP services in Go.
router := chi.NewMux()
// Apply Chi middleware for request logging and panic recovery.
// `middleware.Logger` provides structured logging for each incoming HTTP request,
// showing method, path, status, and duration.
router.Use(middleware.Logger)
// `middleware.Recoverer` catches any panics that occur within handlers,
// logs the stack trace, and prevents the entire server from crashing,
// returning a 500 Internal Server Error to the client.
router.Use(middleware.Recoverer)
// 6. Create Huma API instance
// Huma builds on top of Chi (via humachi adapter) to provide powerful features
// like automatic OpenAPI 3.0 documentation generation and request/response validation.
apiConfig := huma.DefaultConfig("Niyam API", "1.0.0")
apiConfig.Info.Description = "API/Backend service for the Niyam application."
api := humachi.New(router, apiConfig)
// 7. Register API Handlers
// Instantiate the Handlers struct, passing it all its essential dependencies:
// application configuration, business services, and the database client.
// Then, call its method to register all defined API endpoints with the Huma API.
hndlrs := handler.NewHandlers(cfg, svc, dbClient) // Pass config, services, and dbClient to handlers
hndlrs.RegisterHandlers(api)
// // 8. Start HTTP Server
// // The server listens on the port specified in the configuration.
listenAddr := fmt.Sprintf(":%d", cfg.Port)
// listener, err := net.Listen("tcp", listenAddr)
// if err != nil {
// log.Fatalf("FATAL: Server failed to listen on %s: %v", listenAddr, err)
// }
// defer listener.Close() // Ensure the listener is closed when main function exits.
// // Log the actual server listening address (e.g., ":7860" or "[::]:7860")
// // and the publicly accessible documentation URL, which uses the PublicURL from config.
// log.Printf("INFO: Server listening on %s (Access docs at %s/docs)\n", listener.Addr().String(), cfg.PublicURL)
// Dynamically Get the Actual Host and Port (for logging)
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatalf("Server failed to listen on %s: %v", listenAddr, err)
}
defer listener.Close()
// Extract just the port number from the listener's address
_, portStr, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
log.Fatalf("Failed to parse listener address: %v", err)
}
publicBaseURL := fmt.Sprintf("http://localhost:%s", portStr)
appEnv := os.Getenv("APP_ENV")
if publicURL := os.Getenv("API_PUBLIC_URL"); publicURL != "" && appEnv != "development" {
publicBaseURL = publicURL
}
// Use the constructed publicBaseURL in the log message
log.Printf("Server listening on %s (Access docs at %s/docs)\n", listener.Addr().String(), publicBaseURL)
// Configure the HTTP server with explicit timeouts for read, write, and idle operations.
// This helps prevent resource exhaustion and improves server robustness.
srv := &http.Server{
Addr: listener.Addr().String(), // Server address determined by listener
Handler: router, // The Chi router handles all incoming requests
ReadTimeout: 5 * time.Second, // Maximum duration for reading the entire request
WriteTimeout: 10 * time.Second, // Maximum duration before timing out writes of the response
IdleTimeout: 120 * time.Second, // Maximum amount of time to wait for the next request when keep-alives are enabled
}
// Start the HTTP server in a separate goroutine.
// This allows the main goroutine to proceed to set up graceful shutdown.
go func() {
log.Printf("INFO: Starting HTTP server on %s...", srv.Addr)
// srv.Serve() blocks until the server closes or an error occurs.
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
// Log a fatal error if the server fails to start or serve requests unexpectedly,
// ensuring the application exits.
log.Fatalf("FATAL: HTTP server failed: %v", err)
}
log.Println("INFO: HTTP server stopped.")
}()
// 9. Graceful Shutdown
// Set up a channel to receive OS signals. This allows the application to respond
// to termination requests (e.g., Ctrl+C, `docker stop`).
quit := make(chan os.Signal, 1)
// Notify the 'quit' channel upon receiving SIGINT (Ctrl+C) or SIGTERM (termination request).
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Block the main goroutine until an OS signal is received on the 'quit' channel.
sig := <-quit
log.Printf("INFO: Received signal '%s'. Shutting down server gracefully...", sig)
// Create a context with a timeout for the graceful shutdown process.
// If the server doesn't shut down within this duration, it will be forcefully closed.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // Ensure the context's cancel function is called to release resources.
// Attempt to gracefully shut down the HTTP server.
// Existing connections are given time to complete before the server is stopped.
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("FATAL: Server shutdown failed: %v", err)
}
log.Println("INFO: Server gracefully shut down.")
}