https://github.com/doors-dev/doors-starter
Doors Starter — A minimal, idiomatic starter project for building reactive web apps with Doors and GoX.
https://github.com/doors-dev/doors-starter
Last synced: 19 days ago
JSON representation
Doors Starter — A minimal, idiomatic starter project for building reactive web apps with Doors and GoX.
- Host: GitHub
- URL: https://github.com/doors-dev/doors-starter
- Owner: doors-dev
- License: apache-2.0
- Created: 2026-05-23T15:07:44.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-11T12:05:57.000Z (20 days ago)
- Last Synced: 2026-06-11T14:06:55.487Z (20 days ago)
- Language: Go
- Size: 25.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Doors Starter
A starter project for [Doors](https://github.com/doors-dev/doors) — a reactive Go web framework built on [GoX](https://github.com/doors-dev/gox).
## Project Structure
```
.
├── main.go # Entry point: creates the Doors app, attaches middleware, starts HTTP server
├── app.gox # Root component: HTML shell with head elements, nav, router, and footer
├── go.mod / go.sum # Go module definition and dependency checksums
│
├── path/ # URL path models — Go structs describing URL shapes
│ └── root.go # Root path model: "/" and "/about" variants
│
├── components/ # Reusable UI components shared across pages
│ ├── nav.gox # Top navigation bar with doors.ALink navigation links
│ ├── footer.gox # Page footer
│ └── not_found.gox # 404 fallback page with reactive path display
│
├── segments/ # Self-contained page groups (one per path model)
│ └── root/ # Segment for the "/" section
│ ├── main.gox # Routes the Root path model to Landing or About
│ ├── landing.gox # Landing page ("/") — includes the counter demo
│ ├── about.gox # About page ("/about")
│ └── counter.gox # Interactive counter (Source + Bind demo)
│
└── assets/ # Embedded static files
├── embed.go # Go embed directives exposing Style bytes and Static FS
├── style.css # Application stylesheet
└── static/ # Raw static files served via UseFS middleware
└── ico.png # Favicon
```
> Generated `.x.go` files are auto-managed by GoX and omitted from this tree.
> The `segments/` layout above is a suggested convention, not a strict rule.
> For larger sections you may want to split further — e.g. separate Go packages per page, or shared `components/` and `drivers/` packages within a segment. Keep the codebase systematically organized in whatever way fits your project.
## How to Extend
### Add a New Page to an Existing Path Model
If you want a new page under the existing `Root` path model (e.g. `/contact`):
1. Add the variant to `path/root.go`:
```go
const (
RootLanding RootPage = iota
RootAbout
RootContact // new
)
```
Update the struct tag: `` `path:"/ | /about | /contact"` ``
2. Create a new page component in `segments/root/`:
```gox
elem Contact(p doors.Source[path.Root]) {
Contact
Contact
}
```
3. Add a new route match in `segments/root/main.gox`:
```gox
doors.RouteMatch(func(p path.Root) bool {
return p.Page == path.RootContact
}).Source(Contact),
```
4. Link to it in `components/nav.gox`:
```gox
```
### Add a New Path Model (New Section)
To add an entirely new section like `/blog`, `/blog/:id`:
1. Create `path/blog.go`:
```go
package path
type Blog struct {
Section BlogSection `path:"/blog | /blog/:ID"`
ID string
}
type BlogSection int
const (
BlogIndex BlogSection = iota
BlogPost
)
```
2. Create `segments/blog/main.gox`:
```gox
package blog
import (
"github.com/doors-dev/doors"
"github.com/doors-dev/doors-starter/path"
)
elem Main(p doors.Source[path.Blog]) {
~(p.Route(
doors.RouteMatch(func(p path.Blog) bool {
return p.Section == path.BlogIndex
}).Source(Index),
doors.RouteDerive(func(p path.Blog) (string, bool) {
return p.ID, p.Section == path.BlogPost
}).Beam(Post),
))
}
```
`RouteDerive` matches when `Section == BlogPost` and derives the ID into a `Beam[string]`. The `Post` component receives just the ID instead of the full Blog source, keeping updates narrower.
3. Create the page components:
```gox
// segments/blog/index.gox
elem Index(p doors.Source[path.Blog]) {
Blog Index
}
// segments/blog/post.gox — receives Beam[string] (the ID)
elem Post(id doors.Beam[string]) {
Post: ~(id.Bind(elem(v string) { ~(v) }))
}
```
4. Add the model to the router in `app.gox`:
```gox
<>
~(doors.Route(
doors.RouteModel(root.Main),
doors.RouteModel(blog.Main), // new — tried in order
doors.RouteLocationDefault(components.NotFound),
))
>
```
### Add a New Static Asset
- **For a cacheable file** (favicon, images): place it in `assets/static/` — it's served via `UseFS` under `/static/`.
- **For a managed resource** (stylesheet, JS): embed it as `[]byte` in `assets/embed.go` and use it inline:
```gox
```
### Use Tailwind CSS
Create a `style.css` at the project root:
```css
@import "tailwindcss";
```
Build it to `assets/style.css` (which is embedded by `assets/embed.go`):
```bash
npx @tailwindcss/cli -i ./style.css -o ./assets/style.css
```
See [Tailwind CLI installation](https://tailwindcss.com/docs/installation/tailwind-cli) for setup instructions. If using npm, add `node_modules/` to `.gitignore`. A standalone binary is also available (slower but no npm needed).
### Inline Scripts and Styles
It is perfectly fine to use inline `` and `<style>` tags directly. Doors converts them into loadable, cacheable resources at render time. Inline scripts are wrapped in an anonymous async function with access to `$data`, `$hook`, `$fetch`, `$on`, and `$sys`. See the [JavaScript docs](https://doors.dev/docs/15-javascript) for details.
### GoX Workflow
- **Write** GoX source in `.gox` files — this is where templates and components live.
- **Use** `.go` files for plain Go logic (path models, embed directives, helpers).
- **`.x.go` files are auto-generated** — the GoX language server keeps them up to date while you edit `.gox` files. Do not edit them manually.
- Run `gox fmt` then `gox gen` (fmt first, gen second) to format and regenerate from the command line.
### Running the Project
```bash
go run .
```
Open [http://localhost:8080](http://localhost:8080).
> **Safari on localhost**: Doors uses a `Secure` session cookie by default, which Safari rejects on plain HTTP. Use `doors.WithConf(doors.Conf{ServerSessionCookieNoSecure: true})` or set up local HTTPS.
> **Local HTTPS**: Use [mkcert](https://github.com/FiloSottile/mkcert) for trusted self-signed certs — `mkcert -install && mkcert localhost 127.0.0.1 ::1`.
### File Watching
Doors reloads the page automatically when it detects the server has restarted. For file watching during development, install [wgo](https://github.com/bokwoon95/wgo) and run:
```bash
wgo -file=.go -file=.css -file=.js -file=.ts go run .
```
This restarts the server on file changes and the browser reloads automatically.
> Less useful in agentic development — the agent typically restarts the server explicitly after changes.
## Learn More
- [https://doors.dev/docs](https://doors.dev/docs)
- [https://doors.dev/tutorial](https://doors.dev/tutorial)
- [doors repo docs](https://github.com/doors-dev/doors/tree/main/docs)