Reusable Go web foundation for server-rendered applications.
go-web-core provides the common infrastructure I use across Go web projects so new applications can start with a tested foundation instead of rebuilding the same plumbing each time.
It is intentionally small and does not include application-specific features such as authentication, users, clients, projects, billing, or business logic.
- Environment-based application configuration
- SQLite database setup
- Automatic database directory creation
- SQLite foreign-key enforcement
- SQL migration runner
- Migration tracking
- Structured logging with
log/slog - HTTP request logging middleware
- Health endpoint
- HTML template rendering
- HTMX-aware redirects
- Generic form validation helpers
- Automated tests for core behaviour
go get github.com/danieljmanningdev/go-web-coreLoads common application configuration from environment variables.
cfg := config.Load()Supported variables:
APP_ENV
APP_PORT
LOG_LEVEL
DATABASE_PATH
TEMPLATE_DIR
Default values:
APP_ENV=development
APP_PORT=8080
LOG_LEVEL=info
DATABASE_PATH=./data/app.db
TEMPLATE_DIR=web/templates
Provides SQLite database setup and migration support.
db, err := database.Open(ctx, cfg.DatabasePath)
if err != nil {
log.Fatal(err)
}
defer db.Close()Run SQL migrations from a directory:
if err := database.RunMigrations(db.SQL, "migrations"); err != nil {
log.Fatal(err)
}Migration files use numbered filenames:
001_initial.sql
002_users.sql
003_projects.sql
Applied migrations are recorded in the schema_migrations table.
Creates a structured slog.Logger.
logger := logging.New(
cfg.Environment,
cfg.LogLevel,
)Development environments use text logs.
Production uses JSON logs.
Provides reusable HTTP middleware.
handler := middleware.RequestLogger(
logger,
mux,
)The request logger records:
- HTTP method
- request path
- response status
- request duration
Provides a simple health endpoint.
mux.HandleFunc("/health", health.HealthHandler)Response:
{
"status": "ok"
}Provides server-side HTML rendering using Go's html/template.
renderer, err := rendering.New(
"web/templates/layout.html",
"web/templates/page.html",
)
if err != nil {
log.Fatal(err)
}Render a template:
err = renderer.HTML(
w,
http.StatusOK,
"page",
data,
)Templates are rendered into a buffer before the HTTP response is written so template execution errors do not result in partially written pages.
Standard request:
rendering.Redirect(
w,
r,
"/dashboard",
http.StatusSeeOther,
)HTMX requests automatically receive an HX-Redirect response.
Provides generic validation helpers suitable for forms and request data.
errors := validation.Errors{}
if !validation.Required(name) {
errors.Add("name", "Name is required.")
}
if !validation.Email(email) {
errors.Add("email", "Enter a valid email address.")
}
if !validation.MaxLength(name, 100) {
errors.Add("name", "Name must be 100 characters or fewer.")
}
if !validation.OneOf(status, "active", "inactive") {
errors.Add("status", "Invalid status.")
}
if errors.Any() {
// Return validation errors.
}Available helpers include:
Required
MinLength
MaxLength
Email
OneOf
A minimal application could look like:
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/danieljmanningdev/go-web-core/config"
"github.com/danieljmanningdev/go-web-core/database"
"github.com/danieljmanningdev/go-web-core/health"
"github.com/danieljmanningdev/go-web-core/logging"
"github.com/danieljmanningdev/go-web-core/middleware"
)
func main() {
cfg := config.Load()
logger := logging.New(
cfg.Environment,
cfg.LogLevel,
)
db, err := database.Open(
context.Background(),
cfg.DatabasePath,
)
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := database.RunMigrations(
db.SQL,
"migrations",
); err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc(
"/health",
health.HealthHandler,
)
handler := middleware.RequestLogger(
logger,
mux,
)
logger.Info(
"server starting",
"port",
cfg.Port,
)
address := fmt.Sprintf(":%d", cfg.Port)
if err := http.ListenAndServe(
address,
handler,
); err != nil {
log.Fatal(err)
}
}This project is deliberately a web core rather than a full framework.
It contains infrastructure that is useful across many Go applications while leaving domain-specific functionality to individual projects or separate modules.
Features such as authentication, sessions, CSRF protection, client management, project management, contracts, billing, and application-specific UI should remain separate unless they become fundamental dependencies of nearly every application.
The goal is simple:
Start every Go web project with a small, tested and familiar foundation.
Format the code:
gofmt -w .Run the test suite:
go test ./...Run static analysis:
go vet ./...Check whitespace errors:
git diff --checkThe project is currently in early development.
Until the API stabilises, releases should be considered pre-v1.0.0 and may contain breaking changes.
See LICENSE.