Development

Cheatsheet

Short GoLazy snippets for common how-can-I changes, written as sample-app deltas.

By Guillermo Alvarez - Published - Updated

This cheatsheet answers "how can I?" with small sample-app changes. Each snippet is intentionally narrow: it shows the files you would add or edit from the generated sample app, and the code comments carry the local context.

The snippets are not a replacement for the guides. Use them to start the change, then follow the linked guide when the app needs the full design, tradeoffs, tests, or deployment notes.

Routes

// init/routes.go
package appinit

import (
	"golazy.dev/lazyroutes"
	homecontroller "sample_app/app/controllers/home_controller"
	postcontroller "sample_app/app/controllers/post_controller"
)

func Draw(router *lazyroutes.Scope) {
	router.Resources(homecontroller.New, func(home *lazyroutes.Resource) {
		home.Singular("home")
		home.Plural("home")
		home.Path("")
	})

	router.Get("/posts/{post_id}", postcontroller.New, (*postcontroller.PostsController).Show)
	router.Post("/posts", postcontroller.New, (*postcontroller.PostsController).Create)
}

Controllers

// app/controllers/post_controller/postcontroller.go
package postcontroller

import (
	"context"
	"fmt"

	"sample_app/app/controllers"
	"sample_app/services/postservice"
)

type PostsController struct {
	controllers.BaseController
	posts postservice.Service
}

func New(ctx context.Context) (*PostsController, error) {
	base, err := controllers.NewBaseController(ctx)
	if err != nil {
		return nil, err
	}
	posts, ok := postservice.FromContext(ctx)
	if !ok {
		return nil, fmt.Errorf("post service is missing from application context")
	}
	return &PostsController{BaseController: base, posts: posts}, nil
}

func (c *PostsController) Show(postID int) error {
	post, err := c.posts.Find(postID)
	if err != nil {
		return err
	}
	c.Set("post", post)
	return nil
}

Controllers/Base

// app/controllers/base_controller.go
package controllers

import (
	"context"
	"errors"
	"net/http"

	"golazy.dev/lazycontroller"
)

var ErrLoginRequired = errors.New("login required")

type BaseController struct {
	lazycontroller.Base
}

func NewBaseController(ctx context.Context) (BaseController, error) {
	base, err := lazycontroller.NewBase(ctx)
	if err != nil {
		return BaseController{}, err
	}
	return BaseController{Base: base}, nil
}

func (c *BaseController) HandleError(w http.ResponseWriter, r *http.Request, err error) error {
	if errors.Is(err, ErrLoginRequired) {
		return c.RedirectToRoute("login", lazycontroller.RedirectStatus(http.StatusSeeOther))
	}
	return c.Base.HandleError(w, r, err)
}

Controllers/Generators

// app/controllers/post_controller/postcontroller.go
package postcontroller

import (
	"fmt"
	"net/http"

	"golazy.dev/lazycontroller"
)

type Post struct {
	ID    int
	Title string
}

func (c *PostsController) GenPost(postID int) (*Post, error) {
	post, err := c.posts.Find(postID)
	if err != nil {
		return nil, lazycontroller.Error(http.StatusNotFound, fmt.Errorf("post not found"))
	}
	return post, nil
}

func (c *PostsController) Show(post *Post) error {
	c.Set("post", post)
	return nil
}

Controllers/BeforeFilters

// app/controllers/base_controller.go
package controllers

type User struct {
	ID string
}

type AuthenticatedUser struct {
	*User
}

func (c *BaseController) GenUser() (*User, error) {
	value, ok, err := c.SessionGet("user_id")
	if err != nil {
		return nil, err
	}
	userID, _ := value.(string)
	if !ok || userID == "" {
		return nil, nil
	}
	return &User{ID: userID}, nil
}

func (c *BaseController) GenAuthenticatedUser(user *User) (*AuthenticatedUser, error) {
	if user == nil {
		return nil, ErrLoginRequired
	}
	return &AuthenticatedUser{User: user}, nil
}

