server

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 16, 2025 License: MIT Imports: 14 Imported by: 0

README

GoBlogServ SDK

The GoBlogServ SDK allows you to embed blog functionality into your own Go applications.

Installation

go get github.com/harrydayexe/GoBlog/pkg/server

Quick Start

package main

import (
    "log"
    "net/http"
    "github.com/harrydayexe/GoBlog/pkg/server"
)

func main() {
    // Create server with default options
    blogServer, err := server.New(server.DefaultOptions())
    if err != nil {
        log.Fatal(err)
    }
    defer blogServer.Close()

    // Create router and attach blog routes
    mux := http.NewServeMux()
    blogServer.AttachRoutes(mux, "/blog")

    // Start server
    http.ListenAndServe(":8080", mux)
}

Environment Variables

GoBlogServ supports configuration via environment variables, making it perfect for Docker and cloud deployments.

Loading from Environment
// Load configuration from environment variables
opts, err := server.LoadFromEnv()
if err != nil {
    log.Fatal(err)
}

// Or panic if configuration is invalid
opts := server.MustLoadFromEnv()

blogServer, _ := server.New(opts)
Available Variables
Variable Type Default Description
GOBLOG_CONTENT_PATH string ./posts Path to markdown posts
GOBLOG_CACHE_ENABLED bool true Enable Ristretto cache
GOBLOG_CACHE_MAX_MB int64 100 Max cache size in MB
GOBLOG_CACHE_TTL duration 60m Cache TTL (e.g., 30m, 1h)
GOBLOG_SEARCH_ENABLED bool true Enable Bleve search
GOBLOG_SEARCH_INDEX_PATH string ./blog.bleve Search index path
GOBLOG_REBUILD_INDEX bool false Rebuild index on startup
GOBLOG_POSTS_PER_PAGE int 10 Posts per page
GOBLOG_VERBOSE bool false Verbose logging
Docker Example
FROM your-app-base

ENV GOBLOG_CONTENT_PATH=/app/posts \
    GOBLOG_CACHE_MAX_MB=200 \
    GOBLOG_VERBOSE=true

CMD ["/app/yourapp"]
# docker-compose.yml
services:
  blog:
    image: your-blog-app
    environment:
      GOBLOG_CONTENT_PATH: /app/posts
      GOBLOG_CACHE_ENABLED: "true"
      GOBLOG_VERBOSE: "true"
    volumes:
      - ./posts:/app/posts:ro

API Reference

Creating a Server
func New(opts Options) (*Server, error)

Creates a new blog server with the given options.

Example:

opts := server.DefaultOptions()
opts.ContentPath = "./posts"
opts.EnableCache = true
opts.EnableSearch = true

blogServer, err := server.New(opts)
Attaching Routes
func (s *Server) AttachRoutes(mux *http.ServeMux, basePath string)

Attaches blog routes to your HTTP mux at the specified base path.

Routes created:

  • GET {basePath} - Blog index with pagination and search
  • GET {basePath}/posts/{slug} - Individual post view
  • GET {basePath}/tags/{tag} - Posts filtered by tag
  • GET {basePath}/search - Search endpoint (returns HTMX partial)

Example:

mux := http.NewServeMux()
blogServer.AttachRoutes(mux, "/blog")
// Now blog is available at /blog/*
Server Methods
Reload
func (s *Server) Reload() error

Reloads all content from disk and rebuilds the search index.

Example:

// Reload after files change
if err := blogServer.Reload(); err != nil {
    log.Printf("Failed to reload: %v", err)
}
Close
func (s *Server) Close() error

Closes the server and cleans up resources (cache, search index).

Example:

defer blogServer.Close()
Stats
func (s *Server) Stats() Stats

Returns server statistics.

Example:

stats := blogServer.Stats()
fmt.Printf("Total posts: %d\n", stats.TotalPosts)
fmt.Printf("Cache hit ratio: %.2f\n", stats.CacheHitRatio)
GetLoader
func (s *Server) GetLoader() *content.Loader

Returns the content loader for advanced usage.

Example:

loader := blogServer.GetLoader()
posts := loader.GetAll()
posts := loader.GetByTag("golang")
GetIndex
func (s *Server) GetIndex() *search.Index

Returns the search index for advanced usage.

Example:

index := blogServer.GetIndex()
results, _ := index.Search("web development", 10)

Options

DefaultOptions
func DefaultOptions() Options

Returns sensible defaults:

  • ContentPath: "./posts"
  • EnableCache: true
  • CacheMaxMB: 100
  • CacheTTL: 60 * time.Minute
  • EnableSearch: true
  • SearchIndexPath: "./blog.bleve"
  • RebuildIndex: false
  • PostsPerPage: 10
  • Verbose: false
