# Kikoeru: a self-hosted server for a personal library of voice works

> Kikoeru is an Express and SQLite media server for people who own a large personal collection of voice audio works: it scans the folders, pulls metadata from the DLSite catalogue, tracks listening progress, stars and comments, and serves the collection to a web front end and an iOS client. Its own TODO list is the most honest document in the repository, including a note that the architecture is still effectively single user.

**Number178/kikoeru-express** — kikoeru backend support RJ/BJ/VJ/Custom 6 or 8 digits code

- Repository: https://github.com/Number178/kikoeru-express
- Website: https://hub.docker.com/r/number17/kikoeru
- Stars: 542 · Forks: 36
- Language: JavaScript
- License: GPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/number178-kikoeru-express

## A library server for files you already own

Kikoeru is described in the repository description as a self-hosted media server for listening to your own DLsite voice works, and the README calls it a music streaming server dedicated to doujin voice works, with the detailed usage instructions in a separate user document in the project wiki.

The framing that matters is the third clause of the disclaimer. The project states that as open source software it contains no copyrighted content or anything else that violates the law, and that the program is designed for personal users to manage their own lawfully held data. It also states plainly that the author cannot prevent content providers or other users from using the program with infringing or illegal material, that the author has no connection with those content providers, does not provide technical support for them, and does not accept legal responsibility for improper use.

So this is a server for a collection you own, not a distribution channel. The interesting engineering question is what it does once the files are on your disk.

The feature list answers it. The server crawls the voice work metadata from the DLSite catalogue, which means it recognises what each file is and pulls the title, the circle, the tags and the product code from the catalogue rather than from the filename. It tracks progress through each work, lets you star it and write a comment on it. It gives you quick retrieval by tag or by keyword, and it can sort the search results by the metadata. Authentication is a choice rather than a given: you can validate users with JWT or turn user authentication off entirely. The web front end can edit the configuration file and trigger a scan of the library, and the library can have several root folders.

The audience is a person with a large, organised collection and a device in the living room, and the iOS client on TestFlight is the other half of that story.

Most of the backend code, the README says, comes from the open source project kikoeru by nortonandrews, so this is a fork that rewrote the front end and the packaging.

## Running it from source, and the PWA question

The source install path is short, with one prerequisite that is easy to miss. You take the SPA or PWA build produced by the kikoeru-quasar project, put all of its files into the dist folder, make sure dist/index.html exists, and then install and start:

```bash
npm install

npm start
```

The server is an Express application, and the README notes that it listens on port 8888, with the log line it prints being Express listening on the IPv6 any address. The npm scripts confirm the shape: start runs node with trace warnings against app.js, dev runs the same through nodemon, and scan runs the filesystem scanner directly, which is how you trigger a library scan from the command line instead of the web interface.

The PWA versus SPA decision is the interesting part of that section, and the README explains it better than most projects explain their front end. The only difference is the presence of a Service Worker, and because a Service Worker can only run locally and over HTTPS, opening the server remotely over plain HTTP makes the two identical. So if the main use is playing over the local network on a mobile device and you have not configured an HTTPS certificate, you are effectively running the SPA either way.

The PWA's advantage is that basic pages load with almost no delay and the site can be installed to the home screen through the browser as if it were an app. The author uses that version. The cost is that when a new version is deployed, you need at least one extra page refresh before the new build takes over, which is the standard service worker update behaviour and a small price for the offline capability.

There is also a packaged Windows executable, built with pkg, and a Docker image. The README says that with docker-compose you only need to adjust the mount paths inside docker-compose.yml to match your storage locations.

## The compose file, and an image tag that has fallen behind

The docker-compose.yml in the repository is the shortest description of the deployment there is:

```yaml
services:
    kikoeru:
        ports:
            - '8888:8888'
        volumes:
            # Change the next line to your location of storage
            # e.g. /storage/DLsite/xxx:/usr/src/kikoeru/VoiceWork
            - ./VoiceWork:/usr/src/kikoeru/VoiceWork
            - ./sqlite:/usr/src/kikoeru/sqlite
            - ./covers:/usr/src/kikoeru/covers
            - ./config:/usr/src/kikoeru/config
        image: 'muveex/kikoeru:v0.6.2'
        restart: always
```

Four host directories are mounted: the audio library itself, the SQLite database, the cover images and the configuration. The database and the configuration being on volumes is what makes the container disposable, which is the point of packaging it this way.