func (c *BaseController) BeforeAction(user *User) error {
	c.Set("current_user", user)
	return nil
}
// app/controllers/admin_controller/admincontroller.go
package admincontroller

import "sample_app/app/controllers"

func (c *AdminController) Index(user *controllers.AuthenticatedUser) error {
	c.Set("title", "Admin")
	c.Set("user", user)
	return nil
}

Controllers/ContentTypes

// app/controllers/post_controller/postcontroller.go
package postcontroller

import (
	"net/http"

	"golazy.dev/lazycontroller"
)

var Markdown = lazycontroller.NewFormat(
	"text/markdown",
	lazycontroller.As("markdown"),
	lazycontroller.Suffix("md", "markdown"),
)

func (c *PostsController) Show(w http.ResponseWriter, post *Post) error {
	return c.Wants(lazycontroller.Formats{
		lazycontroller.HTML: func() error {
			c.Set("post", post)
			return nil
		},
		Markdown: func() error {
			c.ContentType("text/markdown; charset=utf-8")
			_, err := w.Write([]byte("# " + post.Title + "\n"))
			return err
		},
	})
}

Controllers/LazyLoading

// app/controllers/post_controller/postcontroller.go
package postcontroller

import "context"

func (c *PostsController) Show(post *Post) error {
	c.Set("post", post)

	// SetLater starts loading immediately; use it where older notes say SetEventually.
	c.SetLater("related_posts", func(ctx context.Context) ([]Post, error) {
		return c.posts.Related(ctx, post.ID)
	})

	// SetWhenNeeded waits until the template first reads the value.
	c.SetWhenNeeded("comment_count", func(ctx context.Context) (int, error) {
		return c.posts.CommentCount(ctx, post.ID)
	})

	return nil
}

Controllers/Session

// init/app.go
package appinit

import (
	"os"

	"golazy.dev/lazyapp"
	"golazy.dev/lazysession"
	"sample_app/app"
)

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:         "sample_app",
		Drawer:       Draw,
		Public:       app.Public,
		Views:        app.Views,
		Dependencies: Dependencies,
		Sessions: lazysession.Config{
			Key: os.Getenv("SESSION_KEY"),
		},
	})
}
// app/controllers/session_controller/sessioncontroller.go
package sessioncontroller

import (
	"net/http"

	"golazy.dev/lazycontroller"
)

func (c *SessionsController) Create(form *PasswordForm) error {
	if err := c.SessionSet("user_id", form.Username); err != nil {
		return err
	}
	if err := c.FlashSet("notice", "Signed in"); err != nil {
		return err
	}
	return c.RedirectToRoute("home", lazycontroller.RedirectStatus(http.StatusSeeOther))
}

func (c *SessionsController) Delete() error {
	if err := c.SessionDelete("user_id"); err != nil {
		return err
	}
	return c.RedirectToRoute("home", lazycontroller.RedirectStatus(http.StatusSeeOther))
}

Controllers/Variants

// app/controllers/post_controller/postcontroller.go
package postcontroller

func (c *PostsController) Show(post *Post) error {
	if post.Featured {
		c.Variants("featured")
	}
	c.Set("post", post)
	return nil
}
# app/views/posts/show.html.tpl
# app/views/posts/show.html+featured.tpl
# app/views/posts/_card.html.tpl
# app/views/posts/_card.html+featured.tpl

Forms

// app/controllers/session_controller/password_form.go
package sessioncontroller

type PasswordForm struct {
	Username string `validate:"presence"`
	Password string `validate:"presence"`
}

func (c *SessionsController) GenPasswordForm() (*PasswordForm, error) {
	form := &PasswordForm{}
	if err := c.Decode(form); err != nil {
		return nil, err
	}
	return form, nil
}
// app/controllers/session_controller/sessioncontroller.go
package sessioncontroller

import (
	"net/http"

	"golazy.dev/lazycontroller"
	"golazy.dev/lazyerrors"
)

