Open-source project
journey-ad/Moe-Counter avatar
journey-ad/Moe-Counter

Moe-Counter: a self-hostable visitor badge with 60-plus themes

Moe counter badge with multiple themes! - 多种风格可选的萌萌计数器

3,108 stars302 forksJavaScriptMIT

At a glance

What is it?
Moe-Counter is a JavaScript counter badge service that renders visitor numbers as anime-styled images. It runs on Express with SQLite or MongoDB, ships as a Docker image, and is aimed at people who want a cuter hit counter than a plain digit strip.
Who is it for?
Adopt Moe-Counter if you want a themed image counter you control, and you are comfortable running a Node 22 service with a persistent volume. Do not adopt it if you need a hosted, zero-ops badge, or if you need per-visitor analytics rather than a single incrementing number.
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 received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

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

Editorial analysis

The problem: a hit counter that does not look like a hit counter

Most visitor counters return a number, and the page owner styles it. Moe-Counter inverts that. The count itself is the image. You request a URL, the service increments a stored value, and it returns a rendered badge made of themed artwork, so the digits are drawn in a particular visual style rather than set in a font you choose.

The README lists the themes by name: 3d-num, ai-1, asoul, a long run of booru-* variants, capoo-1 and capoo-2, e621, food, gelbooru, green, four kasuterura variants, kyun, love-and-deepspace, miku, minecraft, moebooru, morden-num, two nixietube variants, normal-1 and normal-2, original-new, original-old, rule34, shimmie2, two sketch variants, and yousa-ling. That is the product. The counting logic is ordinary; the theme catalogue is the reason to pick this over a generic badge.

The audience follows from that. Personal blogs, fan sites, README files, and small project pages where the counter is decoration as much as measurement. It is not aimed at analytics teams, and the README does not present it as one.

How the request path and storage actually work

The repository is a single Node application. index.js is the entry point, package.json declares express as the web framework and pug as the template engine, and views/ and utils/ hold the rest. The Dockerfile sets WORKDIR /app, copies package.json, pnpm-lock.yaml and pnpm-workspace.yaml, runs pnpm install --frozen-lockfile, then copies the source and creates /app/data.

Storage is chosen by environment variable, not by code branch you edit. .env.example documents DB_TYPE as either 'sqlite' or 'mongodb'. SQLite is handled by better-sqlite3, MongoDB by mongoose. Both drivers are declared dependencies, so neither backend is optional at install time.

Writes are batched. DB_INTERVAL is described in .env.example as the database write interval in seconds, with 0 meaning real-time. That is the most consequential setting in the file. At the default of 60, increments accumulate in memory and flush once a minute, which keeps write pressure low on a busy badge but means a process restart can lose the counts that had not yet been flushed. Setting it to 0 trades that risk for a write on every request.

The badge URL itself carries the configuration. The README's own header image uses count.getloli.com/@Moe-counter.github with name, theme, padding, offset, align, scale, pixelated and darkmode parameters. The README points to the demo site for the full parameter reference rather than documenting them inline, so the query surface is only partly visible from the repository alone.

Installing Moe-Counter with Docker and rendering a first counter

The README labels Docker the recommended path and gives the image reference directly. Pulling it fetches the pre-built image from GitHub Container Registry.

bash
docker pull ghcr.io/journey-ad/moe-counter:latest

The run command from the README binds port 3000, mounts a local data directory, and sets the two environment variables that matter most. The volume is what makes the counter survive a container replacement, because /app/data is where the SQLite file lands.

bash
docker run -d -p 3000:3000 \
  -v $(pwd)/data:/app/data \
  -e APP_PORT=3000 \
  -e DB_TYPE=sqlite \
  ghcr.io/journey-ad/moe-counter:latest

If you prefer compose, the repository's docker-compose.yml builds from the local Dockerfile instead of pulling, maps 3000:3000, mounts ./data, and sets APP_PORT and DB_TYPE. Running docker compose up -d in the repository root starts the same service from source.

Once it is up, the badge is requested by path. The README's own header image uses this shape, with @Moe-counter.github as the counter key and theme selecting the artwork:

text
https://count.getloli.com/@Moe-counter.github?name=Moe-counter.github&theme=booru-lewd&padding=7&offset=0&align=top&scale=1&pixelated=1&darkmode=auto

What you should see is an image, not JSON. Each request to that URL increments the stored count and returns the rendered badge. The remaining environment variables come from .env.example: APP_SITE for the site URL, DB_URL for a MongoDB connection string, DB_INTERVAL for the flush interval, LOG_LEVEL for one of debug, info, warn, error or none, and GA_ID for a Google Analytics tag.

Where the batching and the theme catalogue become liabilities

The DB_INTERVAL default is the sharpest edge. A counter that flushes every 60 seconds is fine for a blog footer and wrong for anything where the number is the point. If the process is killed between flushes, the README does not describe a recovery mechanism, and the in-memory increments are simply gone. There is no documented rollback or reconciliation step.

Theme selection is also a governance question, not just an aesthetic one. Several theme names in the README's list reference adult-oriented imageboards, and the README itself does not attach content warnings or usage guidance to them. For a personal blog that is the owner's call. For anything with a public audience, a school project, or a corporate README, picking a theme means picking the imagery that will appear on every page load, and the demo site is where you should look before committing to one.

The service is also a single point of failure for something cosmetic. If the container is down, every page embedding the badge shows a broken image, because the badge is fetched as an image resource and there is no documented fallback. A plain text counter rendered client-side does not have that failure mode.

Finally, this counts requests, not people. There is no documented deduplication by IP, cookie or session. Reloading the page increments the number again.

Moe-Counter compared with a plain badge service

The obvious alternative is a hosted badge endpoint such as Shields.io, which returns an SVG for a given label and value. The difference is where the state lives. Shields.io renders a value you supply or that it fetches from a third-party API; it does not own a persistent counter for your site. Moe-Counter owns the number. You send a request, the server increments its own row, and the returned image reflects that stored value.

That ownership is the whole trade. You get a counter that no external service can reset or rate-limit away, and you get artwork that a generic badge renderer will not produce. In exchange you take on a Node 22 runtime, a persistent volume, and the write-batching behaviour described above. A static SVG badge has no server, no database and no flush interval, but it also cannot count anything on its own.

A second reference point is the demo instance at count.getloli.com, which the README says handles over 10 million requests every month and asks users to sponsor. Using it costs nothing and requires no deployment. Self-hosting makes sense when you want a private counter key, your own theme defaults, or a count that is not tied to someone else's server staying up.

Licence, maintenance and what an upgrade costs you

The LICENSE file is MIT, and package.json declares "license": "MIT". That permits commercial use, modification and redistribution provided the copyright notice and permission notice are kept. It does not grant rights to the theme artwork, which the repository does not separately address; the theme images are the part of this project most likely to carry third-party provenance, and the README's theme list gives no per-theme attribution. Treat the licence as covering the code and check the assets/ directory yourself before shipping the badges in a commercial product.

The repository is not archived, and the last push was on 2026-04-16. There are no retrieved releases, so upgrades follow the container tag rather than a versioned changelog. The README documents no migration procedure, and the two storage backends mean an upgrade that changes the schema touches SQLite files in data/ and MongoDB collections differently. Back up data/ before pulling a new latest image.

Runtime requirements are explicit: package.json sets engines to node >=22 and pnpm >=10, and pins packageManager to [email protected]. The Dockerfile matches with node:22-alpine and corepack prepare pnpm@10. Running from source on an older Node will not be supported by the declared engine range.

Editorial conclusion

Adopt Moe-Counter if you want a themed image counter you control, and you are comfortable running a Node 22 service with a persistent volume. Do not adopt it if you need a hosted, zero-ops badge, or if you need per-visitor analytics rather than a single incrementing number. Before deploying, check the DB_INTERVAL write behaviour against your traffic, confirm which of the two database backends you want in .env, and look at the data/ directory the Docker run mounts, because that is where SQLite state lives.

Frequently asked questions

How do I use Moe-Counter?

Request a URL of the form /@your-name with a theme query parameter, and the service returns a rendered badge image while incrementing the stored count. The README's own example uses count.getloli.com/@Moe-counter.github with name, theme, padding, offset, align, scale, pixelated and darkmode parameters, and points to the demo site for the full configuration reference.

Which database does Moe-Counter use?

The DB_TYPE environment variable selects either 'sqlite' or 'mongodb'. SQLite is handled by better-sqlite3 and MongoDB by mongoose, and both drivers are declared dependencies in package.json. If you use MongoDB, .env.example shows DB_URL as the connection string.

How do I install Moe-Counter with Docker?

The README recommends pulling ghcr.io/journey-ad/moe-counter:latest and running it with port 3000 mapped, a volume mounted at /app/data, and APP_PORT and DB_TYPE set. The repository also includes a docker-compose.yml that builds from the local Dockerfile with the same port mapping and volume.

What is the DB_INTERVAL setting in Moe-Counter?

.env.example describes DB_INTERVAL as the database write interval in seconds, where 0 means real-time. The default is 60, so counts are held in memory and flushed once a minute rather than written on every request.

What Node version does Moe-Counter require?

package.json declares engines of node >=22 and pnpm >=10, and pins packageManager to [email protected]. The Dockerfile uses node:22-alpine and activates pnpm@10 through corepack.

Official sources

  1. Issues
  2. journey-ad/Moe-Counter on GitHub
  3. License: MIT
  4. Project website
  5. README
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/journey-ad-moe-counter.svg)](https://hysenlabs.com/projects/journey-ad-moe-counter)