From 3489431adc4069427a24ab918ff5f7ecbb3dd950 Mon Sep 17 00:00:00 2001 From: isaacwallace123 Date: Thu, 31 Jul 2025 18:07:55 -0400 Subject: [PATCH 1/7] Added config support with .json format, added static files to support /public or any static resource location, added static types in types folder, added config engine to handle configs --- app/config/config.go | 41 +++++++++++++++++++++++++++++++++++++++ app/router.go | 8 +++++++- app/types/static.go | 6 ++++++ pkg/middlewares/static.go | 39 +++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 app/config/config.go create mode 100644 app/types/static.go create mode 100644 pkg/middlewares/static.go diff --git a/app/config/config.go b/app/config/config.go new file mode 100644 index 0000000..f38764c --- /dev/null +++ b/app/config/config.go @@ -0,0 +1,41 @@ +package config + +import ( + "github.com/isaacwallace123/GoUtils/jsonutil" + "github.com/isaacwallace123/GoWeb/app/types" + "os" + "strconv" +) + +type ServerConfig struct { + Port int `json:"port"` +} + +var ( + Server ServerConfig + Static []types.StaticConfig +) + +type appConfig struct { + Server ServerConfig `json:"server"` + Static []types.StaticConfig `json:"static"` +} + +func LoadConfig(path string) error { + file, err := os.ReadFile(path) + if err != nil { + return err + } + var loaded appConfig + if err := jsonutil.FromBytes(file, &loaded); err != nil { + return err + } + Server = loaded.Server + Static = loaded.Static + return nil +} + +// PortString returns ":8080" (or whatever garbage port you configured) +func PortString() string { + return ":" + strconv.Itoa(Server.Port) +} diff --git a/app/router.go b/app/router.go index a528e95..d167fcd 100644 --- a/app/router.go +++ b/app/router.go @@ -7,7 +7,8 @@ import ( ) type Router struct { - routes []internal.CompiledRoute + routes []internal.CompiledRoute + resources []func(http.ResponseWriter, *http.Request) bool } // NewRouter creates a new Router. @@ -27,5 +28,10 @@ func (r *Router) Listen(addr string) error { // ServeHTTP allows Router to implement http.Handler. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + for _, handler := range r.resources { + if handler(w, req) { + return + } + } internal.Dispatch(r.routes, w, req) } diff --git a/app/types/static.go b/app/types/static.go new file mode 100644 index 0000000..b6776fc --- /dev/null +++ b/app/types/static.go @@ -0,0 +1,6 @@ +package types + +type StaticConfig struct { + Path string `json:"path"` + Directory string `json:"directory"` +} diff --git a/pkg/middlewares/static.go b/pkg/middlewares/static.go new file mode 100644 index 0000000..a34b803 --- /dev/null +++ b/pkg/middlewares/static.go @@ -0,0 +1,39 @@ +package middlewares + +import ( + "github.com/isaacwallace123/GoUtils/logger" + "github.com/isaacwallace123/GoWeb/app/types" + "net/http" + "strings" +) + +var StaticMiddleware = types.NewMiddlewareBuilder("static", &types.StaticConfig{}, func(ctx *types.MiddlewareContext, cfg *types.StaticConfig) error { + if cfg.Path == "" || cfg.Directory == "" { + return ctx.Next() + } + if strings.HasPrefix(ctx.Request.URL.Path, cfg.Path) { + fileServer := http.FileServer(http.Dir(cfg.Directory)) + http.StripPrefix(cfg.Path, fileServer).ServeHTTP(ctx.ResponseWriter, ctx.Request) + return nil + } + return ctx.Next() +}) + +func StaticMiddlewares(statics []types.StaticConfig) []types.Middleware { + var staticMiddlewares []types.Middleware + + for _, s := range statics { + staticMiddlewares = append(staticMiddlewares, NewStaticMiddleware(s.Path, s.Directory)) + } + + return staticMiddlewares +} + +func NewStaticMiddleware(path, directory string) types.Middleware { + logger.Info("[Static] Registered: %-12s → %s", path, directory) + + return StaticMiddleware.WithInit(func(cfg *types.StaticConfig) { + cfg.Path = path + cfg.Directory = directory + }) +} From 4d2256b32211eb12e17e1b1251593538cb0a6990 Mon Sep 17 00:00:00 2001 From: isaacwallace123 Date: Fri, 1 Aug 2025 17:25:43 -0400 Subject: [PATCH 2/7] Updated gitgnore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 00a8c8b..2379c88 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ dev/ main.go GoWeb -Makefile \ No newline at end of file +Makefile +Public/ +Static/ From c69310985844314ea9d030b256de4bbeefc31771 Mon Sep 17 00:00:00 2001 From: isaacwallace123 Date: Fri, 1 Aug 2025 17:54:54 -0400 Subject: [PATCH 3/7] Removed static middleware (It should be built into router and not a middleware), create UseStatic method to add a static path to the router, modified router Listen Implementation to offer more control for users to create custom routes --- .gitignore | 4 ++-- app/internal/router.go | 7 ++----- app/router.go | 40 +++++++++++++++++++++++++++++++++++---- pkg/middlewares/static.go | 39 -------------------------------------- 4 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 pkg/middlewares/static.go diff --git a/.gitignore b/.gitignore index 2379c88..74685be 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,5 @@ dev/ main.go GoWeb Makefile -Public/ -Static/ +public/ +static/ diff --git a/app/internal/router.go b/app/internal/router.go index 0fff3d9..6e3e6e8 100644 --- a/app/internal/router.go +++ b/app/internal/router.go @@ -48,11 +48,8 @@ func RegisterControllersImpl(controllers ...types.Controller) []CompiledRoute { return compiled } -func ListenImpl(routes []CompiledRoute, addr string) error { - http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - Dispatch(routes, w, req) - }) - return http.ListenAndServe(addr, nil) +func ListenImpl(router http.Handler, addr string) error { + return http.ListenAndServe(addr, router) } func Dispatch(routes []CompiledRoute, w http.ResponseWriter, req *http.Request) { diff --git a/app/router.go b/app/router.go index d167fcd..459986c 100644 --- a/app/router.go +++ b/app/router.go @@ -1,9 +1,11 @@ package app import ( + "github.com/isaacwallace123/GoUtils/logger" "github.com/isaacwallace123/GoWeb/app/internal" "github.com/isaacwallace123/GoWeb/app/types" "net/http" + "strings" ) type Router struct { @@ -22,16 +24,46 @@ func (r *Router) RegisterControllers(controllers ...types.Controller) { } // Listen starts the HTTP server. -func (r *Router) Listen(addr string) error { - return internal.ListenImpl(r.routes, addr) -} +func (r *Router) Listen(addr string) error { return internal.ListenImpl(r, addr) } -// ServeHTTP allows Router to implement http.Handler. +// ServeHTTP first tries static handlers, then dispatches dynamic routes. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { for _, handler := range r.resources { if handler(w, req) { return } } + internal.Dispatch(r.routes, w, req) } + +// UseStatic registers a static file handler for the given URL prefix and directory. +func (r *Router) UseStatic(prefix, dir string) { + if !strings.HasPrefix(prefix, "/") { + prefix = "/" + prefix + } + if len(prefix) > 1 && strings.HasSuffix(prefix, "/") { + prefix = strings.TrimSuffix(prefix, "/") + } + + fs := http.FileServer(http.Dir(dir)) + handler := func(w http.ResponseWriter, req *http.Request) bool { + path := req.URL.Path + + if path == prefix { + http.Redirect(w, req, prefix+"/", http.StatusMovedPermanently) + logger.Info("[Static] Redirected: %s → %s/", path, prefix) + return true + } + + if strings.HasPrefix(path, prefix+"/") { + logger.Info("[Static] %s → %s (%s)", prefix, dir, path) + http.StripPrefix(prefix, fs).ServeHTTP(w, req) + return true + } + return false + } + + r.resources = append(r.resources, handler) + logger.Info("[Static] Registered: %-12s → %s", prefix, dir) +} diff --git a/pkg/middlewares/static.go b/pkg/middlewares/static.go deleted file mode 100644 index a34b803..0000000 --- a/pkg/middlewares/static.go +++ /dev/null @@ -1,39 +0,0 @@ -package middlewares - -import ( - "github.com/isaacwallace123/GoUtils/logger" - "github.com/isaacwallace123/GoWeb/app/types" - "net/http" - "strings" -) - -var StaticMiddleware = types.NewMiddlewareBuilder("static", &types.StaticConfig{}, func(ctx *types.MiddlewareContext, cfg *types.StaticConfig) error { - if cfg.Path == "" || cfg.Directory == "" { - return ctx.Next() - } - if strings.HasPrefix(ctx.Request.URL.Path, cfg.Path) { - fileServer := http.FileServer(http.Dir(cfg.Directory)) - http.StripPrefix(cfg.Path, fileServer).ServeHTTP(ctx.ResponseWriter, ctx.Request) - return nil - } - return ctx.Next() -}) - -func StaticMiddlewares(statics []types.StaticConfig) []types.Middleware { - var staticMiddlewares []types.Middleware - - for _, s := range statics { - staticMiddlewares = append(staticMiddlewares, NewStaticMiddleware(s.Path, s.Directory)) - } - - return staticMiddlewares -} - -func NewStaticMiddleware(path, directory string) types.Middleware { - logger.Info("[Static] Registered: %-12s → %s", path, directory) - - return StaticMiddleware.WithInit(func(cfg *types.StaticConfig) { - cfg.Path = path - cfg.Directory = directory - }) -} From e96aeb3cb75e5222fc5959daec294b6e7d1a25d2 Mon Sep 17 00:00:00 2001 From: isaacwallace123 Date: Fri, 1 Aug 2025 18:06:36 -0400 Subject: [PATCH 4/7] Updated GoUtils version being used and updated logging to also log status code colors --- go.mod | 2 +- go.sum | 4 ++-- pkg/middlewares/logging.go | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 1fdc146..9b0a027 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/isaacwallace123/GoWeb go 1.24.2 -require github.com/isaacwallace123/GoUtils v1.0.1 +require github.com/isaacwallace123/GoUtils v1.0.2 diff --git a/go.sum b/go.sum index b2cb67f..fb3611d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/isaacwallace123/GoUtils v1.0.1 h1:XGG8zKeG+K/mWf6NQw1KIOppiz9WyxHuGVvFuc4MWC0= -github.com/isaacwallace123/GoUtils v1.0.1/go.mod h1:7SX/JIf8Zdml2dh6Zn6vNSrEsYlXwlkLzU8o85UG3Rk= +github.com/isaacwallace123/GoUtils v1.0.2 h1:CrWqtcuuWU6DxNWSbf5bkOGiZsILQvnNasvADYfgle4= +github.com/isaacwallace123/GoUtils v1.0.2/go.mod h1:7SX/JIf8Zdml2dh6Zn6vNSrEsYlXwlkLzU8o85UG3Rk= diff --git a/pkg/middlewares/logging.go b/pkg/middlewares/logging.go index 6a623d4..6ff88db 100644 --- a/pkg/middlewares/logging.go +++ b/pkg/middlewares/logging.go @@ -27,7 +27,13 @@ var LoggingPost = types.NewMiddlewareBuilder("logging_post", &LoggingConfig{ }, func(ctx *types.MiddlewareContext, cfg *LoggingConfig) error { if cfg.Enabled && ctx.ResponseEntity != nil { methodColored := color.HTTPMethodToColor[ctx.Request.Method] + ctx.Request.Method + color.Reset - logger.Info("%s %s %d", methodColored, ctx.Request.URL.Path, ctx.ResponseEntity.StatusCode) + statusColor := color.HTTPStatusToColor(ctx.ResponseEntity.StatusCode) + + logger.Info("%s %s %s%d%s", + methodColored, + ctx.Request.URL.Path, + statusColor, ctx.ResponseEntity.StatusCode, color.Reset, + ) } return ctx.Next() }) From 43f011b8c4cb1b50b6edef1cbd59d60d99cc6b3f Mon Sep 17 00:00:00 2001 From: isaac Date: Thu, 7 Aug 2025 01:07:57 -0400 Subject: [PATCH 5/7] Updated README.MD with supporting documentation for configuration --- README.MD | 59 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/README.MD b/README.MD index c389c6a..0aaa61c 100644 --- a/README.MD +++ b/README.MD @@ -18,7 +18,6 @@ GoWeb is a lightweight Go web framework that mimics the structure, design, and f ## 📚 Table of Contents -- [✨ Features](#-features) - [🚀 Usage](#-usage) 1. [Define Request/Response DTOs](#1-define-requestresponse-dtos) 2. [Create a Controller](#2-create-a-controller) @@ -86,12 +85,6 @@ func main() { &controllers.UsersController{}, // You can create a controller through OOP controllers.New(), // You can create a controller using the builder ) - - // Register pre-middleware - app.Use(middlewares.LoggingPre) - - // Register post-middleware - app.UseAfter(middlewares.LoggingPost) // Launch the server logger.Info("Server listening on http://localhost:8080") @@ -166,6 +159,57 @@ X-Custom: example "Email": "Test@example.com" } ``` +--- + +## ⚙️ Configuration + +This project supports simple, extensible configuration using a single JSON file, typically located at `./application.json`. All key server settings—such as port and static resource mappings—are defined here. + +### Example Configs + +```json +{ + "server": { + "port": 8080 + }, + "static": [ + { "path": "/public", "directory": "./public" }, + { "path": "/static", "directory": "./static" } + ] +} +``` + +### How It Works +- **Server Port** + - The server listens on the port defined at server.port + - you can use any valid port number (e.g., `8080`, `3000`, etc.). +- **Static Resources** + - Each entry in the `static` array maps a URL prefix (`path`) to a directory on disk (`directory`). + - For example, requests to `/public/example.png` will serve the file located at `./public/example.png`. + +### Adding or Changing Static Resources +To add more static resources, just simply add new entries in the `static` array: +```json +"static": [ + { "path": "/assets", "directory": "./assets" }, + { "path": "/media", "directory": "./media" } +] +``` +Each mapping supports any valid URL prefix and directory path (relative to your project root) +### Loading and Using Configs +On startup, the server loads `application.json` and applies the configuration automatically. Config values are available anywhere in your Go code via the config package (works as a singleton). +```go +import "github.com/isaacwallace123/GoWeb/app/config" + +// Get the configured port (as string) +port := config.PortString() // e.g., ":8080" + +// Loop through statics +for _, s := range config.Static { + fmt.Println("Serving", s.Path, "from", s.Directory) +} +``` + --- ## 🌐 CORS Middleware @@ -211,7 +255,6 @@ Access-Control-Allow-Origin: https://example.com ``` --- - ### ❤️ Inspired By - Java Spring Boot From a06bc2c4c5baf1b3fad345fb0179068146a02b7e Mon Sep 17 00:00:00 2001 From: isaac Date: Thu, 7 Aug 2025 01:17:21 -0400 Subject: [PATCH 6/7] Added more context to README.MD for better documentation --- README.MD | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.MD b/README.MD index 0aaa61c..58ef63d 100644 --- a/README.MD +++ b/README.MD @@ -26,6 +26,10 @@ GoWeb is a lightweight Go web framework that mimics the structure, design, and f - [💡 Why These Matter](#-why-these-matter) - [🧪 Response Builder](#-response-builder) - [📌 Example JSON Response](#-example-json-response) +- [⚙️ Configuration](#-configuration) + - [Example Configs](#example-configs) + - [How It Works](#how-it-works) + - [Loading and Using Configs](#loading-and-using-configs) - [🌐 CORS Middleware](#-cors-middleware) - [✅ Registering the CORS Middleware](#-registering-the-cors-middleware) - [⚙️ Behavior](#-behavior) @@ -101,7 +105,7 @@ func main() { GoWeb is built on a clean and extendable foundation inspired by Spring Boot, but optimized for Go. Below are the key architectural components of the framework: | Concept | Description | -| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **`Controller`** | A struct implementing `BasePath()` and `Routes()` to define a route group. It may embed `ControllerBase` to enable optional controller-specific middleware. | | **`Route`** | Defines a single HTTP endpoint via `Method`, `Path`, and a string-based `Handler` name that maps to a function in the controller. | | **DTOs** | Plain structs representing request or response data (e.g., `UserRequest`, `UserResponse`). GoWeb automatically binds path/query/body values to arguments. | @@ -112,6 +116,7 @@ GoWeb is built on a clean and extendable foundation inspired by Spring Boot, but | **Middleware** | Middleware objects implement the `Middleware` interface. They're registered globally or per controller using `app.Use(...)` or `controller.Use(...)`. | | **Middleware Builder** | Use `NewMiddlewareBuilder(...)` to create strongly-typed, reusable middleware with config (`.Config`), init logic (`.WithInit()`), and error hooks (`.OnError()`). | | **Request Context Helpers** | Access path params, query strings, and headers using `types.PathVar(ctx, "id")`, `QueryParam(ctx, "q")`, and `Header(ctx, "X-Token")`. Injected automatically by the router. | +| **Configuration** | A very straight-forward and easy way to manage configurations that your entire project can easily access via the `app/config` util | ### 💡 Why These Matter @@ -199,14 +204,27 @@ Each mapping supports any valid URL prefix and directory path (relative to your ### Loading and Using Configs On startup, the server loads `application.json` and applies the configuration automatically. Config values are available anywhere in your Go code via the config package (works as a singleton). ```go -import "github.com/isaacwallace123/GoWeb/app/config" +import ( + "github.com/isaacwallace123/GoWeb/app/config" + "github.com/isaacwallace123/GoUtils/logger" + "github.com/isaacwallace123/GoWeb/app" +) -// Get the configured port (as string) -port := config.PortString() // e.g., ":8080" +func main() { + // This is load your configs into a singleton struct. + if err := config.LoadConfig("./dev/application.json"); err != nil { + logger.Fatal("Failed to load config: %v", err) + } + + router := app.NewRouter() + + // UseStatic is a method that will automatically create a static resource URI to the designated directory + for _, s := range config.Static { + router.UseStatic(s.Path, s.Directory) + } -// Loop through statics -for _, s := range config.Static { - fmt.Println("Serving", s.Path, "from", s.Directory) + // Get the configured port (as string) + port := config.PortString() // e.g., ":8080" } ``` From 98ba191a5b8fb77a46182c61730e096af5dcc9fe Mon Sep 17 00:00:00 2001 From: isaac Date: Thu, 7 Aug 2025 01:17:51 -0400 Subject: [PATCH 7/7] Fixed tabbing issues on README.MD --- README.MD | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.MD b/README.MD index 58ef63d..f49074e 100644 --- a/README.MD +++ b/README.MD @@ -211,14 +211,14 @@ import ( ) func main() { - // This is load your configs into a singleton struct. + // This is load your configs into a singleton struct. if err := config.LoadConfig("./dev/application.json"); err != nil { logger.Fatal("Failed to load config: %v", err) } router := app.NewRouter() - - // UseStatic is a method that will automatically create a static resource URI to the designated directory + + // UseStatic is a method that will automatically create a static resource URI to the designated directory for _, s := range config.Static { router.UseStatic(s.Path, s.Directory) }