func (c *SessionsController) New() error {
	c.Set("form", &PasswordForm{})
	return nil
}

func (c *SessionsController) Create(form *PasswordForm) error {
	if err := lazyerrors.Validator(form); err != nil {
		c.Status(http.StatusUnprocessableEntity)
		c.Set("form", form)
		c.Set("session_errors", err)
		return c.Render("new")
	}
	return c.RedirectToRoute("home", lazycontroller.RedirectStatus(http.StatusSeeOther))
}
{{/* app/views/session/new.html.tpl */}}
{{ form_for .form . (form_action "/session") (form_file "session_fields") }}
{{/* app/views/session/_session_fields.html.tpl */}}
{{ text_field "Username" }}
{{ password_field "Password" }}
{{ range field_errors_for .session_errors "username" }}
  <p>{{.}}</p>
{{ end }}
{{ submit_button "Sign in" }}

SEO

// init/seo.go
package appinit

import (
	"context"

	"golazy.dev/lazyseo"
)

func SEO(ctx context.Context) []lazyseo.Option {
	return []lazyseo.Option{
		lazyseo.SiteName("Sample App"),
		lazyseo.Description("A small GoLazy application."),
		lazyseo.Language("en"),
		lazyseo.Locale("en_US"),
		lazyseo.Type("website"),
	}
}
// init/app.go
package appinit

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:         "sample_app",
		Drawer:       Draw,
		Public:       app.Public,
		Views:        app.Views,
		Dependencies: Dependencies,
		SEO:          SEO,
	})
}
{{/* app/views/layouts/app.html.tpl */}}
<!doctype html>
<html lang="{{seo_lang}}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {{seo}}
    {{stylesheet "/styles.css"}}
  </head>
  <body>{{.content}}</body>
</html>
// app/controllers/post_controller/postcontroller.go
package postcontroller

func (c *PostsController) Show(post *Post) error {
	c.Title(post.Title)
	c.Description(post.Summary)
	c.Canonical("https://example.com/posts/" + post.Slug)
	c.Set("post", post)
	return nil
}

SEO/href-lang-tags

// app/controllers/post_controller/postcontroller.go
package postcontroller

func (c *PostsController) Show(post *Post) error {
	c.Language(post.Language)
	c.Canonical("https://example.com/" + post.Language + "/posts/" + post.Slug)
	c.Alternate("en", "https://example.com/en/posts/"+post.Slug)
	c.Alternate("de", "https://example.com/de/posts/"+post.Slug)
	c.Alternate("x-default", "https://example.com/posts/"+post.Slug)
	c.Set("post", post)
	return nil
}

SEO/alternates

// app/controllers/post_controller/postcontroller.go
package postcontroller

import "golazy.dev/lazyseo"

func (c *PostsController) Show(post *Post) error {
	c.SEO(lazyseo.AlternateLink(lazyseo.Alternate{
		Type:  "application/rss+xml",
		Title: "Posts feed",
		URL:   "https://example.com/posts/feed.xml",
	}))
	c.SEO(lazyseo.AlternateLink(lazyseo.Alternate{
		Media: "only screen and (max-width: 640px)",
		URL:   "https://m.example.com/posts/" + post.Slug,
	}))
	c.Set("post", post)
	return nil
}

SEO/Metadata (JSONLD)

// services/postservice/post.go
package postservice

import (
	"time"

	"golazy.dev/lazyseo"
	"golazy.dev/lazyseo/jsonld"
)

type Post struct {
	Slug        string
	TitleValue  string
	Summary      string
	ImageURL     string
	PublishedAt time.Time
	UpdatedAt   time.Time
}