Options Struct
type Options struct {
    ContentPath      string        // Path to markdown posts (required)
    EnableCache      bool          // Enable Ristretto cache
    CacheMaxMB       int64         // Max cache size in MB
    CacheTTL         time.Duration // Cache TTL
    EnableSearch     bool          // Enable Bleve search
    SearchIndexPath  string        // Path to Bleve index
    RebuildIndex     bool          // Rebuild index on startup
    PostsPerPage     int           // Posts per page
    Verbose          bool          // Enable verbose logging
}

Stats Struct

type Stats struct {
    TotalPosts    int       // Total number of published posts
    AllTags       []string  // All unique tags
    CacheHits     uint64    // Cache hit count
    CacheMisses   uint64    // Cache miss count
    CacheHitRatio float64   // Hit ratio (0.0 - 1.0)
    IndexedDocs   uint64    // Number of indexed documents
}

Error Handling

The SDK defines several error types:

var (
    ErrInvalidContentPath  = errors.New("invalid content path")
    ErrInvalidCacheSize    = errors.New("cache size must be at least 1MB")
    ErrInvalidPostsPerPage = errors.New("posts per page must be at least 1")
    ErrServerNotStarted    = errors.New("server not started")
)

Example:

blogServer, err := server.New(opts)
if errors.Is(err, server.ErrInvalidContentPath) {
    log.Fatal("Content path does not exist")
}

Advanced Examples

Multiple Blogs

Host multiple independent blogs:

techBlog, _ := server.New(server.Options{
    ContentPath: "./tech-posts",
    EnableSearch: true,
})
techBlog.AttachRoutes(mux, "/tech")

personalBlog, _ := server.New(server.Options{
    ContentPath: "./personal-posts",
    EnableCache: true,
})
personalBlog.AttachRoutes(mux, "/personal")
Custom Statistics Endpoint
mux.HandleFunc("GET /api/blog/stats", func(w http.ResponseWriter, r *http.Request) {
    stats := blogServer.Stats()
    json.NewEncoder(w).Encode(stats)
})
Hot Reload

Watch for file changes and reload:

// Using fsnotify or similar
watcher.Add(opts.ContentPath)
for {
    select {
    case <-watcher.Events:
        log.Println("Reloading blog content...")
        if err := blogServer.Reload(); err != nil {
            log.Printf("Reload failed: %v", err)
        }
    }
}
Disable Features

Run without caching or search:

opts := server.DefaultOptions()
opts.EnableCache = false  // Disable cache
opts.EnableSearch = false // Disable search

blogServer, _ := server.New(opts)

Performance Tuning

Cache Size

Match cache size to your content:

opts.CacheMaxMB = 200  // 200MB for large blogs
opts.CacheTTL = 30 * time.Minute  // Shorter TTL for frequent updates
Search Index

Rebuild index only when needed:

// First run
opts.RebuildIndex = true

// Subsequent runs
opts.RebuildIndex = false
Pagination

Adjust posts per page:

opts.PostsPerPage = 20  // Show 20 posts per page

Integration Patterns

With Existing Router
// Your existing application
app := yourframework.New()

// Add blog
blogServer, _ := server.New(server.DefaultOptions())
blogServer.AttachRoutes(app.Router, "/blog")
With Middleware
// Authentication middleware
authMux := http.NewServeMux()
blogServer.AttachRoutes(authMux, "/admin/blog")

mux.Handle("/admin/", requireAuth(authMux))
With Reverse Proxy
// Blog on subdomain
blogServer, _ := server.New(server.DefaultOptions())
mux := http.NewServeMux()
blogServer.AttachRoutes(mux, "/")

// Main app on different port
go http.ListenAndServe(":8080", mainApp)
go http.ListenAndServe(":8081", mux)  // blog.example.com -> :8081

See Also

Documentation

Index

Constants

View Source
const (
	ErrCodeInvalidConfig    = "INVALID_CONFIG"
	ErrCodePostNotFound     = "POST_NOT_FOUND"
	ErrCodeIndexCorrupt     = "INDEX_CORRUPT"
	ErrCodeCacheFailure     = "CACHE_FAILURE"
	ErrCodeSearchFailure    = "SEARCH_FAILURE"
	ErrCodeContentLoad      = "CONTENT_LOAD_FAILURE"
	ErrCodeFileWatch        = "FILE_WATCH_FAILURE"
	ErrCodeServerNotStarted = "SERVER_NOT_STARTED"
)

