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

  1. Inspect the value passed to app.use: add console.log(typeof fn, fn) right before the call to identify which registration is non-function.
  2. 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).
  3. Confirm you are using Koa-compatible middleware (koa-bodyparser, @koa/router, koa-cors), not Express middleware (body-parser, cors).
  4. For conditional registration, guard with if (mw) app.use(mw) or use a default no-op: app.use(mw || ((ctx, next) => next())).
  5. For router middleware, ensure @koa/router is imported and instantiated: const router = new Router(); app.use(router.routes()).
  6. 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

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.