Build And Deploy

Upgrade Guide

Upgrade an existing GoLazy application to the latest GoLazy release.

By Guillermo Alvarez - Published - Updated

Upgrade from v0.1.19

GoLazy v0.1.20 is source-compatible for applications already on v0.1.19. It adds default in-memory auth, PostgreSQL auth, cache byte ceilings, automatic OpenTelemetry startup, route/controller metric labels, richer route traces, lazyjobs schedules and queue limits, response-writer and session helpers, generated BeforeAction arguments, typed form validation helpers, and the latest cheatsheet snippets. Existing application code can keep running, but production deployments should review the operational defaults below before rolling out.

Telemetry also remains source-compatible. lazyapp.New now creates a lazytelemetry.Telemetry instance automatically and registers it in the app dependency lifecycle. Deployments that already set OTEL_TRACES_EXPORTER=otlp or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will start exporting request spans through the standard OpenTelemetry trace exporter after the upgrade, so confirm the endpoint, protocol, and sampler settings before rolling this into production. Prometheus request metrics now populate the route label with the matched route pattern. The http_server_requests_total and http_server_request_duration_seconds series also include controller and action labels for matched controller routes, so dashboards or alerts that aggregate those metrics may need to include or drop those labels explicitly.

Cache behavior remains source-compatible. Applications that rely on the lazyapp.New default in-memory backend now get a 50 MiB cache byte ceiling. Deployments that need another default-backend ceiling can set LAZYAPP_CACHE_SIZE, using bare bytes or case-insensitive Kb, Mb, or Gb units. Applications that need code-owned cache behavior can still provide their own lazyapp.Config.Cache.Backend with inmemorycache.Options.MaxSizeBytes. Production deployments with tight memory limits should also set GOMEMLIMIT below the container memory limit.

The new MCP, auth, OAuth, JWT, and rate-limit packages are additive. Existing applications do not need source changes unless they opt in to lazyapp.Config.Auth, lazyapp.Config.OAuth, or lazyapp.Config.MCP. lazyapp.New now includes a default in-memory lazyauth backend with zero users. Set LAZYAUTH_DEFAULT_PASS to create the bootstrap admin user, and set LAZYAUTH_DEFAULT_USER to use a different username. Apps with custom password, file, PostgreSQL, SSO, or account-service auth should keep using lazyapp.Config.Auth.

The new lazyjobs scheduling and queue-limit APIs are additive. Existing Enqueue, EnqueueIn, EnqueueAt, job definitions, and backends continue to work. Apps that register schedules must use a backend that implements lazyjobs.SchedulerBackend; the bundled in-memory backend and golazy.dev/pg/pgjobs do. PostgreSQL apps should include the latest pgjobs.Migrations() source before enabling scheduled jobs so the lazy_job_schedules table and schedule_key metadata exist.

lazycontroller.Base.ResponseWriter() is additive and does not require source changes. Apps with custom base controllers that only copied the current request or response writer in BindRequest can remove that app-level lifecycle method, use Request() and ResponseWriter(), and keep request setup in BeforeAction.

Generated BeforeAction arguments are additive and do not require source changes. When you next touch a protected controller group, prefer a typed generator such as GenUser() (*User, error) plus BeforeAction(user *User) error over a CurrentUser view value or repeated auth-only user parameters on every action. Keep the login redirect or status mapping in HandleError.

The controller route redirect, session, flash, and Param helpers are additive. Existing RedirectTo, Redirect, lazysession.Get(r), and r.PathValue(...) code keeps working. New or touched controllers should prefer RedirectToRoute with RedirectStatus(http.StatusSeeOther) after successful create/update actions, PermanentRedirectTo or PermanentRedirectToRoute for canonical moves, and SessionGet for read-only session access. Use SessionSet, SessionDelete, FlashSet, and FlashGet for writes so the framework can mark the session dirty and save the cookie only when needed. Application auth wrapper types remain app-owned generated values; no lazy upgrade source rewrite is required.

Generated form arguments are also additive. Existing direct c.Decode(&form) calls can keep working, but new or touched form actions should usually move the request input struct into a nearby file such as password_form.go, implement GenPasswordForm by allocating the form and calling c.Decode(form), and have the action receive *PasswordForm. Decode errors returned by the generator skip the action and go through the same HandleError path as action errors. Application validation failures after a create or update should come from ordinary errors returned by lazyerrors.Validator(form) and optional form-owned Validate() error methods. Render the form view with http.StatusUnprocessableEntity, set the joined validation error for field_errors_for / errors_for, and redirect rather than render after successful creates.

Run the standard module update:

lazy upgrade --target v0.1.20

