Hello Doors

In this tutorial, we'll build a small weather dashboard with Doors. Along the way, you'll learn the core building blocks of the framework: rendering HTML with GoX, serving a page with Doors, wiring routes, and learning a few helpful APIs.

The full code for this tutorial is available at github.com/doors-dev/tutorial.

To keep the tutorial focused on Doors, many example handlers intentionally ignore database, HTTP, and other errors. In real applications, you should check and handle those errors.

Finished tutorial app preview

Install

Make sure you're using Go 1.25.1 or newer:

go version

Doors is built on top of GoX, so editor support matters a lot here. Use one of these:

You can also follow the manual installation steps in the GoX README.

It is also a good idea to have the gox binary on your PATH. That gives you commands like gox fmt and gox gen, and it helps editor tooling and code agents run the same workflow explicitly when needed.

The practical workflow is:

  • write GoX source in .gox
  • use .go for ordinary Go files
  • treat .x.go as generated output and do not edit it directly

The GoX language server keeps generated .x.go files in sync when you save a .gox file. You can still run gox gen or gox fmt manually whenever you want.

Setup

Create a directory for the tutorial app:

mkdir doors-tutorial
cd doors-tutorial

Initialize a Go module and install Doors:

go mod init github.com/doors-dev/tutorial
go get github.com/doors-dev/doors

App

Create app.gox:

package main

import "github.com/doors-dev/gox"

type App struct{}

elem (a App) Main() {
	<!doctype html>
	<html lang="en">
		<head>
			<meta charset="utf-8">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title>Hello Doors!</title>
		</head>
		<body>
			<main class="container">
				<h1>Hello Doors!</h1>
			</main>
		</body>
	</html>
}

In Doors and GoX, a component needs a Main() method that returns gox.Elem. The elem keyword lets you write HTML directly inside the function body instead of building it manually in Go.

When you save app.gox, the GoX language server generates the matching .x.go file for you.

Serve

Now create main.go:

package main

import (
	"context"
	"net/http"

	"github.com/doors-dev/doors"
	"github.com/doors-dev/gox"
)

func main() {
    // Create a Doors app with a factory for our App.
	app := doors.NewApp(func(ctx context.Context, r doors.Request) gox.Comp {
		return App{}
	})

    // Serve the Doors app on port 8080.
	if err := http.ListenAndServe(":8080", app); err != nil {
		panic(err)
	}
}

Safari on localhost

Doors uses a Secure internal session cookie by default. Chrome and Firefox usually accept secure cookies on http://localhost, but Safari rejects them on plain HTTP. If you test with Safari locally, either use local HTTPS or disable the Secure attribute for development:

app := doors.NewApp(/* ... */, doors.WithConf(doors.Conf{
	ServerSessionCookieNoSecure: true,
}))

Run:

go run .

Then open http://localhost:8080.

Hello Doors page

Styles

For a decent default look, let's add Pico CSS.

You could use a normal <link href="...">, but doors.ResourceExternal(...) also lets Doors track the external URL for CSP if you enable it later.

For resource-backed styles and external URLs, see Styles.

Update app.gox like this:

package main

import (
	"github.com/doors-dev/doors"
	"github.com/doors-dev/gox"
)

type App struct{}

elem (a App) Main() {
	<!doctype html>
	<html lang="en" data-theme="dark">
		<head>
			<meta charset="utf-8">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title>Hello Doors!</title>
			<link
				(doors.ResourceExternal("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"))
				rel="stylesheet">
		</head>
		<body>
			<main class="container">
				<h1>Hello Doors!</h1>
			</main>
		</body>
	</html>
}

doors.ResourceExternal(...) is an attribute modifier. It updates the tag's attributes and lets Doors track the external URL as part of resource metadata.

Attribute modifiers can inspect and change tag attributes. You can build your own by implementing the gox.Modify interface.

Reloading

An easy way to get live reloading for Go files is wgo:

go install github.com/bokwoon95/wgo@latest

Then run it from the project directory:

wgo -file=.go go run .

For the usual editor workflow, the GoX language server already regenerates .x.go files whenever you save .gox, so you usually do not need to watch .gox files separately. In real projects, you may also want to watch files such as .css, .js, or .ts.


Next: Integrations