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

  1. Inspect the value passed to app.use right before the call: console.log(typeof fn, fn) and confirm it prints 'function'.
  2. If using koa-router, invoke the generator: app.use(router.routes()) and app.use(router.allowedMethods()), with parentheses.
  3. 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.
  4. Add a typeof guard or assertion before use() so failures point at the offending module.
  5. 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

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)