func (p Post) Title() string            { return p.TitleValue }
func (p Post) Description() string      { return p.Summary }
func (p Post) Canonical() string        { return "https://example.com/posts/" + p.Slug }
func (p Post) Image() string            { return p.ImageURL }
func (p Post) Kind() lazyseo.PageKind   { return lazyseo.Article }
func (p Post) PublishedTime() time.Time { return p.PublishedAt }
func (p Post) LastUpdated() time.Time   { return p.UpdatedAt }
func (p Post) JSONLD() any {
	article := jsonld.NewArticle(p.TitleValue)
	article.Description = p.Summary
	article.URL = p.Canonical()
	article.Image = p.ImageURL
	article.DatePublished = jsonld.Date(p.PublishedAt)
	article.DateModified = jsonld.Date(p.UpdatedAt)
	return article
}
// app/controllers/post_controller/postcontroller.go
package postcontroller

import "sample_app/services/postservice"

func (c *PostsController) Show(post postservice.Post) error {
	c.Metadata(post)
	c.Set("post", post)
	return nil
}

Cache/Actions

// app/controllers/post_controller/postcontroller.go
package postcontroller

func (c *PostsController) Show(post *Post) error {
	if c.CacheKey(post.ID, post.UpdatedAt) {
		return nil
	}
	c.Set("post", post)
	return nil
}

func (c *PostsController) Sidebar() error {
	if c.CacheKeyF("posts-sidebar", c.posts.UpdatedAt()) {
		return nil
	}
	c.NoLayout()
	c.Set("posts", c.posts.Latest())
	return c.Render("sidebar")
}

Cache/Views

{{/* app/views/posts/index.html.tpl */}}
{{ range .posts }}
  {{ cache (cache_key "post-card" .ID .UpdatedAt) "post_card" . }}
{{ end }}
{{/* app/views/posts/show.html.tpl */}}
{{ turbo_frame "post" .post (cache_key "post" .post.ID .post.UpdatedAt) (turbo_src .post.URL) }}
{{/* app/views/posts/_post_card.html.tpl */}}
<article>
  <h2>{{.Title}}</h2>
  <p>{{.Summary}}</p>
</article>

Turbo/Visits

// app/js/app.js
// golazy:turbo
// golazy:stimulus
{{/* app/views/layouts/app.html.tpl */}}
{{importmap "/assets/importmap.json"}}
<script type="module">import "app.js"</script>
{{/* app/views/home/index.html.tpl */}}
<a href="{{path_for "posts"}}" data-turbo-action="advance">Posts</a>
<a href="{{path_for "home"}}" data-turbo="false">Full reload</a>

Turbo/Forms

{{/* app/views/posts/new.html.tpl */}}
{{ turbo_frame "post_form" . }}
{{/* app/views/posts/_post_form_frame.html.tpl */}}
<form action="{{path_for "posts"}}" method="post" data-turbo-frame="post_form">
  <label for="post_title">Title</label>
  <input id="post_title" name="title" value="{{.form.Title}}">
  {{ range field_errors_for .post_errors "title" }}
    <p>{{.}}</p>
  {{ end }}
  <button type="submit">Create post</button>
</form>

Turbo/Frames

{{/* app/views/posts/show.html.tpl */}}
<section>
  <h1>{{.post.Title}}</h1>
  {{ turbo_frame "comments" . (turbo_src (path_for "post_comments" .post.ID)) (turbo_loading "lazy") }}
</section>
{{/* app/views/posts/_comments_frame.html.tpl */}}
<ol>
  {{ range .comments }}
    <li>{{.Body}}</li>
  {{ else }}
    <li>No comments yet.</li>
  {{ end }}
</ol>

Turbo/Frames/Controllers

// app/controllers/post_controller/postcontroller.go
package postcontroller

import "golazy.dev/lazyturbo"

func (c *PostsController) Comments(post *Post) error {
	c.Set("comments", c.posts.Comments(post.ID))
	return c.RenderTurboFrame("comments", lazyturbo.Loading("lazy"))
}
{{/* app/views/posts/_comments_frame.html.tpl */}}
<ol>
  {{ range .comments }}
    <li>{{.Body}}</li>
  {{ else }}
    <li>No comments yet.</li>
  {{ end }}