The image tag is the problem. The compose file pins muveex/kikoeru:v0.6.2, while the release history in the repository metadata shows v0.6.18-20261017 from 2026-10-18, v0.6.17-20261007 and v0.6.16-20260919. So the compose file in the tree is running a release from a long way behind the current one, and anyone who copies it verbatim will get a server that is missing a long list of fixes, including the OpenList remote media library support that arrived in v0.6.16 and the sorting improvements in v0.6.17.

The same gap shows up in the package.json, where the version field is 0.6.7, and in the Dockerfile, which is built FROM node:14-alpine in both stages. Node 14 has been end of life for years, and the package.json itself asks for node >=12.0.0, so the runtime floor in the image and the floor in the manifest are both years behind a current Node release.

None of that is a reason to avoid the project, since the code is modern enough in the parts that matter, but it is a reason to build your own image rather than trust the pinned tag.

The Dockerfile itself is otherwise well made for a single container app. It uses tini as the init process, installs ffmpeg alongside it, declares the same three data volumes, exposes 8888 and starts node app.js.

## The stack, and what each dependency is actually for

The README lists the technology stack with a one line justification for each entry, and reading it is a decent summary of how the server works.

Express builds the backend service, and the package.json shows the middleware in use: body-parser, compression, connect-history-api-fallback for the single page app routing, express-jwt for the token validation and express-validator for the request validation. jsonwebtoken is the user authentication layer, and dotenv handles the environment configuration. The axios dependency is for outbound HTTP, which is the scraper's transport. cheerio and htmlparser2 parse the DLSite catalogue pages into a queryable form. sqlite3 is the file based database, knexjs is the query builder and knex-migrate the migration runner, with the migrations living in the database directory.

Two of those deserve a note. First, jschardet is there to detect text file encodings, which matters for a library whose filenames and folder names come from a Japanese catalogue and whose folder structure is on a filesystem that may not use UTF-8 throughout. Second, socket.io is used to push the results of a library scan to clients in real time, so a scan of a few hundred gigabytes shows progress in the browser instead of leaving the page spinning.

The lrc-file-parser dependency handles LRC lyric files, and the directory listing in the README shows a lyrics directory among the others, alongside routes, config, covers, database, dist, filesystem, scraper, sqlite and static. The scanner and updater live in the filesystem directory, the crawler in the scraper directory.

There is also a child_process dependency, which pairs with the pkg packaging, and pkg itself is how the Windows executable is produced. The pkg configuration in package.json lists the scripts and assets that have to be embedded, including the scanner, the updater, the database migrations and the front end build.

The overall architecture is a conventional Node service: a scanner that walks the filesystem and talks to the catalogue, a database layer with migrations, a REST API with JWT middleware, a socket channel for live progress, and a static front end.

## The TODO list is the honest documentation

The README carries a long checklist, and it is more informative than most projects' documentation because it shows what is finished and what is not, side by side.

Much of it is done. The draggable lyrics control, the back button on secondary pages, manual starring, the front end and back end CRUD for stars, the progress marking page, user reviews, the fix for placeholders in the spaghetti code, the SQLite upgrade, the metadata refresh, keeping works instead of clearing them, the fix for a blocking scan, the foreign key check, the migration scripts, the rewrite of the database creation logic, the first scan bug and the scan settings are all ticked.

The unticked items describe the current limits precisely, and three of them should decide whether the project fits your use.

The first is that unit tests and CI are still open. The package.json has a test script that runs ESLint and then mocha over the test directory, so the scaffolding exists, but the checklist says the work is not finished, and the tree at the root does have a test directory and a .github directory. A project without a finished test suite is a project where regressions are found by users.

The second is the multi user question. One item says to refactor the config and schema to add multi user support, and notes in parentheses that it is currently still effectively a single user architecture, and another says to refactor the authentication logic, the cookie, CSRF, and not to pass the md5 salt to the administrator. Those are specific and they are the reason the JWT option in the feature list should be read carefully: the authentication model is a work in progress, and the security notes in that item are not cosmetic.

The third is the feature backlog: playlists, where the note says there is currently only one, editable tags, a rescan, dark mode, importing and exporting reviews and progress, adding works by hand, scheduled jobs to refresh dynamic metadata, using an ID to identify folders, and using vuex to refactor the favourites. Plus refactoring WorkCard and WorkDetail, and an Intersection Observer.

So the project is a working, actively developed personal library server that is honest about where it stands, which is more than most projects of its age manage.

## Licence, cadence and how the releases are named

The licence is the GNU General Public License v3.0, stated in the README's licence section, and the package.json records it as GPL-3.0-or-later with the LICENSE file at the root of the repository. For a self-hosted tool you run for yourself that has no effect, and it is the expected licence for a project of this kind.

The release naming is unusual and worth understanding because it is a date, not a version. The recent tags are v0.6.18-20261017, whose title notes a fix to a custom work search bug in the iOS client, v0.6.17-20261007, titled support for sorting by total duration along with other improvements, and v0.6.16-20260919, titled OpenList remote media library support. The pattern is a semantic version followed by the date, and the default branch is alpha, which tells you where the published artifacts sit relative to the development line.

The cadence is fast. Three releases in a month, and the last push to the repository was on 2026-10-18, the day after the newest tag. There is a Changelog.md at the root and a publish_to_docker.sh script, which is how the image gets built and pushed, and that script is the place to look if you want to build a current image rather than the v0.6.2 one the compose file pins.

The project also has a build.sh and a run_dev.sh, an upgrade.js, an aboutMigrate.md, an .eslintrc.json and .eslintignore, and a user document in Chinese at the root of the repository.

So the adoption picture is a fast moving project with an active maintainer, a small published release surface and a compose file that has not kept up. If you deploy it, build from the current branch rather than pulling the pinned image, and expect to re-read the changelog when you upgrade.

## Conclusion

Adopt Kikoeru if you own a large personal collection of voice works in folders on disk and want browser and iOS access to it with progress tracking, stars, comments and metadata scraped from the DLSite catalogue, and you are happy running a single user server. Do not adopt it as a shared or multi tenant service, because the README's own TODO says the architecture is still effectively single user and lists a refactor to add multi user support, and do not expect a polished test suite, because unit tests and CI are still unchecked items. Verify first that the container image tag you use matches the release you want, since the bundled docker-compose.yml still points at v0.6.2 while the current release is in the v0.6.18 range, whether you serve it over HTTPS, because a service worker only runs on localhost or over TLS and a PWA opened over plain HTTP behaves exactly like the SPA, and that the Node runtime in the image, node 14, is old enough to be worth replacing with a current build.

## FAQ

### What is Kikoeru?

Kikoeru is a self-hosted media server for listening to your own DLsite voice works, an Express and SQLite application that scans the library folders, pulls metadata from the DLSite catalogue, tracks listening progress, stars and comments, and serves the collection to a web front end and an iOS client on TestFlight.

### How do I run Kikoeru from source?

Put the SPA or PWA build from the kikoeru-quasar project into the dist folder, make sure dist/index.html exists, then run npm install and npm start. The Express server listens on port 8888, and npm run scan triggers a library scan directly.

### What is the difference between the PWA and the SPA version of Kikoeru?

Only the Service Worker, and because a Service Worker can only run locally and over HTTPS, opening the server remotely over plain HTTP makes the two behave identically. The PWA loads basic pages with almost no delay and can be installed to the home screen, at the cost of needing one extra refresh when a new version is deployed.

### Does Kikoeru support multiple users?

Not yet. The README's TODO list includes refactoring the config and schema to add multi user support and notes that it is currently still effectively a single user architecture, alongside a separate item to refactor the authentication logic, cookies, CSRF and the handling of the md5 salt.

### Which Docker image should I use for Kikoeru?

The compose file in the repository pins muveex/kikoeru:v0.6.2 while the current release is in the v0.6.18 range, so the bundled compose file is behind. The image is built FROM node:14-alpine, and the compose file mounts the audio library, the SQLite database, the covers and the config as volumes on port 8888.

## Sources

- [License: GPL-3.0](https://github.com/Number178/kikoeru-express/blob/alpha/LICENSE)
- [Number178/kikoeru-express on GitHub](https://github.com/Number178/kikoeru-express)
- [Project website](https://hub.docker.com/r/number17/kikoeru)
- [README](https://github.com/Number178/kikoeru-express/blob/alpha/README.md)
- [Releases](https://github.com/Number178/kikoeru-express/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/number178-kikoeru-express
