Self-hosted service
adhocteam/pushup avatar
adhocteam/pushup

Pushup: a page-oriented web framework that compiles to Go

Pushup is for making modern, page-oriented web apps in Go

852 stars30 forksGoMIT

At a glance

What is it?
Pushup is an experiment in a different kind of Go web framework, where a page is a .up file mixing HTML, Go code and a small markup language, and a compiler turns those files into pure Go built on net/http. The README is blunt that it is preview software with a possible risk of data loss and expects breaking changes.
Who is it for?
Pushup is worth a weekend of experimentation for a Go developer who wants server rendered pages without a template language and without a client side framework, because the compile step produces ordinary Go on net/http and the page to URL mapping removes route boilerplate.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 113 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The proposition: the page is the unit of design

Pushup is described as an experimental project exploring the viability of a new approach to web frameworks in Go. Its stated goal is to make building page-oriented, server-side web apps using Go easy, and it embraces the server while acknowledging that modern web apps improved on interactivity.

The argument behind the design is short and worth quoting in spirit: the page is the right level of abstraction for most kinds of server-side web apps.

Three things follow from that idea, and the README lists them as the main aspects of Pushup. The first is an opinionated project directory structure that enables file-based routing. The second is a lightweight markup alternative to traditional web framework templates, one that combines Go code for control flow and imperative, view-controller-like code with HTML markup. The third is a compiler that parses that markup and generates pure Go code, building standalone web apps on top of the Go standard library net/http package.

That last point is the important one for anyone weighing the framework. The output is not a template interpreted at request time. It is Go source that gets compiled into a binary, with the runtime underneath being net/http and a thin runtime package. The cost is a build step; the benefit is that the resulting server has no template engine in the request path.

What a .up page looks like

The core object is the page: a file with the .up extension containing a mix of HTML, Go code and the markup language that glues them together. The extension matters, because it is what tells the compiler that the file is a Pushup page.

The README's example page starts with an import directive, then a braced block that assigns a title variable, then HTML that interpolates that variable, and then an if and else pair written with the same syntax Go uses. The markup language is therefore not a separate expression language. Go syntax is the expression language.

Routing falls out of the filesystem. Pushup pages participate in URL routing by virtue of their path, so a file at app/pages/hello.up is automatically mapped to the URL path /hello once the app is built and run. Dynamic routes are handled by the same mechanism with parameterised filenames.

The directive set listed in the README covers the template mechanics: an import directive, a layout directive and its no-layout variant, the braced block, a handler directive, control flow statements for conditionals and loops, and section and partial directives for layout and template composition. Escaping is handled by the framework rather than by the author, which is the part a security minded reviewer should look at first in a new template engine.

There is also a Vim syntax file in the repository, a small sign that the author expects people to edit these files by hand in a text editor.

The compiler pipeline in the repository

The top level of the repository reads like a compiler written by someone who knows compilers.

There is a lexer.go and a parser.go, an ast.go for the tree, a codegen.go and a compiler.go, and an entities.go that holds the named character references. The entities file is generated rather than written by hand: the Makefile has a rule that runs a helper program from the tools directory and redirects its output into entities.go.

The runtime side lives in a directory named _runtime, which is compiled and tested as a separate package. The test files at the top level, lexer_test.go, parser_test.go, codegen_test.go, compiler_test.go and main_test.go, mirror that structure, and the Makefile test target runs the root package together with the runtime package.

There is also a reloader.go, which is the piece that makes the edit and refresh loop bearable during development, and a vendor directory, so the compiler builds without network access.

The README states that the project has a decent unit test suite, including fuzzing test cases for the parser. For a project whose input is a markup language that mixes HTML and code, fuzzing the parser is the correct investment, and its presence is a point in the project favour even though the quality label on the release is not.

Installing Pushup four different ways

The README gives four installation paths, and the prerequisite is a Go toolchain.

The stated prerequisite is go 1.18 or later, and it also reminds you to check that the directory where the go tool installs executables is on your PATH, which is the GOPATH bin directory. There is a one line check for that.

The simplest path is the release page, where you download a binary for your platform. The most reproducible is Nix, and the README gives four Nix variants: installing into your local profile, opening a temporary shell, opening a temporary shell with flakes, and running the tool without installing it at all.

bash
$ nix-shell -p pushup
bash
$ nix shell nixpkgs#pushup
bash
$ nix run nixpkgs#pushup

The package is in the unstable channel of nixpkgs. For Go developers the shortest route is go install against the module path.

bash
go install github.com/adhocteam/pushup@latest

Building from a git checkout works too, with a clone and a make.

One inconsistency is worth flagging. The README says Go 1.18 or later, but the module file in the repository declares go 1.23, and the Dockerfile builds from golang:1.19.1-alpine3.15, which is older than the module requirement. So the documented prerequisite, the module requirement and the container image disagree, and the module file is the one the toolchain will enforce.

Scaffolding a project and the Docker quick start

The quick start path builds the tool as a container image and then uses it to scaffold and run a project.

bash
git clone https://github.com/adhocteam/pushup.git
cd pushup
make build-docker

The image itself is a straightforward Alpine build on top of a Go base image, with the module files copied first so that dependency downloads are cached between builds.

dockerfile
FROM golang:1.19.1-alpine3.15
RUN apk update upgrade
RUN apk add build-base git
WORKDIR /usr/src/app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN make
ENV GOCACHE=/usr/src/app/.cache
ENTRYPOINT [ "/go/bin/pushup" ]

The scaffold step then creates a project directory for you, and the run step starts it with the working directory mounted and the user identity passed through so that files created inside the container belong to you rather than to root.

bash
docker run --rm -v $(pwd):/usr/src/app --user $(id -u):$(id -g) pushup new myproject
cd myproject
docker run --rm -v $(pwd):/usr/src/app --user $(id -u):$(id -g) -p 8080:8080 pushup run

The command line therefore has a new verb for scaffolding and a run verb for serving, on top of the make targets in the Makefile, which include an install target that compiles with the GC flags for debugging, a build target that produces a binary named pushup, and a lint target that shells out to golangci-lint.

The module file shows the dependency footprint, which is small: file system notification, a comparison library for tests, and a few golang.org/x packages.

The preview label and what it means for use

The Project status section of the README is the first thing to read, and it is unusually direct.

Pushup is called an experiment. In terms of the development life cycle it should be considered preview pre-release software: largely functional, likely to have significant bugs including the potential for data loss, and possibly subpar performance. It is described as suitable for demos and testing. The README says not to count on it for anything serious yet, and to expect significant breaking changes.

That is a self assessment worth respecting rather than talking around. A template and routing system that can lose data is not a problem you can shrug off with a backup, because the failure would be in the request path rather than in storage.

The release history explains the context. There are two tags, v0.1 as a preview in January 2023 and v0.2 in December 2023, while the last push to the repository was on 2026-07-02. So the code is being touched, but no new version has been published in close to three years, and the version numbering has stayed in the zero range the whole time.

The practical advice is to treat the tool the way the author does. Use it for a demo or to evaluate the idea, keep the data you point at it throwaway, and read the tests, especially the fuzzing cases for the parser, before you trust it with a request you care about.

Where Pushup sits against the alternatives

The obvious comparison is with the standard Go template approach, using html/template with the standard library router, or with one of the larger frameworks.

The difference in approach is where the work happens. With html/template, the template is parsed and executed at request time, and the template syntax is its own language with its own escaping rules and its own helpers. With Pushup, the page is compiled into Go source before the server starts, the Go code in the page is ordinary Go, and the resulting server has no template engine in the request path.

That buys a few things. There is no per request parsing cost. The escaping rules are decided once by the compiler rather than at runtime. And a developer who already writes Go is not learning a new expression language, because there is not one: the markup adds directives, not semantics.

Against a larger framework, the comparison is about the size of the thing you adopt. Pushup is a compiler plus a thin runtime on net/http, so the dependency list is short and the deployment artifact is a single binary. A full framework gives you middleware, templating, sessions, an ORM integration and a larger dependency tree, in exchange for the conveniences it has already solved.

The cost of the Pushup route is the one the README keeps repeating. It is preview software, it compiles rather than interprets, so a change to a page means a rebuild and a restart, and the project is three years past its last tagged release. That makes it an interesting experiment to learn from and a poor foundation to bet a service on.

Editorial conclusion

Pushup is worth a weekend of experimentation for a Go developer who wants server rendered pages without a template language and without a client side framework, because the compile step produces ordinary Go on net/http and the page to URL mapping removes route boilerplate. It is the wrong choice for production work today, because the project describes itself as a preview release with potential data loss, significant bugs and breaking changes to come, and the newest tagged release is from 2023. Verify first that your Go toolchain matches what go.mod requires, run the test suite before you trust the parser with anything you care about, and keep a plan to migrate to a maintained framework if the experiment goes further than a demo.

Frequently asked questions

What is Pushup?

Pushup is an experimental Go web framework where the core object is a page: a .up file mixing HTML, Go code and a lightweight markup language, compiled into pure Go and built into a standalone server on top of net/http.

How do I install Pushup?

Download a binary from the releases page, install it from Nix, clone the repository and run make, or run go install github.com/adhocteam/pushup@latest. The stated prerequisite is Go 1.18 or later, while the module file declares go 1.23.

Is Pushup ready for production use?

The README says no. It describes Pushup as an experiment and preview pre-release software that is largely functional but likely to have significant bugs including the potential for data loss, is suitable for demos and testing, and will see significant breaking changes.

How does Pushup do routing?

Routing is file-based. Pages participate in URL routing through their path in the filesystem, so a file such as app/pages/hello.up is mapped to the URL path /hello once the app is built and run.

Official sources

  1. adhocteam/pushup on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/adhocteam-pushup.svg)](https://hysenlabs.com/projects/adhocteam-pushup)

Was this useful?