</ol>

Turbo/Streams

{{/* app/views/posts/create.turbo_stream.tpl */}}
<turbo-stream action="prepend" target="posts">
  <template>
    {{ partial "post_card" .post }}
  </template>
</turbo-stream>

<turbo-stream action="replace" target="post_form">
  <template>
    {{ turbo_frame "post_form" . }}
  </template>
</turbo-stream>

Turbo/Streams/Controller

// app/controllers/post_controller/postcontroller.go
package postcontroller

import "golazy.dev/lazycontroller"

func (c *PostsController) Create(form *PostForm) error {
	post, err := c.posts.Create(form.Title)
	if err != nil {
		return err
	}
	c.Set("post", post)
	c.Set("form", &PostForm{})
	return c.Wants(lazycontroller.Formats{
		lazycontroller.HTML: func() error {
			return c.RedirectToRoute("post", post.ID)
		},
		lazycontroller.TurboStream: func() error {
			return c.Render("create")
		},
	})
}

Turbo/ControllerFrames

// app/controllers/post_controller/postcontroller.go
package postcontroller

import "golazy.dev/lazycontroller"

func (c *PostsController) Show(post *Post) error {
	c.Set("post", post)
	return c.Wants(lazycontroller.Formats{
		lazycontroller.HTML: func() error {
			return nil
		},
		lazycontroller.TurboFrame: func() error {
			return c.RenderTurboFrame("post")
		},
	})
}

Turbo/Controller

// app/controllers/post_controller/postcontroller.go
package postcontroller

import (
	"net/http"

	"golazy.dev/lazycontroller"
)

func (c *PostsController) Update(post *Post, form *PostForm) error {
	updated, err := c.posts.Update(post.ID, form.Title)
	if err != nil {
		return err
	}
	c.Set("post", updated)
	return c.Wants(lazycontroller.Formats{
		lazycontroller.HTML: func() error {
			return c.RedirectToRoute("post", updated.ID, lazycontroller.RedirectStatus(http.StatusSeeOther))
		},
		lazycontroller.TurboFrame: func() error {
			return c.RenderTurboFrame("post")
		},
		lazycontroller.TurboStream: func() error {
			return c.Render("update")
		},
	})
}

Tailwind

/* app/styles/application.css */
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));
{{/* app/views/layouts/app.html.tpl */}}
{{stylesheet "/styles.css"}}
# Regenerate app/public/styles.css after changing Tailwind input.
lazy tailwind

lazyshaft

# js.toml
[entrypoint.turbo]
module = "@hotwired/turbo"

[entrypoint.stimulus]
module = "@hotwired/stimulus"
# Regenerate app/public/assets/importmap.json and app/public/assets/lazyshaft.
lazy js
{{/* app/views/layouts/app.html.tpl */}}
{{importmap "/assets/importmap.json"}}
<script type="module">import "app.js"</script>

Js

// app/js/app.js
// golazy:turbo
// golazy:stimulus
// app/js/controllers/counter_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["output"]
  static values = { count: { type: Number, default: 0 } }

  increment() {
    this.countValue += 1
    this.outputTarget.textContent = String(this.countValue)
  }
}
{{/* app/views/home/index.html.tpl */}}
<div data-controller="counter">
  <output data-counter-target="output">0</output>
  <button type="button" data-action="counter#increment">Increment</button>
</div>

Assets

// app/files.go
package app

import (
	"embed"

	"golazy.dev/lazyapp"
)

//go:embed views public
var Files embed.FS

var Views = lazyapp.MustSub(Files, "views")
var Public = lazyapp.MustSub(Files, "public")
// init/app.go
package appinit

import (
	"golazy.dev/lazyapp"
	"sample_app/app"
)

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:         "sample_app",
		Drawer:       Draw,
		Public:       app.Public,
		Views:        app.Views,
		Dependencies: Dependencies,
	})
}
{{/* app/views/layouts/app.html.tpl */}}
{{stylesheet "/styles.css"}}
<img src="{{asset_path "/images/logo.svg"}}" alt="Logo">