Error codes for BlogError

Variables

View Source
var (
	// ErrInvalidContentPath is returned when content path is invalid
	ErrInvalidContentPath = errors.New("invalid content path")

	// ErrInvalidCacheSize is returned when cache size is invalid
	ErrInvalidCacheSize = errors.New("cache size must be at least 1MB")

	// ErrInvalidPostsPerPage is returned when posts per page is invalid
	ErrInvalidPostsPerPage = errors.New("posts per page must be at least 1")

	// ErrServerNotStarted is returned when server operations are attempted before starting
	ErrServerNotStarted = errors.New("server not started")

	// ErrPostNotFound is returned when a post cannot be found
	ErrPostNotFound = &BlogError{
		Code:    ErrCodePostNotFound,
		Message: "post not found",
	}

	// ErrIndexCorrupt is returned when the search index is corrupted
	ErrIndexCorrupt = &BlogError{
		Code:    ErrCodeIndexCorrupt,
		Message: "search index is corrupted",
	}

	// ErrCacheFailure is returned when cache operations fail
	ErrCacheFailure = &BlogError{
		Code:    ErrCodeCacheFailure,
		Message: "cache operation failed",
	}
)

Predefined errors for common cases

Functions

This section is empty.

Types

type BlogError added in v0.5.0

type BlogError struct {
	Code    string // Machine-readable error code
	Message string // Human-readable message
	Err     error  // Underlying error
}

BlogError represents a structured error with context

func NewBlogError added in v0.5.0

func NewBlogError(code, message string, err error) *BlogError

NewBlogError creates a new BlogError

func (*BlogError) Error added in v0.5.0

func (e *BlogError) Error() string

Error implements the error interface

func (*BlogError) Is added in v0.5.0

func (e *BlogError) Is(target error) bool

Is checks if the error matches the target

func (*BlogError) Unwrap added in v0.5.0

func (e *BlogError) Unwrap() error

Unwrap returns the underlying error

type CORSConfig added in v0.5.0

type CORSConfig struct {
	// AllowedOrigins is a list of allowed origins (e.g., ["https://example.com"])
	// Use ["*"] to allow all origins (not recommended for production)
	AllowedOrigins []string

	// AllowedMethods is a list of allowed HTTP methods
	// Defaults to ["GET", "POST", "OPTIONS"]
	AllowedMethods []string

	// AllowedHeaders is a list of allowed headers
	// Defaults to ["Content-Type"]
	AllowedHeaders []string

	// AllowCredentials indicates whether credentials are allowed
	AllowCredentials bool

	// MaxAge indicates how long (in seconds) the results of a preflight request can be cached
	MaxAge int
}

CORSConfig configures Cross-Origin Resource Sharing

type EventHandlers added in v0.5.0

type EventHandlers struct {
	// OnPostView is called when a post is viewed
	OnPostView func(slug string, r *http.Request)

	// OnSearch is called when a search is performed
	OnSearch func(query string, resultCount int, r *http.Request)

	// OnError is called when an error occurs
	OnError func(err error, r *http.Request)

	// OnReload is called when content is reloaded
	OnReload func(postCount int)
}

EventHandlers contains callbacks for blog events

type Options

type Options struct {
	// ContentPath is the path to markdown posts (required)
	ContentPath string `env:"GOBLOG_CONTENT_PATH" envDefault:"./posts"`

	// Cache settings
	EnableCache bool          `env:"GOBLOG_CACHE_ENABLED" envDefault:"true"`
	CacheMaxMB  int64         `env:"GOBLOG_CACHE_MAX_MB" envDefault:"100"`
	CacheTTL    time.Duration `env:"GOBLOG_CACHE_TTL" envDefault:"60m"`

	// Search settings
	EnableSearch    bool   `env:"GOBLOG_SEARCH_ENABLED" envDefault:"true"`
	SearchIndexPath string `env:"GOBLOG_SEARCH_INDEX_PATH" envDefault:"./blog.bleve"`
	RebuildIndex    bool   `env:"GOBLOG_REBUILD_INDEX" envDefault:"false"`

	// Blog settings
	PostsPerPage int `env:"GOBLOG_POSTS_PER_PAGE" envDefault:"10"`

	// File watching
	WatchFiles bool `env:"GOBLOG_WATCH_FILES" envDefault:"false"`

	// Logging
	Verbose bool `env:"GOBLOG_VERBOSE" envDefault:"false"`

	// Middleware - custom middleware to wrap all blog routes
	Middleware []func(http.Handler) http.Handler

	// CustomCSS - path to custom CSS file or inline CSS to inject
	CustomCSS string

	// CustomTemplates - custom template overrides (not implemented yet, reserved for future)
	CustomTemplates map[string]string

	// EventHandlers - callbacks for blog events
	EventHandlers *EventHandlers

	// CORS - Cross-Origin Resource Sharing configuration
	CORS *CORSConfig

	// Feed configuration
	FeedTitle       string // Title for RSS/Atom feeds
	FeedDescription string // Description for RSS/Atom feeds
	FeedAuthor      string // Author for RSS/Atom feeds
	FeedAuthorEmail string // Author email for Atom feeds
	SiteURL         string // Base URL of the site (e.g., "https://example.com")
}

