koajs/koa · error · TypeError
middleware must be a function!
Error message
middleware must be a function!
What it means
Thrown synchronously by app.use(fn) when the supplied value is not a function. Koa's middleware pipeline is composed entirely of functions (ctx, next) => Promise, so registering anything else (undefined, an object, a string) would break compose() at request time; instead Koa fails fast at registration. The check is intentionally primitive (typeof fn !== 'function') so it accepts both normal and async functions and rejects every other type. This is a programmer/usage error, not a runtime condition — it indicates a bug in wiring middleware.
Source
Thrown at lib/application.js:153
* @api public
*/
inspect () {
return this.toJSON()
}
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {(context: Context) => Promise<any | void>} fn
* @return {Application} self
* @api public
*/
use (fn) {
if (typeof fn !== 'function') { throw new TypeError('middleware must be a function!') }
debug('use %s', fn._name || fn.name || '-')
this.middleware.push(fn)
return this
}
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback () {
const fn = this.compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
View on GitHub (pinned to 52d5e8ff5a)
Solutions
- Inspect the value passed to app.use: add console.log(typeof fn, fn) right before the call to identify which registration is non-function.
- Verify each middleware import resolves to a function — check require('./x') is not undefined (missing/late module.exports) and that factories are invoked (e.g. bodyparser() not bodyparser).
- Confirm you are using Koa-compatible middleware (koa-bodyparser, @koa/router, koa-cors), not Express middleware (body-parser, cors).
- For conditional registration, guard with if (mw) app.use(mw) or use a default no-op: app.use(mw || ((ctx, next) => next())).
- For router middleware, ensure @koa/router is imported and instantiated: const router = new Router(); app.use(router.routes()).
- If importing from an ESM/CJS package that has a .default interop issue, read the actual export: const mw = mod.default || mod; app.use(mw).
Example fix
// before
const router = require('@koa/router')
app.use(router.routes()) // router is the class, not an instance -> not a function
// after
const Router = require('@koa/router')
const router = new Router()
router.get('/', ctx => { ctx.body = 'hi' })
app.use(router.routes()) // router.routes() now returns middleware Defensive patterns
Strategy: type-guard
Validate before calling
// Before registering middleware, validate the value is a function
function register (app, mw, label = 'middleware') {
if (typeof mw !== 'function') {
throw new TypeError(`Cannot register ${label}: expected function, got ${mw === null ? 'null' : typeof mw}`)
}
app.use(mw)
}
// Usage: register(app, router.routes(), 'router.routes()') Type guard
// Type guard for Koa middleware (sync or async)
function isMiddleware (fn) {
return typeof fn === 'function'
}
function isAsyncMiddleware (fn) {
return typeof fn === 'function' && fn.constructor?.name === 'AsyncFunction'
}
// Narrow before use
const mw = (mod && (mod.default || mod))
if (isMiddleware(mw)) app.use(mw)
else throw new Error(`import did not resolve to a middleware function`)
// TypeScript:
// const isMiddleware = (x: unknown): x is import('koa').Middleware =>
// typeof x === 'function' Try / catch
// Synchronous TypeError — wrap registration loops to report the offending middleware
for (const [name, mw] of Object.entries(middlewares)) {
try {
app.use(mw)
} catch (e) {
if (e instanceof TypeError && /middleware must be a function/.test(e.message)) {
throw new Error(`Middleware "${name}" is not a function (got ${typeof mw}). Check its import/export.`)
}
throw e
}
} Prevention
- Always invoke middleware factories: bodyparser(), cors(), router.routes() — a bare reference is rarely the middleware.
- Lint for app.use() calls with non-call arguments; add a unit test that asserts every entry in the middleware list is a function before app.listen().
- After every refactor of imports/re-exports, run the app once in a test harness — the throw is synchronous at registration, so a smoke test catches it instantly.
- Keep a whitelist: only use middleware from packages whose name starts with koa- or @koa/; Express middleware (body-parser, morgan) will not satisfy the function contract.
- Avoid conditional middleware with bare && — prefer explicit if blocks so an absent middleware is a conscious decision, not an accidental undefined.
When it happens
Trigger: Calling app.use(undefined), app.use(null), app.use({}), or app.use(someObject) where a middleware factory returned undefined instead of a function. Common forms: app.use(router.routes()) when @koa/router is not imported or router is undefined; app.use(require('./middleware')) when the module forgot module.exports; app.use(bodyParser()) when body-parser is for Express (returns an object) instead of koa-bodyparser; spreading an array app.use(...middlewares) with an empty slot; conditional registration app.use(cond && mw) where cond is false yielding undefined.
Common situations: Mixing Express middleware (which returns objects/handlers) into a Koa app; forgetting to actually invoke a middleware factory (app.use(cors) vs app.use(cors())); typo'd or missing import so the symbol is undefined; dead-code/conditional registration that yields a falsy value; upgrading a middleware package whose API changed from returning a function to requiring invocation; circular import evaluating the middleware file before its export is assigned (value still undefined at use() time).
Related errors
AI-assisted analysis of koajs/koa@52d5e8ff5a (2026-08-03).
Data as JSON: /data/errors/09da3c790b0e2fad.json.
Report an issue: GitHub.