Skip to content

Commit ad314c9

Browse files
authored
Merge pull request #27 from nilotpaul/cli
feat(template): new option & docs of seperate frontend as rendering strategy
2 parents 2cafb0a + 43fd67e commit ad314c9

25 files changed

+744
-50
lines changed

README.md

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,19 @@ Read detailed usage and examples of every stack configured.
5757
- [Go + Echo + Templates](/docs/go-echo-templates.md)
5858
- [Go + Fiber + Templates](/docs/go-fiber-templates.md)
5959
- [Go + Chi + Templates](/docs/go-chi-templates.md)
60+
- [Go + Seperate Client](/docs/go-seperate-client.md)
6061

6162
**(Others)**
6263
- [Development Usage](/docs/development-usage.md)
6364
- [Recommendations](/docs/recommendations/index.md)
6465

6566
# Configuration Options
6667

67-
**Web Framework**
68-
- Echo
69-
- Fiber
70-
- Chi
71-
72-
**Styling**
73-
- Vanilla
74-
- Tailwind
75-
76-
**UI Library**
77-
- Preline
78-
- DaisyUI
79-
80-
**Extra Options**
81-
- HTMX
82-
- Dockerfile
68+
The configuration options include settings from various web frameworks to different rendering modes. For a detailed list, please check the [Configuration Options Docs](/docs/configuration.md).
8369

8470
# Coming Soon
8571