Mailers

// init/dependencies.go
package appinit

import (
	"context"
	"fmt"
	"net/smtp"
	"os"

	"golazy.dev/lazydeps"
	"golazy.dev/lazymailer"
)

func Dependencies(deps *lazydeps.Scope) error {
	_, err := lazydeps.Service(deps, "mailer", func(ctx context.Context) (
		context.Context,
		*lazymailer.Mailer,
		error,
		context.CancelFunc,
	) {
		deliveries := lazymailer.NewRegistry("default", map[string]lazymailer.Delivery{
			"default": lazymailer.SMTPDelivery{
				Addr: os.Getenv("SMTP_ADDR"),
				Auth: smtp.PlainAuth("", os.Getenv("SMTP_USER"), os.Getenv("SMTP_PASSWORD"), os.Getenv("SMTP_HOST")),
			},
		})
		mailer, err := lazymailer.New(ctx, deliveries)
		if err != nil {
			return ctx, nil, fmt.Errorf("initialize mailer: %w", err), nil
		}
		return lazymailer.WithContext(ctx, mailer), mailer, nil, nil
	})
	return err
}
// app/mailers/notice_mailer/noticemailer.go
package noticemailer

import (
	"context"

	"golazy.dev/lazymailer"
)

type NoticeMailer struct {
	base lazymailer.Base
}

func New(ctx context.Context) (*NoticeMailer, error) {
	base, err := lazymailer.NewBase(ctx, "notice_mailer", lazymailer.Defaults{
		From:     lazymailer.MustParseAddress("GoLazy <[email protected]>"),
		Delivery: "default",
		Layout:   "mailer",
	})
	if err != nil {
		return nil, err
	}
	return &NoticeMailer{base: base}, nil
}

func (m *NoticeMailer) Welcome(to lazymailer.Address, name string) error {
	m.base.Set("name", name)
	return m.base.Mail(lazymailer.Options{
		Action:  "welcome",
		To:      []lazymailer.Address{to},
		Subject: "Welcome",
	})
}
{{/* app/views/notice_mailer/welcome.html.tpl */}}
<p>Hello {{.name}}</p>

Jobs

// app/jobs/basejob/basejob.go
package basejob

import "golazy.dev/lazyjobs"

type BaseJob struct {
	lazyjobs.BaseJob
}
// app/jobs/send_welcome/sendwelcome.go
package sendwelcome

import (
	"context"

	"sample_app/app/jobs/basejob"
)

type Job struct {
	basejob.BaseJob
	UserID string `json:"user_id"`
}

func (*Job) Kind() string { return "mail.send_welcome" }

func (j *Job) Work(ctx context.Context) error {
	// Load services from ctx and send the welcome message for j.UserID.
	return nil
}
// app/jobs/jobs.go
package jobs

import (
	"golazy.dev/lazyjobs"
	"sample_app/app/jobs/send_welcome"
)

func DefinedJobs(runner *lazyjobs.JobRunner) {
	runner.MustRegister(&sendwelcome.Job{})
}
// init/app.go
package appinit

import (
	"golazy.dev/lazyapp"
	"golazy.dev/lazyjobs"
	"sample_app/app"
	"sample_app/app/jobs"
)

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:         "sample_app",
		Drawer:       Draw,
		Public:       app.Public,
		Views:        app.Views,
		Dependencies: Dependencies,
		Jobs: lazyapp.Jobs(lazyjobs.Config{
			Define:  jobs.DefinedJobs,
			Workers: 2,
		}),
	})
}

Services

// services/postservice/postservice.go
package postservice

import "context"

type Post struct {
	ID    int
	Title string
}

type Service interface {
	Latest() []Post
	Find(id int) (*Post, error)
}

type service struct{}
type contextKey struct{}

func New() Service {
	return service{}
}

