Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 200 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,211 @@
# ramchi
`ramchi` is an extension to `chi` for rapid & modular development of sites and restful applications.
It allows modular composition of routes, with easy handler registering in a manner that is simple to use,
`ramchi` is based upon developer experience and usage, while still making your website fast and responsive.

## Install
`ramchi` is an extension to the [chi](https://github.com/go-chi/chi) HTTP router designed for rapid and modular development of web applications. `ramchi` focuses on developer experience while ensuring your website remains fast and responsive.

`go get -u github.com/etwodev/ramchi`
---

## Config
## Features

When you create [your first server](), `ramchi` will generate a `ramchi.config.json` file,
which allows you to configure aspects of the server.
- Modular router and middleware loading
- Support for feature flagging via experimental toggles
- Unified backend and frontend serving capabilities
- Zero-configuration TLS support
- Structured, leveled logging powered by `zerolog`
- Graceful shutdown and signal handling
- Extensible helpers for requests, responses, crypto, email, and more

---

## Installation

```bash
go get -u github.com/etwodev/ramchi
```

---

## Getting Started

ramchi allows easy, modular registration of endpoints through grouping.

Create a new server instance and start it:

```go
package main

import (
"github.com/etwodev/ramchi"
"encoding/json"
"net/http"
)

func main() {
s := ramchi.New()
s.LoadRouter(Routers())
s.Start()
}

func Routers() []router.Router {
return []router.Router{
router.NewRouter("example", Routes(), true),
}
}

func Routes() []router.Route {
return []router.Route{
router.NewGetRoute("/demo", true, false, ExampleGetHandler),
}
}

// This route will be a GET endpoint registered at /example/demo
func ExampleGetHandler(w http.ResponseWriter, r *http.Request) {
res, _ := json.Marshal(map[string]string{"success": "ping"})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(201)
if _, err := w.Write(res); err != nil {
t.Fatal(err)
}
}
```

On the first run, `ramchi` will automatically generate a default `ramchi.config.json` file in your working directory.

---

## Configuration

The `ramchi.config.json` file controls server behavior and feature toggling.

### Default Configuration Example

```json
{
"port": "8080",
"address": "localhost",
"experimental": false
"port": "7000",
"address": "0.0.0.0",
"experimental": false,
"logLevel": "info",
"enableTLS": false,
"tlsCertFile": "",
"tlsKeyFile": "",
"readTimeout": 15,
"writeTimeout": 15,
"idleTimeout": 60,
"maxHeaderBytes": 1048576,
"shutdownTimeout": 15,
"enableCORS": false,
"allowedOrigins": ["*"],
"enableRequestLogging": false
}
```

### Configuration Fields

| Field | Type | Description | Default |
| ---------------------- | --------- | --------------------------------------------------------------------------- | ----------- |
| `port` | string | TCP port the server listens on | `"7000"` |
| `address` | string | IP address to bind to | `"0.0.0.0"` |
| `experimental` | bool | Enables or disables experimental feature flags | `false` |
| `logLevel` | string | Log verbosity level (`debug`, `info`, `warn`, `error`, `fatal`, `disabled`) | `"info"` |
| `enableTLS` | bool | Enable HTTPS by providing TLS certificate and key | `false` |
| `tlsCertFile` | string | Path to TLS certificate file (required if `enableTLS` is true) | `""` |
| `tlsKeyFile` | string | Path to TLS key file (required if `enableTLS` is true) | `""` |
| `readTimeout` | int | Maximum duration (in seconds) for reading the request | `15` |
| `writeTimeout` | int | Maximum duration (in seconds) before timing out response writes | `15` |
| `idleTimeout` | int | Maximum duration (in seconds) to keep idle connections open | `60` |
| `maxHeaderBytes` | int | Maximum size of request headers in bytes | `1048576` |
| `shutdownTimeout` | int | Time (in seconds) allowed for graceful shutdown | `15` |
| `enableCORS` | bool | Automatically enables CORS middleware | `false` |
| `allowedOrigins` | \[]string | List of allowed CORS origins (e.g., `["*"]`, `["https://example.com"]`) | `["*"]` |
| `enableRequestLogging` | bool | Automatically enables HTTP request logging middleware | `false` |

---

## Togglable Middleware

You can enable built-in middleware through the config file without registering them manually.

### Available Middleware

| Middleware | Config Flag | Description |
| ------------------- | ---------------------- | --------------------------------------------------------------- |
| **CORS** | `enableCORS` | Adds a permissive or origin-restricted CORS layer |
| **Request Logging** | `enableRequestLogging` | Logs all incoming HTTP requests using structured logging format |

These are injected globally before any custom middleware or routes.

If you require more control (e.g., middleware ordering or conditional logic), you can still register them manually through the `LoadMiddleware()` method.

---

## Using the Configuration in Code

Your application can access config values via the `config` package accessor functions:

```go
import c "github.com/etwodev/ramchi/config"

port := c.Port() // e.g. "7000"
address := c.Address() // e.g. "0.0.0.0"
if c.Experimental() {
// Enable experimental features
}
level := c.LogLevel() // e.g. "debug"
if c.EnableTLS() {
cert := c.TLSCertFile()
key := c.TLSKeyFile()
// Use cert and key to start HTTPS server
}
```

The server internally uses these config values to set up logging, timeouts, TLS, and feature flags.

---

## Logging

`ramchi` integrates [zerolog](https://github.com/rs/zerolog) for structured, leveled logging with console-friendly output by default. However, logging can be replaced. If you would like a specific package to be supported, please raise an issue.

* Log verbosity is controlled by the `logLevel` config (e.g., `debug`, `info`, `disabled`).
* Logs include contextual fields such as the server group, function names, HTTP method, route path, and middleware names.
* Graceful shutdown logs warnings and fatal errors as appropriate.

---

## Middleware & Routing

* Load your middlewares and routers modularly before starting the server.
* `ramchi` respects middleware and route `Experimental` flags based on your config.
* Routes and middleware with disabled status or mismatched experimental flags are skipped.

---

## TLS Support

Set `enableTLS` to `true` and provide valid paths to `tlsCertFile` and `tlsKeyFile` in your config to serve HTTPS.

---

## Extending Helpers

`ramchi` ships with helper packages for common tasks:

* `helpers/request.go`: HTTP request utilities (e.g., extracting IP, URL params)
* `helpers/response.go`: Response helpers for JSON encoding, error handling
* `helpers/crypto.go`: Crypto utilities (hashing, encryption helpers)
* `helpers/email.go`: Email sending and templating helpers
* `helpers/strings.go`: String manipulation utilities (e.g., truncation, padding, sanitization)
* `helpers/encoding.go`: Encoding utilities (e.g., toHex, toBase64)

You are encouraged to extend these helper packages or create your own.

---

## Contributing

Contributions and suggestions are welcome. Please open issues or pull requests on the [GitHub repository](https://github.com/etwodev/ramchi).

---

## License

MIT License © Etwodev
30 changes: 27 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var c *Config
func Load() error {
_, err := os.Stat(CONFIG)
if os.IsNotExist(err) {
if err := Create(); err != nil {
if err := Create(nil); err != nil {
return fmt.Errorf("Load: failed creating load: %w", err)
}
}
Expand All @@ -30,15 +30,39 @@ func Load() error {
return nil
}

func Create() error {
file, err := json.MarshalIndent(&Config{Port: "7000", Address: "0.0.0.0", Experimental: false}, "", " ")
func Create(override *Config) error {
var defaultConfig Config = Config{
Port: "7000",
Address: "0.0.0.0",
Experimental: false,
ReadTimeout: 15,
WriteTimeout: 15,
IdleTimeout: 60,
LogLevel: "info",
MaxHeaderBytes: 1048576,
EnableTLS: false,
TLSCertFile: "",
TLSKeyFile: "",
ShutdownTimeout: 15,
EnableCORS: false,
AllowedOrigins: []string{"*"},
EnableRequestLogging: false,
}

if override != nil {
defaultConfig = *override
}

file, err := json.MarshalIndent(&defaultConfig, "", " ")
if err != nil {
return fmt.Errorf("Create: failed marshalling config: %w", err)
}

err = os.WriteFile(CONFIG, file, 0644)
if err != nil {
return fmt.Errorf("Create: failed writing config: %w", err)
}

return nil
}

Expand Down
44 changes: 30 additions & 14 deletions config/types.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package config

type Config struct {
Port string `json:"port"`
Address string `json:"address"`
Experimental bool `json:"experimental"`
Port string `json:"port"`
Address string `json:"address"`
Experimental bool `json:"experimental"`
ReadTimeout int `json:"readTimeout"` // in seconds
WriteTimeout int `json:"writeTimeout"` // in seconds
IdleTimeout int `json:"idleTimeout"` // in seconds
LogLevel string `json:"logLevel"` // e.g. "debug", "info", "disabled"
MaxHeaderBytes int `json:"maxHeaderBytes"`
EnableTLS bool `json:"enableTLS"`
TLSCertFile string `json:"tlsCertFile"`
TLSKeyFile string `json:"tlsKeyFile"`
ShutdownTimeout int `json:"shutdownTimeout"` // graceful shutdown timeout seconds
EnableCORS bool `json:"enableCORS"`
AllowedOrigins []string `json:"allowedOrigins"`
EnableRequestLogging bool `json:"enableRequestLogging"`
}

func Port() string {
return c.Port
}

func Address() string {
return c.Address
}

func Experimental() bool {
return c.Experimental
}
func Port() string { return c.Port }
func Address() string { return c.Address }
func Experimental() bool { return c.Experimental }
func ReadTimeout() int { return c.ReadTimeout }
func WriteTimeout() int { return c.WriteTimeout }
func IdleTimeout() int { return c.IdleTimeout }
func LogLevel() string { return c.LogLevel }
func MaxHeaderBytes() int { return c.MaxHeaderBytes }
func EnableTLS() bool { return c.EnableTLS }
func TLSCertFile() string { return c.TLSCertFile }
func TLSKeyFile() string { return c.TLSKeyFile }
func ShutdownTimeout() int { return c.ShutdownTimeout }
func EnableCORS() bool { return c.EnableCORS }
func AllowedOrigins() []string { return c.AllowedOrigins }
func EnableRequestLogging() bool { return c.EnableRequestLogging }
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/Etwodev/ramchi

go 1.21.1
go 1.23.0

toolchain go1.23.2

require (
github.com/go-chi/chi/v5 v5.0.10
Expand All @@ -10,5 +12,6 @@ require (
require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
golang.org/x/crypto v0.40.0
golang.org/x/sys v0.34.0 // indirect
)
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
Loading
Loading