Options configures the blog server

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns default server options

func LoadFromEnv

func LoadFromEnv() (Options, error)

LoadFromEnv loads options from environment variables Falls back to defaults for any unset variables

func MustLoadFromEnv

func MustLoadFromEnv() Options

MustLoadFromEnv loads options from environment variables Panics if parsing fails

func (Options) Validate

func (o Options) Validate() error

Validate checks if options are valid

func (Options) WithCORS added in v0.5.0

func (o Options) WithCORS(cors *CORSConfig) Options

WithCORS sets CORS configuration

func (Options) WithCache added in v0.5.0

func (o Options) WithCache(enabled bool, maxMB int64, ttl time.Duration) Options

WithCache configures caching

func (Options) WithContentPath added in v0.5.0

func (o Options) WithContentPath(path string) Options

WithContentPath sets the content path

func (Options) WithCustomCSS added in v0.5.0

func (o Options) WithCustomCSS(css string) Options

WithCustomCSS sets custom CSS

func (Options) WithEventHandlers added in v0.5.0

func (o Options) WithEventHandlers(handlers *EventHandlers) Options

WithEventHandlers sets event handlers

func (Options) WithFeed added in v0.5.0

func (o Options) WithFeed(title, description, author, authorEmail, siteURL string) Options

WithFeed sets feed configuration

func (Options) WithFileWatching added in v0.5.0

func (o Options) WithFileWatching(enabled bool) Options

WithFileWatching enables or disables file watching

func (Options) WithMiddleware added in v0.5.0

func (o Options) WithMiddleware(middleware ...func(http.Handler) http.Handler) Options

WithMiddleware adds middleware to the options

func (Options) WithPostsPerPage added in v0.5.0

func (o Options) WithPostsPerPage(count int) Options

WithPostsPerPage sets posts per page

func (Options) WithSearch added in v0.5.0

func (o Options) WithSearch(enabled bool, indexPath string, rebuild bool) Options

WithSearch configures search

func (Options) WithVerbose added in v0.5.0

func (o Options) WithVerbose(enabled bool) Options

WithVerbose enables or disables verbose logging

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents the blog server

func New

func New(opts Options) (*Server, error)

New creates a new blog server with the given options

func (*Server) AttachRoutes

func (s *Server) AttachRoutes(mux *http.ServeMux, basePath string)

AttachRoutes attaches blog routes to the given mux at the specified base path

Example:

mux := http.NewServeMux()
server.AttachRoutes(mux, "/blog")

This will register:

GET /blog           - Blog index
GET /blog/posts/{slug} - Individual post
GET /blog/tags/{tag}   - Posts by tag
GET /blog/search       - Search (HTMX partial)

func (*Server) Close

func (s *Server) Close() error

Close closes the server and cleans up resources

func (*Server) GetIndex

func (s *Server) GetIndex() *search.Index

GetIndex returns the search index (for advanced usage)

func (*Server) GetLoader

func (s *Server) GetLoader() *content.Loader

GetLoader returns the content loader (for advanced usage)

func (*Server) Reload

func (s *Server) Reload() error

Reload reloads all content from disk

func (*Server) RenderPost added in v0.5.0

func (s *Server) RenderPost(slug string) (string, error)

RenderPost renders a single post as HTML (for custom layouts) Returns the HTML content that can be embedded in your own templates

func (*Server) RenderPostList added in v0.5.0

func (s *Server) RenderPostList(tags []string, page int) (string, error)

RenderPostList renders a list of posts as HTML (for custom layouts) Returns the HTML content that can be embedded in your own templates If tags is empty, returns all posts. Use page for pagination (0-indexed)

func (*Server) Stats

func (s *Server) Stats() Stats

Stats returns server statistics

type Stats

type Stats struct {
	TotalPosts    int
	AllTags       []string
	CacheHits     uint64
	CacheMisses   uint64
	CacheHitRatio float64
	IndexedDocs   uint64
}

Stats represents server statistics

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL