go-u8views: the Go service behind a GitHub profile view counter
GitHub profile views tracker: displaying monthly view statistics
At a glance
- What is it?
- u8views counts views of GitHub profile pages and renders them as a badge, and this repository is the Go implementation of that service: Gin, PostgreSQL with goose migrations, sqlc for typed queries, and a Makefile-driven Docker Compose workflow. It is infrastructure for a small product, and the interesting parts are the storage model and the deployment scripts.
- Who is it for?
- Adopt go-u8views if you want to run a self hosted profile view counter and are comfortable owning a PostgreSQL instance, a migration chain and a Go build, because the repository ships all three and the Makefile drives the whole cycle. Do not adopt it if you only want a badge in your README, since the hosted service already exposes one over a plain image URL and the counter's accuracy depends on traffic arriving through the service.
- 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 141 days ago.
- What is it written in?
- Mainly HTML, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A badge image is the product's whole public surface
u8views.com is a GitHub profile views tracker, and the repository describes itself as a badge and a view counter. The public surface is a single image URL, and the README shows exactly what it looks like.
The badge address follows a readable pattern: a versioned API path, the provider, the profile id, and then the set of windows to render, in this case https://u8views.com/api/v1/github/profiles/121827373/views/day-week-month-total-count.svg
That single path tells you more about the design than any paragraph could. The counters are aggregated per period, the response is an SVG, and the endpoint is versioned with a v1 prefix. The SVG format is the point, because an SVG badge drops into a GitHub README with no JavaScript, no image proxy and no third party script running on the page.
The service behind it is this Go repository. The README also links to two write-ups about the project, a Reddit post about building a GitHub profile view counter and a Peerlist project page, which is a reasonable indication of the audience: developers who want the badge in their profile and are curious about the implementation.
The audience for the code itself is smaller. This is a self hostable backend for a specific hosted product, so the people who need it are those who want to run their own instance rather than point at u8views.com.
A Go service with Gin, PostgreSQL and sqlc
The dependency list in go.mod is short and tells you the shape of the system.
module github.com/u8views/go-u8views
go 1.26.1The web layer is Gin, with the gzip middleware in front of it, and the module targets Go 1.26.1. Storage is PostgreSQL through lib/pq, and the database access is not written by hand. The sqlc.yaml file at the top level, together with the `internal/` directory, points to a code generation approach where SQL is the source of truth and Go query code is produced from it.
Migrations are handled by goose, and the Makefile makes the migration vocabulary explicit with targets named migrate-pgsql-create, migrate-pgsql-up, migrate-pgsql-down, migrate-pgsql-redo, migrate-pgsql-reset and migrate-pgsql-status, all pointed at the ./internal/storage/migrations directory and a schema_migrations table.
Two rendering libraries are worth calling out. `github.com/narqo/go-badge` is in the direct dependency list, which is how the service produces the SVG badge without hand writing it. `github.com/valyala/quicktemplate` sits alongside `github.com/valyala/fasthttp`, and that pairing suggests the service renders HTML pages with a template engine on top of a fast HTTP server, rather than serving a JavaScript application.
The repository layout backs this up. There is a `cmd/` directory with a versioned entry point, the Makefile builds `./cmd/v3/main.go`, and there are `client/`, `console/`, `public/` and `scripts/` directories for the front end, an administration console and static assets. There is also a `database-schema/` directory with a versioned schema image, and a `version.sh` script next to a `version.txt` file.
The local development loop is four make targets
The README documents a development session as a short sequence of make targets, and it is a good illustration of how the pieces fit together.
cp .local.env .env
make env-up
make migrate-all-reset
make postgres-fixtures
make postgres-fixtures-countThe first step copies a checked in `.local.env` template to `.env`, which is where the compose file and the Makefile look for the credentials and the DSN. The repository does carry a `.local.env` at the top level, so the template is in the tree.
`make env-up` starts the environment. Looking at the Makefile, that target runs docker-compose with the compose file and the `.env` file, and the compose file defines two services: a `postgres` service on the postgres:14.2 image with the database user, password and name taken from the environment, and an `app` service on a golang:1.26.1-alpine image with the repository mounted into the container. The app service runs `sleep infinity` as its command, so the container is a development shell rather than a running server, and it publishes ports 80 and 443.
services:
postgres:
container_name: "go_u8views_postgres"
image: postgres:14.2The migration and fixture targets then prepare the database inside that app container, since the goose and Go toolchain are only present there. `make postgres-fixtures` loads the fixture data and `make postgres-fixtures-count` checks it, with a commented out `make postgres-fixtures-clear` available to reset. The end of a session is `make env-down`, or `make env-down-with-clear` if you also want the volumes removed.
After a code change the README says the same thing applies: `make env-up` is enough, because the volume mount means the code is already in the container.
Hourly rows and monthly aggregates
The storage model is visible in the SQL the README publishes, and it is the part of the design most worth understanding before you run your own instance.
The main table is `profile_hourly_views_stats`, which holds per-user counts bucketed by hour. The stats query aggregates that table by month and reports three different numbers side by side.
The three columns are the interesting bit. Views is `COUNT(*)`, which is the number of hourly rows that exist, so it counts periods with recorded activity rather than raw hits. Users is `COUNT(DISTINCT (user_id))`, the number of distinct profiles active in the month. Total is `SUM("count")`, the sum of the recorded counts. The number of distinct users is consistently a small fraction of the view count, which is the expected shape for a service where a profile is fetched repeatedly.
The published table of monthly results runs from January 2023 to February 2026, and the trend is a steady climb in both views and distinct users, from 15 views and 3 users in the first month to 35127 views and 890 users in February 2026.
One row in that table is hard to explain from the documentation. July 2025 shows a total of 2407710, while the months on either side show 78508 and 87879. The README does not say what happened that month, and the query text does not distinguish a different counting rule, so the figure stands as published without a stated cause.
A second query generates a daily series for the last month using GENERATE_SERIES, which is the shape you would use to fill gaps in a chart with zero rather than missing days.
What the benchmarks in the README actually measure
The README publishes benchmark output for a single benchmark function, BenchmarkProfileStatsService, at three iteration counts on two machines. That is unusual for a README, and it is useful if you read it carefully.
The first machine is an Intel Core i7-8750H at 2.20GHz. At 100 iterations the profile stats service reports 627990 ns/op with 5033 B/op and 80 allocs/op, at 1000 iterations 449478 ns/op with 4124 B/op and 72 allocs/op, and at 10000 iterations 546875 ns/op with 4885 B/op and 81 allocs/op. The benchmark lives in the internal/tests package.
The second machine is a Vultr VPS with 1024 MB of memory on a Skylake processor. The same benchmark reports 2275173 ns/op at 100 iterations, 2121516 ns/op at 1000 and 2153319 ns/op at 10000, with a much lower allocation count of 43 to 44 allocs/op and around 1570 B/op.
Two things follow from those numbers. The single core laptop is roughly four times faster per operation than the shared VPS, which is what you would expect from a compute-optimised local core against a shared virtual machine. The allocation profile differs more than the time does, with 72 to 81 allocs per op on the PC against 43 to 44 on the VPS.
The scope of the measurement matters as much as the numbers. This is one service function, the stats computation, and it is not a measurement of the badge endpoint or of the ingest path. The README does not publish any end to end request benchmark, so the number to keep in mind is the cost of the aggregate query, not of serving a page.
Deployment, configuration and the pieces you inherit
Running this outside your laptop means inheriting a set of moving parts, and the compose file and Makefile show all of them.
Configuration arrives through environment variables, both in the compose file and in the app service. The app service receives GIN_MODE, ENVIRONMENT, POSTGRES_DSN, GOOSE_DBSTRING, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, TLS_CERTIFICATES_DIR and HOSTS. The GitHub client credentials are the notable ones: they are there so the service can talk to GitHub as an OAuth application, and the certificate directory suggests TLS material is mounted in rather than baked in. The Makefile also keeps a default DSN in the form postgresql://u8user:u8pass@localhost:5432/u8views with sslmode disabled, which is a development convenience and should not survive into a real deployment.
The Makefile includes Makefile.ansible, and there is a separate Makefile.server, which indicates that the production deployment path is driven by Ansible rather than by the compose file. The compose file also carries a commented out port mapping for the database, and the postgres service persists to a bind mount under ./.docker/volumes/postgresql/data.
The Makefile's application targets show the intended workflow. `app-build` compiles ./cmd/v3/main.go to a binary named u8views-server inside the container, `app-start` runs that binary, `app-stop` kills it, and `app-restart` chains build, stop and start.
Two limits are worth stating plainly. The service needs PostgreSQL, not SQLite, because the aggregation queries lean on PostgreSQL specific functions such as DATE_TRUNC and GENERATE_SERIES, so there is no lightweight mode. And the OAuth credentials mean that a self hosted instance is registered with GitHub in the same way the hosted one is, which is a piece of setup the compose file cannot do for you.
Licence, upkeep and what the counter cannot tell you
The licence field on the repository says MIT, which is permissive and, for a service you run yourself, about as uncomplicated as it gets. Nothing here is legal advice, and the only note worth making is that the badge image and the hosted API are operated by someone else if you use u8views.com rather than running this code.
On upkeep, the repository is not archived and the last push was on 2026-06-07, more than four months before this writing, and there are no GitHub releases, so the versioning happens in the `version.txt` file and the `version.sh` script rather than in tags. The `cmd/v3` path in the build target suggests the command layer has been restructured before.
The honest limitation is about what a view count means. The service records that a profile page was requested. It cannot distinguish a person from a crawler, a preview, or a repeated reload, and the published data shows the shape of that problem, since distinct users per month remain a small fraction of the recorded view count. Any decision you make on the basis of the badge, such as treating it as an audience metric, is being made on a number that counts requests rather than people.
The second limitation is operational. This is a service with a database, a migration chain, a session and a certificate mount, and the repository is a small project rather than a supported product. If your goal is a badge in a README, the hosted endpoint already gives you one in a single line, and running this code only makes sense if you need your own instance or your own data.
Editorial conclusion
Adopt go-u8views if you want to run a self hosted profile view counter and are comfortable owning a PostgreSQL instance, a migration chain and a Go build, because the repository ships all three and the Makefile drives the whole cycle. Do not adopt it if you only want a badge in your README, since the hosted service already exposes one over a plain image URL and the counter's accuracy depends on traffic arriving through the service. Before deploying, read the migrations under internal/storage/migrations and the SQL in sqlc.yaml, and check the last push date of 2026-06-07 against how much of your own infrastructure you want to maintain.
Frequently asked questions
How do I get the u8views badge image for my GitHub profile?
The README shows the badge as an image URL on u8views.com, with a versioned API path for the provider and profile id and a set of periods to render, ending in an .svg file. Because it is an SVG image, it can be embedded in a README with no script running on the page.
How do I run go-u8views locally for development?
The README's development session starts with `cp .local.env .env`, then `make env-up`, `make migrate-all-reset` and the postgres fixture targets. The Makefile shows that env-up runs docker-compose against docker-compose.yml with the .env file, starting a postgres:14.2 service and a golang:1.26.1-alpine development container.
Which database does go-u8views use and how are migrations handled?
PostgreSQL, accessed through lib/pq, with migrations managed by goose. The Makefile provides migrate-pgsql-up, migrate-pgsql-down, migrate-pgsql-redo, migrate-pgsql-reset and migrate-pgsql-status targets that run against the ./internal/storage/migrations directory.
What do the monthly view numbers in the README represent?
The published stats query aggregates the profile_hourly_views_stats table by month and reports three numbers: views as the count of hourly rows, users as the count of distinct user_id values, and total as the sum of the recorded counts. The table runs from January 2023 to February 2026.
Is go-u8views still being worked on?
The repository is not archived and the last push was on 2026-06-07, and there are no GitHub releases, so version tracking lives in version.txt and version.sh rather than in tagged releases.
Official sources
Was this useful?