koa-mount: putting a Koa app under a path prefix without it knowing
Mount other Koa applications or middleware to a given pathname
At a glance
- What is it?
- koa-mount mounts a Koa application or a piece of middleware at a path, and it strips that path from the request URL for the duration of the call, restoring it as the stack unwinds, so the mounted code behaves the same wherever it is placed. It is a single file package with one dependency, and its debug output is one of the more useful things in the Koa ecosystem for understanding how a middleware stack is traversed.
- Who is it for?
- koa-mount fits a Koa service that has to serve several independent applications or a set of shared middleware under different path prefixes, especially when those parts are written without any knowledge of where they will be mounted, and the design goal of a single file with one dependency is easy to audit.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The path is stripped on the way in and restored on the way out
koa-mount is a tiny module with one job: take a Koa application or a middleware function and run it under a path prefix. The mechanism is described in one sentence in the documentation, and that sentence is the whole design. The path passed to mount() is stripped from the URL temporarily until the stack unwinds.
The consequence is that a mounted application behaves as if it were the only application in the process. A blog mounted at /blog with a router that matches GET / and GET /posts will respond correctly to GET /blog/posts, because when the router runs, the URL it sees is /posts. The prefix exists only at the point where the mount decision was made.
The restore happens on the way out, which means the rewriting is not permanent state on the context that a later middleware has to clean up. It is a scoped change with a matching scope end, and the debug output makes the order visible: each mount logs an enter line with the path it saw and the path it replaced it with, and a matching leave line when the stack comes back. That symmetry is the mechanism working correctly, and reading that log is the fastest way to understand a stack you did not write.
The purpose, as the documentation puts it, is creating entire apps or middleware that function correctly regardless of which path segment or segments they should operate on.
Mounting two applications side by side
The documented example builds two minimal Koa applications and mounts each of them, and the requests table at the end of it is the clearest statement of what the module does:
const mount = require('koa-mount');
const Koa = require('koa');
const a = new Koa();
a.use(async function (ctx, next) {
await next();
ctx.body = 'Hello';
});
const b = new Koa();
b.use(async function (ctx, next) {
await next();
ctx.body = 'World';
});
const app = new Koa();
app.use(mount('/hello', a));
app.use(mount('/world', b));The application then listens on port 3000, and a request for / returns Not Found, because neither mount matches. A request for /hello returns Hello, and a request for /world returns World. Note that the mounted applications set the body after awaiting next(), so the order of the middleware inside them is ordinary Koa middleware order, and the mounting does not change it.
The installation is a single command:
$ npm install koa-mountThe repository also points at an examples directory with working examples, and the top level of the repository lists five of them, including cascade.js, errors.js, middleware.js, pathless.js and simple.js. The names are a fair guide to what the module does at its edges: a cascade example for nested mounts, an errors example for failure inside a mounted app, a pathless example for the optional path argument, and a middleware example for the other use.
Mounting middleware that never learns its prefix
The second use case is mounting plain middleware rather than a whole application, and the difference matters more than it looks. A mounted middleware function is not aware of the prefix at all, so it operates independently of it. That makes koa-mount a way to share one piece of behaviour across several paths, for instance an authentication or rate limiting function that runs under /api and /admin without either copy knowing where it sits.
The middleware form looks like this:
const mount = require('koa-mount');
const Koa = require('koa');
async function hello(ctx, next) {
await next();
ctx.body = 'Hello';
}
async function world(ctx, next) {
await next();
ctx.body = 'World';
}
const app = new Koa();
app.use(mount('/hello', hello));
app.use(mount('/world', world));
app.listen(3000);The path argument is optional and defaults to /, which lets you write a mount call with only the thing being mounted:
app.use(mount(a));
app.use(mount(b));The pathless case is worth thinking about, because it is where the module turns into a plain composition helper. Mounting at / still rewrites the path, it just rewrites it to itself, and the request flow is unchanged. In other words, if you find yourself using mount() without a prefix, you are using koa-compose, which is the single runtime dependency of this package, and you could call it directly.
NODE_DEBUG shows the enter and leave sequence of every mount
Debugging a middleware stack in Koa is harder than it should be, because the order in which middleware enter and leave is not visible from the code. koa-mount gives you that visibility through the standard NODE_DEBUG environment variable:
$ NODE_DEBUG=koa-mount node myapp.js &
$ GET /foo/bar/baz
koa-mount enter /foo/bar/baz -> /bar/baz +2s
koa-mount enter /bar/baz -> /baz +0ms
koa-mount enter /baz -> / +0ms
koa-mount leave /baz -> / +1ms
koa-mount leave /bar/baz -> /baz +0ms
koa-mount leave /foo/bar/baz -> /bar/baz +0msThree things are readable from that output. The prefix is removed in stages, one segment at a time, so a nested mount chain is visible as a sequence of enter lines with progressively shorter paths. The leave lines come back in reverse order, which is the stack unwinding. And each line carries a duration, so the +2s on the first line shows where the time was spent rather than only where the request ended up.
The values are written as from and to, so the enter line shows the path that arrived and the path that replaced it, and the leave line shows the path as it is when the stack comes back. That pair of readings is what lets you confirm that a path was correctly restored, which is the failure you would worry about if a mount were leaving a rewritten path behind for later middleware to trip over.
One published file, one dependency, Node 22 and a native test runner
The package is unusually small in a way that is easy to audit. The files array in the package metadata contains exactly one entry, index.js, so the whole runtime is a single file, and the only runtime dependency is koa-compose at version 4.1.0 or newer. The peer dependency is koa at 2 or newer, and the engine requirement is Node 22 or newer.
The development side is modern and mostly toolchain rather than runtime. The dev dependencies include koa 3, koa-static, supertest for HTTP level assertions, xo as the linter with an eslint-config-xo-lass configuration, husky for the git hooks configured by the .huskyrc file, c8 for coverage, and np for the release step. The test script is node --test test/*.test.js --test-force-exit, which means the tests run on the Node built-in test runner with no test framework dependency, and the pretest hook runs the linter first so a lint failure blocks the tests. The CI variant wraps the same run in c8 with an lcov reporter, and the release script is np.
The tree is small to match: index.js, package.json, the Readme, History.md for the changelog, the examples directory, the test directory and the .github directory. A module this size is easy to read end to end, which is a fair argument for choosing it over a heavier solution when the problem is only path prefixing.
MIT in the metadata, no licence file in the repository, and an old release tag
The licensing position needs a note. The package metadata declares MIT, and the README's own licence section says MIT, but the repository has no detected licence, and the top level listing contains no LICENSE file: .github, .gitignore, .huskyrc, .xo-config.json, History.md, Readme.md, examples, index.js, package.json and test. For a package that is published to a registry and installed by thousands of applications, that gap is worth closing before you vendor it, because the terms you would rely on are stated in a metadata field rather than in the file most people look at.
On releases, the tagged versions are v4.2.0 and v4.1.0, both published on 2025-04-27, with 4.1.0 and 4.2.0 arriving within an hour of each other. The last push to the repository was on 2026-09-22, so there has been work since those tags, and the default branch is master. A package pinned in a lockfile will keep working, but a new project starting today has to decide between the last published version and the current state of the branch.
The peer dependency range is worth a second look before you upgrade. The peer range is koa 2 or newer, which spans the Koa 2 and Koa 3 lines, and the development dependency is Koa 3. That combination suggests the module is expected to work with both, but the tests only run against the version in the dev dependencies, so an application on the older Koa line is relying on compatibility that the project's own test run does not cover.
Editorial conclusion
koa-mount fits a Koa service that has to serve several independent applications or a set of shared middleware under different path prefixes, especially when those parts are written without any knowledge of where they will be mounted, and the design goal of a single file with one dependency is easy to audit. It is the wrong tool for a service that only has one app, for a case where the mounted code needs to know its own prefix, and for a project that needs a maintained release, since the last tagged versions are 4.1.0 and 4.2.0 from 2025-04-27 even though the repository was pushed to on 2026-09-22. Before adopting it, read the pathless example and the cascade example in the examples directory, run it with NODE_DEBUG=koa-mount to see the enter and leave sequence for your own stack, and check the licence position, since the package metadata says MIT while the repository has no detected licence file.
Frequently asked questions
What does koa-mount do with the path it is given?
The path passed to mount() is stripped from the URL temporarily until the stack unwinds, and it is restored as the middleware stack comes back. A mounted application therefore sees the URL as if it were the only one in the process, and behaves the same regardless of the prefix it is mounted under.
How do I install koa-mount and mount an application?
Install it with npm install koa-mount, then call mount with a path and the application or middleware function, and pass the result to app.use. The path argument is optional and defaults to /, so mount(a) is valid on its own.
How can I debug the order in which koa-mount middleware runs?
Start the process with the NODE_DEBUG environment variable set to koa-mount, for example NODE_DEBUG=koa-mount node myapp.js. Each mount then logs an enter line with the incoming and rewritten paths and a matching leave line, and each line carries the elapsed time.
What does koa-mount depend on at runtime?
The package publishes a single file, index.js, and its only runtime dependency is koa-compose at 4.1.0 or newer, with koa 2 or newer required as a peer dependency. The engines field requires Node 22 or newer, and the tests run on the Node built-in test runner with node --test.
Official sources
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.
[](https://hysenlabs.com/projects/koajs-mount)Was this useful?