8672
- More Framework Options.
87-
- Different Rendering Strategies (seperate client, [templ](https://templ.guide)).
73+
- Different Rendering Strategies (~~seperate client~~, [templ](https://templ.guide)).
8874
- More examples and documentation.
8975
- Please suggest More 🙏🏼

cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func init() {
6767
)
6868
initCmd.Flags().StringVar(
6969
&stackConfig.RenderingStrategy, "render", "",
70-
strings.Join(config.RenderingStrategy, ", "),
70+
strings.Join(util.GetRenderingOpts(true), ", "),
7171
)
7272
initCmd.Flags().StringSliceVar(
7373
&stackConfig.ExtraOpts, "extra", []string{},

command.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func handleInitCmd(cmd *cobra.Command, args []string) {
6767
return
6868
}
6969

70-
util.PrintSuccessMsg(targetPath.Path)
70+
util.PrintSuccessMsg(targetPath.Path, cfg)
7171
}
7272

7373
// handleVersionCmd handles the `version` command for gospur CLI.

config/config.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ var (
3838
"Preline": {"Tailwind"},
3939
"DaisyUI": {"Tailwind"},
4040
}
41-
RenderingStrategy = []string{
42-
"Templates",
41+
RenderingStrategy = map[string]string{
42+
"Templates": "Templates",
43+
"Seperate Client (eg. react,etc.)": "Seperate",
4344
}
4445

4546
// Flags Only
@@ -70,9 +71,10 @@ var (
7071
// Template path is not required anymore for pages.
7172
// We're processing these as raw files.
7273
ProjectPageFiles = map[string]string{
73-
"web/Home.html": "",
74-
"web/Error.html": "",
75-
"web/layouts/Root.html": "",
74+
"web/Home.html": "",
75+
"web/Error.html": "",
76+
"web/layouts/Root.html": "",
77+
"web/dist/instruction.md": "",
7678
}
7779

7880
ProjectAPIFiles = map[string][]string{

docs/configuration.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Configuration Options
2+
3+
> To see the available options, run gopsur init -h. The output will list all valid options, and their exact casing must be used (e.g., if an option is displayed as HTMX, it must be passed as HTMX).
4+
5+
## For init command
6+
7+
These will be asked as prompts when you run `init`. If an option is selected by a flag, it'll skip that prompt.
8+
9+
**Web Framework**
10+
- Echo
11+
- Fiber
12+
- Chi
13+
```sh
14+
# flag
15+
--framework Echo
16+
```
17+
18+
**Rendering Strategy**
19+
- Templates
20+
- Seperate Client (eg. react,svelte,etc.)
21+
```sh
22+
# flag
23+
--render Seperate
24+
```
25+
26+
**Styling**
27+
- Vanilla
28+
- Tailwind
29+
```sh
30+
# flag
31+
--styling Tailwind
32+
```
33+
34+
**UI Library**
35+
- Preline
36+
- DaisyUI
37+
```sh
38+
# flag
39+
--ui DaisyUI
40+
```
41+
42+
## Options via Flags
43+
44+
These options can only be enabled/used via flags.
45+
46+
**Extra Options**
47+
- HTMX
48+
- Dockerfile
49+
```sh
50+
# flag
51+
--extra Dockerfile
52+
```

docs/go-seperate-client.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Setting Up Your Frontend
2+
3+
You have two options for integrating your frontend with the Go backend:
4+
5+
1. Keep frontend inside this repo
6+
7+
- Create your frontend project inside the `web` folder.
8+
- Do not modify `web/dist` directly - Go requires it to exist.
9+
- Configure your frontend to output static files to `web/dist`, replacing the default one.
10+
- If your build output directory is not `dist`, update it in `build_prod.go`.
11+
12+
2. Keep frontend in a separate repo
13+
14+
- Build the frontend in a CI pipeline (examples given below).
15+
- Merge the generated `dist` folder into `web/dist`.
16+
17+
## Examples
18+
- [Basic Github Actions](#)
19+
- [Docker Github Actions](#)
20+
- [Dockerfile](#)
21+
22+
> **TODO: will update later.**
23+
24+
# Serving a Separate Frontend with Go
25+
26+
Frontend frameworks like React, Vue, Svelte, etc. generate static assets (HTML, CSS, JS), which can be served directly from a Go backend. This simplifies deployment and avoids CORS issues.
27+
28+
# How It Works
29+
30+
- The frontend build (`build`/`dist` folder) is embedded in Go using `embed.FS`.
31+
- If a requested file exists, it's served normally.
32+
- If not, Go serves `index.html` (for SPAs) or a dedicated error page if available (for SSGs).
33+
34+
# SPA vs. SSG Behavior
35+
36+
- **SPA (Single Page App)**: Always fallbacks to `index.html`, and routing/errors are handled in JavaScript.
37+
- **SSG (Static Site Generation)**: If a `404.html` or similar page exists, serve that instead of `index.html`.
38+
39+
# Configuring Fallback Pages (if needed)
40+
41+
Some SSG frameworks may generate a `404.html` for handling missing pages. To serve it properly:
42+
43+
**Echo Example**
44+
```go
45+
// Todo: will fix this later. Echo doesn't have an option to specify a fallback.
46+
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
47+
Root: "web/dist",
48+
Index: "index.html",
49+
Filesystem: http.FS(web),
50+
}))
51+
```
52+
53+
**Fiber Example**
54+
```go
55+
app.Use(filesystem.New(filesystem.Config{
56+
Root: http.FS(subFS),
57+
Browse: false,
58+
Index: "index.html",
59+
NotFoundFile: "404.html", // Change this
60+
}))
61+
```
62+
63+
**Chi Example**
64+
```go
65+
mux.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
path := strings.TrimPrefix(r.URL.Path, "/")
67+
68+
// Check if the requested file exists
69+
_, err := subFS.Open(path)
70+
if err != nil {
71+
// If not found, serve fallback page.
72+
http.ServeFileFS(w, r, subFS, "404.html") // Change this
73+
return
74+
}
75+
76+
fs.ServeHTTP(w, r)
77+
}))
78+
```

template/api/api.go.chi.tmpl

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{{- if .Render.IsTemplates -}}
12
package api
23

34
import (
@@ -101,4 +102,61 @@ func (api *APIServer) registerGlobalMiddlewares(mux *chi.Mux) {
101102
mux.Use(middleware.Recoverer)
102103

103104
api.ServeStatic(mux)
104-
}
105+
}
106+
{{- else if .Render.IsSeperate -}}
107+
package api
108+
109+
import (
110+
"log"
111+
"net/http"
112+
113+
"{{ .ModPath }}/config"
114+
115+
"github.com/go-chi/chi/v5"
116+
"github.com/go-chi/chi/v5/middleware"
117+
)
118+
119+
type ServerConfig struct {
120+
// Serving static assets from `web` folder.
121+
ServeStatic func(*chi.Mux)
122+
}
123+
124+
type APIServer struct {
125+
listenAddr string
126+
env config.EnvConfig
127+
ServerConfig
128+
}
129+
130+
func NewAPIServer(env config.EnvConfig, cfg ServerConfig) *APIServer {
131+
return &APIServer{
132+
listenAddr: ":" + env.PORT,
133+
env: env,
134+
ServerConfig: cfg,
135+
}
136+
}
137+
138+
// Start will run the API Server
139+
// Any global middlewares like Logger should be registered here.
140+
func (api *APIServer) Start() error {
141+
mux := chi.NewMux()
142+
143+
// Global Middlewares
144+
api.registerGlobalMiddlewares(mux)
145+
146+
// Routes
147+
r := NewRouter(api.env)
148+
r.RegisterRoutes(mux)
149+
150+
log.Printf("Visit http://localhost%s", api.listenAddr)
151+
152+
return http.ListenAndServe(api.listenAddr, mux)
153+
}
154+
155+
// Extend the list of global middlewares as needed.
156+
func (api *APIServer) registerGlobalMiddlewares(mux *chi.Mux) {
157+
mux.Use(middleware.Logger) // Logger should come before Recoverer
158+
mux.Use(middleware.Recoverer)
159+
160+
api.ServeStatic(mux)
161+
}
162+
{{- end -}}

template/api/api.go.echo.tmpl

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{{- if .Render.IsTemplates -}}
12
package api
23

34
import (
@@ -81,4 +82,64 @@ func (api *APIServer) registerGlobalMiddlewares(e *echo.Echo) {
8182
e.Renderer = &Template{templates: api.LoadTemplates(e), env: api.env}
8283

8384
api.ServeStatic(e)
84-
}
85+
}
86+
{{- else if .Render.IsSeperate -}}
87+
package api
88+
89+
import (
90+
"log"
91+
92+
"{{ .ModPath }}/config"
93+
94+
"github.com/labstack/echo/v4"
95+
"github.com/labstack/echo/v4/middleware"
96+
)
97+
98+
type ServerConfig struct {
99+
// Serving static assets from `web` folder.
100+
ServeStatic func(*echo.Echo)
101+
}
102+
103+
type APIServer struct {
104+
listenAddr string
105+
env config.EnvConfig
106+
ServerConfig
107+
}
108+
109+
func NewAPIServer(env config.EnvConfig, cfg ServerConfig) *APIServer {
110+
return &APIServer{
111+
listenAddr: ":" + env.PORT,
112+
env: env,
113+
ServerConfig: cfg,
114+
}
115+
}
116+
117+
// Start will run the API Server
118+
// Any global middlewares like Logger should be registered here.
119+
func (api *APIServer) Start() error {
120+
e := echo.New()
121+
122+
// Global Middlewares
123+
api.registerGlobalMiddlewares(e)
124+
125+
// Routes
126+
r := NewRouter(api.env)
127+
r.RegisterRoutes(e.Router())
128+
129+
log.Printf("Visit http://localhost%s", api.listenAddr)
130+
131+
return e.Start(api.listenAddr)
132+
}
133+
134+
// Extend the list of global middlewares as needed.
135+
func (api *APIServer) registerGlobalMiddlewares(e *echo.Echo) {
136+
e.Use(middleware.Recover())
137+
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
138+
Format: "-> '${uri}' - ${method} (${status})\n",
139+
}))
140+
141+
e.HTTPErrorHandler = HTTPErrorHandler
142+
143+
api.ServeStatic(e)
144+
}
145+
{{- end -}}

0 commit comments

Comments
 (0)