func WithContext(ctx context.Context, service Service) context.Context {
	return context.WithValue(ctx, contextKey{}, service)
}

func FromContext(ctx context.Context) (Service, bool) {
	service, ok := ctx.Value(contextKey{}).(Service)
	return service, ok
}

func (service) Latest() []Post {
	return []Post{{ID: 1, Title: "Hello"}}
}

func (service) Find(id int) (*Post, error) {
	return &Post{ID: id, Title: "Hello"}, nil
}
// init/dependencies.go
package appinit

import (
	"context"

	"golazy.dev/lazydeps"
	"sample_app/services/postservice"
)

func Dependencies(deps *lazydeps.Scope) error {
	_, err := lazydeps.Service(deps, "postservice", func(ctx context.Context) (
		context.Context,
		postservice.Service,
		error,
		context.CancelFunc,
	) {
		service := postservice.New()
		return postservice.WithContext(ctx, service), service, nil, nil
	})
	return err
}

PWA

// init/app.go
package appinit

import (
	"golazy.dev/lazyapp"
	"golazy.dev/lazypwa"
	"sample_app/app"
)

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:   "sample_app",
		Drawer: Draw,
		Public: app.Public,
		Views:  app.Views,
		PWA: lazypwa.Config{
			Installable: true,
			Manifest: lazypwa.ManifestConfig{
				Name:       "Sample App",
				ShortName:  "Sample",
				StartURL:   "/",
				Scope:      "/",
				Display:    "standalone",
				ThemeColor: "#FDDD00",
			},
			Offline: lazypwa.OfflineConfig{
				Enabled:       true,
				FallbackURL:   "/",
				URLs:          []string{"/"},
				IncludeAssets: true,
			},
		},
	})
}
{{/* app/views/layouts/app.html.tpl */}}
<head>
  {{seo}}
  {{pwa}}
</head>

MCP

// app/mcps/admin_mcp/adminmcp.go
package adminmcp

import (
	"context"

	"golazy.dev/lazymcp"
)

type AdminMCP struct {
	lazymcp.Base
}

func New(ctx context.Context) *AdminMCP {
	return &AdminMCP{Base: lazymcp.NewBase(ctx)}
}

type UserCountParams struct {
	Active bool `json:"active"`
}

type UserCountResult struct {
	Count int `json:"count"`
}

func (m *AdminMCP) UserCountTool(ctx context.Context) lazymcp.ToolSpec {
	return lazymcp.ToolSpec{
		Desc: "Count users.",
		Fn:   m.UserCount,
		UI:   lazymcp.UI("ui://admin/dashboard"),
	}
}

func (m *AdminMCP) UserCount(ctx context.Context, params UserCountParams) (UserCountResult, error) {
	return UserCountResult{Count: 3}, nil
}

func (m *AdminMCP) DashboardApp(ctx context.Context) lazymcp.AppSpec {
	return lazymcp.AppSpec{
		Name:      "dashboard",
		Desc:      "Admin dashboard.",
		View:      "dashboard",
		UseLayout: true,
	}
}
// init/mcp.go
package appinit

import (
	"context"

	"golazy.dev/lazymcp"
	adminmcp "sample_app/app/mcps/admin_mcp"
)

func RegisterMCP(ctx context.Context, scope *lazymcp.Scope) error {
	return scope.Register(adminmcp.New(ctx))
}
// init/app.go
package appinit

import (
	"golazy.dev/lazyapp"
	"sample_app/app"
)

func App() *lazyapp.App {
	return lazyapp.New(lazyapp.Config{
		Name:         "sample_app",
		Drawer:       Draw,
		Public:       app.Public,
		Views:        app.Views,
		Dependencies: Dependencies,
		MCP:          RegisterMCP,
	})
}
{{/* app/views/mcp/admin/dashboard.html.tpl */}}
<section>
  <h1>Admin</h1>
  <p>Rendered as an MCP Apps UI resource.</p>
</section>

See Others