main.go — Entry Point

Application entry point. Loads config, connects to the database, registers routes, applies middleware, and starts the server with graceful shutdown.

package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func setupLogger(level string) {
	var logLevel slog.Level
	switch level {
	case "debug":
		logLevel = slog.LevelDebug
	case "warn":
		logLevel = slog.LevelWarn
	case "error":
		logLevel = slog.LevelError
	default:
		logLevel = slog.LevelInfo
	}
	opts := &slog.HandlerOptions{Level: logLevel}
	var handler slog.Handler
	if os.Getenv("ENV") == "production" {
		handler = slog.NewJSONHandler(os.Stdout, opts)
	} else {
		handler = slog.NewTextHandler(os.Stdout, opts)
	}
	slog.SetDefault(slog.New(handler))
}

func main() {
	cfg := LoadConfig()
	setupLogger(cfg.LogLevel)

	db, err := OpenDB(cfg.DatabaseURL)
	if err != nil {
		slog.Error("database connection failed", "err", err)
		os.Exit(1)
	}

	store := NewBookmarkStore(db)
	if err := store.Init(context.Background()); err != nil {
		slog.Error("database init failed", "err", err)
		os.Exit(1)
	}

	tmpl := parseTemplates()

	mux := http.NewServeMux()

	// Static files
	mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

	// HTML
	mux.HandleFunc("GET /", handleBookmarksList(tmpl, store))
	mux.HandleFunc("POST /bookmarks", handleBookmarksCreate(store))
	mux.HandleFunc("POST /bookmarks/{id}/delete", handleBookmarksDelete(store))

	// Health
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("ok"))
	})

	// API
	mux.HandleFunc("GET /api/bookmarks", handleListBookmarks(store))
	mux.HandleFunc("POST /api/bookmarks", handleCreateBookmark(store))
	mux.HandleFunc("GET /api/bookmarks/{id}", handleGetBookmark(store))
	mux.HandleFunc("DELETE /api/bookmarks/{id}", handleDeleteBookmark(store))

	handler := Chain(mux, RequestID, Recovery, CORS, Logger)

	srv := &http.Server{
		Addr:    ":" + cfg.Port,
		Handler: handler,
	}

	go func() {
		slog.Info("server starting", "addr", srv.Addr)
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			slog.Error("server error", "err", err)
			os.Exit(1)
		}
	}()

	// Wait for interrupt signal
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()
	<-ctx.Done()

	slog.Info("shutting down")
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Second signal forces immediate exit
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
		<-sig
		slog.Warn("forced shutdown")
		os.Exit(1)
	}()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		slog.Error("shutdown error", "err", err)
	}

	db.Close()
	slog.Info("server stopped")
}

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy