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 middleware argument is not callable. Koa's middleware pipeline (koa-compose) dispatches each registered item as a function, so a non-function value would break invocation at request time; Koa fails fast at registration instead. The guard is a simple typeof check that surfaces a clear TypeError rather than a cryptic 'fn is not a function' later. It is a hard precondition of the public use() API.
Solutions
- Inspect the value passed to app.use right before the call: console.log(typeof fn, fn) and confirm it prints 'function'.
- If using koa-router, invoke the generator: app.use(router.routes()) and app.use(router.allowedMethods()), with parentheses.
- Verify the import resolves to the function itself, not a namespace: in ESM interop use the correct default/named binding (import fn from './mw' vs import { fn } from './mw'); log the imported binding to confirm.
- Add a typeof guard or assertion before use() so failures point at the offending module.
- If passing a conditional middleware, default to a no-op function (async (ctx, next) => next()) instead of null/undefined.
Example fix
// before
const router = require('./router')
app.use(router.routes) // routes is a method, not the composed middleware
// after
const router = require('./router')
app.use(router.routes()) // invoke to get the middleware function
app.use(router.allowedMethods()) Defensive patterns
Strategy: validation
Validate before calling
// run before app.use(fn)
function registerMiddleware(app, fn, label = 'middleware') {
if (typeof fn !== 'function') {
throw new TypeError(
`${label} must be a function, got ${fn === null ? 'null' : typeof fn} (${JSON.stringify(fn)})`
)
}
app.use(fn)
}
// usage
registerMiddleware(app, router.routes(), 'router')
registerMiddleware(app, require('./mw'), './mw default export') Type guard
// narrows a value to a function suitable for app.use()
function isMiddleware(fn) {
return typeof fn === 'function'
}
if (isMiddleware(mw)) {
app.use(mw)
} else {
throw new TypeError(`Expected middleware function, received ${typeof mw}`)
} Prevention
- Always invoke router middleware factories: router.routes() and router.allowedMethods(), not the bare methods.
- Log the type of every imported middleware binding once at startup to catch undefined/namespace-object imports early.
- Default conditional middleware to a pass-through (async (ctx, next) => next()) rather than null.
- When adding ESM imports of CJS modules, verify whether the function lives at the default binding or a named one before passing it to use().
- Add a tiny registerMiddleware(app, fn, label) wrapper with a typeof guard so registration failures identify the offending module by name.
When it happens
Trigger: Calling app.use() with a value that is not a function: app.use(null), app.use(undefined), app.use({}), or app.use('router'). Most commonly app.use(router.routes) where routes is a method that was referenced but never invoked (should be router.routes()), or app.use(middleware) where the named export does not exist and resolved to undefined.
Common situations: Forgetting the parentheses on koa-router: app.use(router.routes) instead of app.use(router.routes()). ESM/CommonJS interop where import X from 'pkg' yields { default: fn } and the caller passes the namespace object. Refactoring a middleware module to export an object { auth, logger } and forgetting to update the call site. Conditional requires (const mw = cond ? require('./mw') : null) leaking null into use(). Typos in named imports (import { logger } from './mw' when the export is named log).
Related errors
AI-assisted analysis of koajs/koa@571938d1b4 (2026-08-04).
Data as JSON: /api/errors/09da3c790b0e2fad.
Report an issue: GitHub.
Appendix: source
Thrown at lib/application.js:168
* @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 571938d1b4)