If an app adopted early migration examples with app-owned files in migrations/postgres and uses lazymigrate.ForDatabase, move those files to db/postgres/migrations before relying on the helper. Apps that intentionally keep another layout can continue using an explicit lazymigrate.FromFS(files, "migrations/postgres").

Upgrade from v0.1.18

GoLazy v0.1.19 adds opt-in PWA and browser-worker support plus bundled application migrations through lazyapp.Config.Migrations. Existing applications do not need source changes unless they want to become installable, register worker scripts through GoLazy, prepare browser push subscriptions, or let the app binary run migrations with LAZYAPP_MIGRATE=up or LAZYAPP_MIGRATE=auto. The matching lazy development panel also adds Terminal, Docs, and Media tabs. Terminal and Docs do not require application source changes. The Media tab shows storage, file, and variant data when an app opts in by passing named storages, *lazyfiles.Files, or *lazymedia.Media through lazyapp.Config.

GoLazy also starts permanently redirecting GET and HEAD trailing-slash requests for registered routes to the same route without trailing slashes. That canonicalization is automatic and does not require application source changes.

Run the standard module update:

lazy upgrade --target v0.1.19

Then add lazyapp.Config.PWA and {{pwa}} in your layout only if the application should opt in. Use lazyapp.Config.Workers for app-owned workers that are not part of the PWA setup. Read PWA, Workers And Notifications. Add lazyapp.Config.Migrations only when the app should bundle migration backends. Object-storage deployments can also opt in to golazy.dev/lazyassets/assetmigrate to upload the current asset registry during LAZYAPP_MIGRATE=up or LAZYAPP_MIGRATE=auto. This is additive; lazy upgrade does not rewrite application source for migrations or asset uploads.

Upgrade from v0.1.17

GoLazy v0.1.18 makes lazyapp.Config.Jobs run after Dependencies with the dependency-initialized app context. Applications without Jobs do not need source changes.

Run the automated migration:

lazy upgrade --target v0.1.18

The migration updates the golazy.dev module requirement and wraps static job configs:

Jobs: lazyapp.Jobs(lazyjobs.Config{
    Define: jobs.DefinedJobs,
})

Apps with durable job backends can instead return the config from a function that reads shared dependencies from context, such as pg.FromContext(ctx) for PostgreSQL-backed jobs. Read Background Jobs and PostgreSQL.

Upgrade from v0.1.16

GoLazy v0.1.17 changes two controller APIs:

  • c.SetLayout("name") becomes c.Layout("name").
  • c.CacheKey(...) and c.CacheKeyF(...) now return true when an existing cached body was written. Return nil immediately on that hit, then continue setting controller data and rendering on a miss. Render cache keys now also include the app build version, and active render variants when present, so existing in-memory render cache entries naturally miss after a new build.

Run the automated migration:

lazy upgrade --target v0.1.17

The migration updates the golazy.dev module requirement, rewrites common SetLayout, CacheKey, and CacheKeyF controller call shapes, and keeps the normal upgrade checks in place.

Upgrade from v0.1.15

GoLazy v0.1.16 does not require source rewrites for applications that already run on v0.1.15. The release adds framework cache support, deferred template values, route-link helpers, richer telemetry, and the DevTools-style lazydev panel. Use lazy upgrade to update the golazy.dev module requirement and run the normal follow-up checks.

After updating the CLI, the default lazy development proxy may show a local HTTPS setup page on the plain HTTP address. Install the GoLazy local certificate authority once per machine or browser trust store to use the development panel and proxied app over HTTPS on the same port. This does not require application source changes.

Install or update the matching CLI:

curl -fsSL https://golazy.dev/install.sh | sh
lazy --version

Run the application migration:

lazy upgrade --target v0.1.16

lazy upgrade applies the framework module update with go get from the release's go.mod manifest, then runs the standard follow-up checks. It does not edit application source for the v0.1.15 -> v0.1.16 step.

Apps that adopted the early JSONL repositories for lazyfiles or lazymedia should update their imports and constructors:

filesRepo, err := filesjsonl.New("storage/files.log.jsonl")
mediaRepo, err := mediajsonl.New("storage/variants.log.jsonl")

Use golazy.dev/lazyfiles/jsonl and golazy.dev/lazymedia/jsonl for those aliases. The parent packages continue to expose the Repository interfaces used by lazyfiles.Files and lazymedia.Media.

PostgreSQL-backed apps can now use golazy.dev/pg/pgfiles and golazy.dev/pg/pgmedia for durable file and media metadata, and golazy.dev/pg/pgstorage anywhere a lazystorage backend is useful, including lazyassets.Upload.

Asset commands may change which Node package manager they use after the CLI upgrade. lazy js and lazy tailwind now prefer active installed mise tools in pnpm, yarn, bun, node order, with node mapped to npm. If mise cannot provide one of those tools, the commands fall back to direct npm / npx. No application source migration is required for this behavior change.

After updating to a CLI with the current lazy js behavior, app-owned files under app/js regenerate as readable, unbundled, content-hashed assets. Manifest library entrypoints remain bundled. App-owned importmap specifiers are now relative to app/js: layouts import app.js, and generated Stimulus code imports controllers such as controllers/hello_controller.js. Run lazy upgrade to rewrite old /js/... imports; the v0.1.17 migration runs lazy js automatically when js.toml is present. Commit the refreshed importmap and lazyshaft files when the generated output changes.

lazy dump, lazy load, and the managed service preparation flow are additive. No source migration is required. Apps can opt in by adding service lifecycle tasks under .mise/tasks/<service>/ and, when explicit ordering is needed, listing service names in lazy.toml.

If you are upgrading from v0.1.14 or earlier, first run the earlier automated migrations or read the previous versioned upgrade guides. The v0.1.14 -> v0.1.15 step still migrates dependency initializers and SEO defaults before the v0.1.16 module update runs.

Review New Features

Caching

lazyapp.App now owns a default cache and exposes it through golazy.dev/lazycache. Controller views, partials, and Turbo frame bodies can use the cache helpers. Render cache keys include the app build version and active render variants when present. Lazydev exposes cache state, keys, entry content, and On/Off actions. Read Caching.

Deferred Template Values

Controllers can use SetLater and SetWhenNeeded when a template value is expensive or only needed in some render paths. Templates resolve the value with .Value. Read Template Data And Helpers.

Templates can use link_to, path_for, attr, data, and unless_current to render escaped links to named routes without handwritten anchor tags. Read Template Data And Helpers.

Telemetry

GoLazy can enable OpenTelemetry-compatible tracing, logging, request IDs, and in-memory metrics from meaningful OTEL_* environment variables. Set OTEL_SDK_DISABLED=true to disable telemetry explicitly. Prometheus metrics are served from the control plane when OTEL_METRICS_EXPORTER=prometheus and CONTROL_PLANE_ADDR are configured. Read Telemetry.

Background Jobs

Applications can opt into golazy.dev/lazyjobs through lazyapp.Config.Jobs. Existing apps do not need source changes unless they want background jobs. Read Background Jobs.

lazydev Panel

The lazy development loop now embeds a DevTools-style panel at /_golazy/ with App, Requests, Services, Terminal, Routes, Jobs, BuildInfo, Assets, and Cache tabs. It also forwards request IDs and trace context to the child app so panel artifacts line up with framework telemetry. The Requests tab can list captured request paths, filter by path or category, and open lazy-loaded Headers, Tracing, and Logs details when detailed request monitoring is enabled. The Routes tab reads the app's development control plane, so no application source change is needed to see registered routes there; static GET routes link to the running app. The BuildInfo tab shows the running app's Go version, command path, main module, dependencies, replacements, recorded build settings, and the last Go build trace summary; no application source migration is required. The Dependencies tab shows the running app's lazydeps service graph, progressively enhanced into SVG from server-rendered table rows. It can simulate lazydev shutdown by sending 10 requests per second to the app root for a configured delay, marking /readyz not ready, showing active requests and connections, and coloring services as dependency cancellation progresses; no application source migration is required. The App tab owns app status, changed-file groups, and rebuild, restart, and open-app controls. The Services tab can restart, stop, or start managed local services when service lifecycle tasks are present, groups output by service tree and script name, parses JSON log lines into message and attribute columns, caps log output at 100 rows, and shows run numbers for repeated task attempts such as check; no application source migration is required. The Assets tab lists the app asset manifest and public paths, and the Terminal tab opens an xterm.js shell through a lazy-owned WebSocket in the current app root. The Cache tab shows cache size, usage, searchable keys, selected entry content, and cache controls. Detailed request monitoring also adds per-region self time and allocation samples to the Requests tab without requiring application source changes. Panel tab streams hydrate their lists when they connect and then update only from relevant backend events. The status bar keeps its status stream inside the permanent Turbo frame, so tab switches keep one long-lived status connection; no application source migration is required for that behavior. The proxy also serves Chrome DevTools Automatic Workspace Folders metadata for the app's app/js folder; no application route or source migration is required. The injected host script also exposes window.disableDevPanel() for the Chrome DevTools extension; no application source migration is required. The proxy now injects hidden panel host markup that is controlled by devpanel_controller.js; no application template migration is required. Read lazydev and Lazy.

The same public port accepts plain HTTP and HTTPS. Plain HTTP serves the local certificate authority setup page until the browser trusts the GoLazy CA; HTTPS serves the panel and proxied app traffic, with HTTP/2 available for the panel's long-lived streams.