Komga's backend ported line by line to TypeScript: same server, API, database and web UI, 3 to 6 ...
874
Komga, the media server for your comics, mangas, BDs, magazines and eBooks — with its backend ported line by line from Kotlin to TypeScript. Same server, same API, same database, same web interface, three to six times less memory.
Version française · The story of the port: KomgaJS on smea.tech (in French)
Komga is written in Kotlin on the JVM, and a JVM is generous with memory: an idle Komga with an empty library sits above half a gigabyte, and grows past a gigabyte once it has scanned and served a library. KomgaJS runs the same program on Node.js. It is not a rewrite and not a clone: every one of Komga's 442 backend files has a TypeScript twin of the same name, in the same place, with the same functions in the same order — so that when Komga moves on, its changes can be carried over by reading the diff.
The same library of 60 comic books (645 MB), the same scenario, each server starting from an
empty configuration with its default settings, both measured on the same machine under the
same load, with the current code (tools/mem-bench.mjs), library created as the web interface
does (ISBN barcode import off, unless stated). Resident memory of the process:
| Komga (JVM) | KomgaJS | ||
|---|---|---|---|
| Idle, after start-up | 599 MB | 165 MB | ÷ 3.6 |
| After scanning and analysing the library | 1,372 MB | 234 MB | ÷ 5.9 |
| After reading (thumbnails, pages) | 1,496 MB | 235 MB | ÷ 6.4 |
| Start-up | 11.3 s | 1.0 s | ÷ 11 |
| Scan and analysis of the 60 books | 13.3 s | 10.4 s | 1.3 × faster |
| Same, with ISBN barcode import enabled | 32.0 s | 18.8 s | 1.7 × faster |
Large libraries. The search index lives off the JavaScript heap, in compact typed arrays, with the same results as Lucene: on 7,000 books, about 300 MB idle after rebuilding the index, and the rebuild fits in a 256 MB heap up to at least 48,000 books.
On a Raspberry Pi 4 (arm64) with a real library of 6,594 books in 4 libraries, the same database and the same files, each server running alone for 15 minutes after start-up, measured the same evening (the NAS was serving files over NFS at the same time):
| Komga (JVM) | KomgaJS | ||
|---|---|---|---|
| Idle, 15 min after start-up | 483 MB | 166 MB | ÷ 2.9 |
| Peak (start-up, scan of the 4 libraries) | 492 MB | 256 MB | ÷ 1.9 |
| Start-up | 18.3 s | 4.5 s | ÷ 4 |
| API response time, median | 8 ms | 4 ms | |
| API response time, 99th percentile | 391 ms | 16 ms |
How it runs. A single JavaScript thread serves the web requests and runs the background
tasks; file reads, hashing, decompression and image coding run on Node's native thread pool,
and long loops yield every 10 ms. During the scan of 6,500 books the server answers in 11 ms
(median), 29 ms (99th percentile). Still on the main thread, and able to delay a request while
they last: SQLite queries, PDF rendering, RAR decompression, EPUB parsing, index updates.
Benchmarks: tools/mem-bench.mjs, tools/scan-latency-bench.mjs.
Settings (environment variables):
| Variable | Default | Effect |
|---|---|---|
KOMGAJS_MAX_HEAP_MB | ¼ of the container memory limit, at least 256 MB | JavaScript heap cap |
KOMGAJS_IMAGE_THREADS | 2 | native threads per image operation (1: lowest memory; 2: thumbnails ~20 % faster than 1; 0: all cores) |
UV_THREADPOOL_SIZE | 4 | concurrent native operations; keep it above Komga's task threads |
KOMGAJS_TASK_WORKER | false | true runs tasks in a separate thread (+60–100 MB while tasks run) |
Everything Komga does, because it is Komga's code:
cbl read listsThe web interfaces are Komga's own (komga-webui and next-ui), built from the very commit
this port follows.
# compose.yaml
services:
komga:
image: ghcr.io/smeagolworms4/komga-js:latest
container_name: komga
volumes:
- ./config:/config
- /path/to/your/books:/data:ro
ports:
- 25600:25600
restart: unless-stopped
It is used exactly like Komga's image: the
configuration and the database live in /config, the server listens on 25600, and the
same application.yml settings and KOMGA_* environment variables apply.
The same image is on Docker Hub as smeagolworms4/komga-js
(amd64, arm64, armv7). Versions are tagged vX.Y.Z.N: X.Y.Z is the Komga version ported, N the
revision of the port. Image tags: :1.27.1.1 (fixed), :1.27.1 (latest revision of the port of
Komga 1.27.1), :latest (latest release), and :main, republished on every push to main, to try
the latest changes.
Platforms: linux/amd64 and linux/arm64 (Node 24), linux/arm/v7 (32-bit Raspberry Pi,
Node 22: Node 24 has no armv7 build; the test suite also runs on Node 22 in CI).
Node.js 24, a C compiler, the ICU development files and zip (for the tests):
sudo apt install build-essential libicu-dev zip
npm ci
npm run build:native # SQLite ICU collations, JDK-identical JPEG codec, bcrypt off the JS thread
npm run build
bin/komgajs --server.port=25600 --komga.config-dir=$HOME/.komga
The web interface is optional from source: build Komga's komga-webui and next-ui and
install them with node tools/install-webui.mjs <komga-webui/dist> <next-ui/dist>. Without
it, the API, OPDS, Kobo and KOReader endpoints work as usual.
The database is the same, table for table and byte for byte: KomgaJS opens a Komga
/config as it is, and Komga opens it again afterwards. The only file that is not shared is
the search index, which is rebuilt automatically on the first start, as Komga does when its
index is missing.
Switch from one to the other, never run both on the same database at once: each would scan,
run tasks and keep its own index, and neither would see the other's changes. To compare them
side by side, give each its own copy of /config and the same books, read-only.
The whole backend is ported and every Kotlin test of Komga has been ported with it.
Known differences, all listed in PORTING.md: the search index uses its own
file format; thumbnails and PDF pages are encoded by different libraries, so their pixels
differ slightly (never their sizes or formats); validation errors come out
in a fixed order where Komga's order is random.
The rules are in PORTING.md. In short:
// @port-of <kotlin file>@<commit>. No refactoring: an upstream bug is
reproduced and marked // UPSTREAM-BUG:, and every unavoidable deviation is marked
// PORT:.src/port.tools/jshell-komga.sh runs Java code with Komga's
complete classpath; the values it produces are committed as fixtures, and the tests compare
the port against them.UPSTREAM_REF records the Komga commit ported;
node tools/upstream-diff.mjs <new version> lists every changed Kotlin file with its
TypeScript twin and the diff to carry over, and node tools/port-status.mjs fails until
every file and every test is up to date.npm test # the whole suite
npx vitest run test/domain # one area
npm run typecheck
node tools/port-status.mjs # port coverage, file by file and test by test
node tools/progress-page.mjs build/progress.html
src/ the ported backend, mirroring komga/src/main/kotlin/org/gotson/komga
port/ the Java/Kotlin libraries, reimplemented (no Kotlin twin)
flyway/ the Kotlin database migrations
test/ the ported tests, mirroring komga/src/test, plus the oracle comparisons
resources/ application.yml, database migrations, fonts (from Komga)
native/ SQLite ICU extension, libjpeg 6b + LittleCMS (JDK-identical JPEG)
tools/ port status, upstream diff, oracle, benchmark, web UI install
Every push runs the types, builds the native modules, checks that every Kotlin file and test has its twin, runs the whole suite, and compiles. Only if all of that passes is the image built and published. Nothing in the pipeline needs Java or a running Komga.
Komga is the work of Gauthier Roebroeck and its contributors: every feature, every design decision and every line of the web interface of this project is theirs. If you use KomgaJS, please support the original:
Questions about Komga itself belong on Komga's Discord and website; issues specific to this port belong here, and you can talk about the port on SmeagolWorms4's Discord.
The TypeScript port is by SmeagolWorms4, also the author of Media Center Sync. Komga, its ideas and its design belong to Gauthier Roebroeck and the Komga contributors: if you give, give to Komga first. The port can also be supported, as a bonus: GitHub Sponsors · Buy me a coffee · PayPal.
MIT, like Komga — both copyrights are kept in LICENSE. A few support files are
derived from other projects and keep their own licence: see
THIRD_PARTY_NOTICES.md.
Content type
Image
Digest
sha256:76221b79a…
Size
118.3 MB
Last updated
1 day ago
docker pull smeagolworms4/komga-js