Definitelytyped: [@types/koa-router] incompatible type with koa

Created on 13 Jun 2019  ·  23Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

If you know how to fix the issue, make a pull request instead.

  • [x] I tried using the @types/koa-router package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @hellopao @schfkt @JounQin @romain-faust @Falinor

Source Code

import Router from 'koa-router'
import { Context } from 'koa'

const router = new Router()

router.get(
  '/auth',
  async (ctx: Context): Promise<void> => {
    ctx.body = 'aaa'
  }
)

Error Message

Argument of type '(ctx: Context) => Promise<void>' is not assignable to parameter of type 'Middleware<ParameterizedContext<any, IRouterParamContext<any, {}>>>'.
  Types of parameters 'ctx' and 'context' are incompatible.
    Property 'log' is missing in type 'BaseContext & { app: import("/Users/li.li/Work/poking/node_modules/@types/koa/index.d.ts")<any, {}>; request: Request; response: Response; req: IncomingMessage; res: ServerResponse; ... 4 more ...; respond?: boolean | undefined; } & IRouterParamContext<...>' but required in type 'Context'.ts(2345)

Most helpful comment

koa provides us with multiple types for ctx. At this moment we only need to care about Context and ParameterizedContext
Some libraries extends Context by typescript declaration merging. For example, koa-sessiondoes it

declare module "koa" {
    interface Context {
        session: session.Session | null;
        readonly sessionOptions: session.opts | undefined;
    }
}

Fortunately or unfortunately koa-router uses ParameterizedContext instead of Context for typing ctx object in middlewares (actually it's provided by koa, but it doesn't matter here). That is why the code bellow shows an error

import Router from 'koa-router';
import { Context } from 'koa';

const router = new Router();

router.post('/', (ctx: Context) => { // error: Argument of type '(ctx: Context) => void' is not assignable to parameter of type 'Middleware<ParameterizedContext<any, IRouterParamContext<any, {}>>>' because it is missing the following properties from type 'Context': session, sessionOptions
    ctx.body = 'Hello worls';
});

To fix this problem we just need to use capabilities of generic Router class

import Router from 'koa-router';
import { DefaultState, Context } from 'koa';

const router = new Router<DefaultState, Context>();

router.post('/', (ctx: Context) => {
    ctx.body = 'Hello world';
});

Some people recommend to use BaseContext (or DefaultContext in new versions). I recommend to avoid it, because it simply has [key: string]: any; that allows to put whatever you want. Typescript was actually developed for avoiding such things

You may not have such an issue if you use @types/[email protected]. But it's because the same [key: string]: any; which was moved to DefaultContext in new versions

All 23 comments

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "strict": true,
    "strictPropertyInitialization": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "sourceMap": true
  },
  "include": [
    "src"
  ]
}

I also get this error, however you can use BaseContext instead and things will work, I don't know yet if this is expected, but it's not clear which Interface should be used in both koa and koa router contexts.

The error says it's expecting ParameterizedContext, which works when I use it.

In the Koa definition file I found this line interface Context extends ParameterizedContext {}, so that's as close as you can get to using Context itself.

@ejose19, BaseContext is missing some stuff. Search for interface Context extends in the Koa definition file and read backwards from there and you'll see that Context extends more than just BaseContext.

(I'm completely new to TypeScript so I don't really know this stuff, just reading the errors and source code and trying to make sense of things.)

Yes, Parametrized context has way more stuff than BaseContext, however it's still missing some important things like login / logIn / logout / logOut (which they are in Context alone) but if you try to use ctx: Context inside a koa-router route it says it's incompatible. Hope Anyone fix this because we should be using the correct interface.

I digged a bit more and saw that libraries like koa-session / koa-generic-session / koa-passport (and probably many more) are creating / extending their own interface Context (here's an extract from @types/koa-passport)

declare module "koa" {
    interface Context {
        login(user: any, options?: any): Promise<void>;
        logIn: Context["login"];

        logout(): void;
        logOut: Context["logout"];

        isAuthenticated(): boolean;
        isUnauthenticated(): boolean;
    }
}

That's why ParametrizedContext type is missing all those libraries types, my knowledge in typescript is not enough to solve this issue, but I hope this information leads to the proper resolution.

For anyone still stuck on this issue, simply removing the type definition on the ctx parameter fixes the problem:

baseRouter.get( '/auth', async (ctx): Promise<void> => { ctx.body = 'aaa' } )

If I use require I don't have an issue using a new Koa() koa-websocket: const Koa = require("Koa")

I found another way to fix this issue--blow away node_modules and your lock file (if any) and re-install everything.

The problem is all the @types koa additives like koa-router, koa-compose and kao-websockets all include a dependency on "@types/koa": "*". NPM/Yarn downloads these at the current version and stores them local to the Koa-router types module.

When one updates the koa types to a new version, the old downloads for additive packages are not updated too. Deleting the node_modules and lock files causes npm or yarn to redownload everything to the same version of @types/koa.

Remove node_modules and package-lock.json then reinstall with npm install do NOT solve the problem.

The following code can be used to reproduce this issue with `koa-session':

```lang:javascript
// npm install koa koa-session koa-router
// npm install -D @types/koa @types/koa-session @types/koa-router
import Koa from 'koa';
import session from 'koa-session';
import Router from 'koa-router';

const app = new Koa();
const router = new Router();

app.keys = ['key1', 'key2'];

const CONFIG = {
key: 'koa:sess',
maxAge: 86400000
};

app.use(session(CONFIG, app));

router.get('/', (ctx) => {
ctx.body = 'welcome';
ctx.session.name = 'abc';
});

app.use(router.routes())
.use(router.allowedMethods());

app.listen(3000);

When run this code with `ts-node`, it will generate following error:

koarouterissue.ts(21,7): error TS2339: Property 'session' does not exist on type 'ParameterizedContext ```

Same with koa-passport.

Property 'login' does not exist on type 'ParameterizedContext<any, IRouterParamContext<any, {}>>'.

same issue here

Any news? I've the same problem.

the problem seems to be 2.0.52.

if forced to 2.0.50

  "resolutions": {
    "@types/koa": "2.0.50"
  },

and I also force to 2.0.50 in dependencies

"@types/koa": "2.0.50",

it works again!

same problem.and use "@types/koa": "2.0.50" can fix this problem, but cannot use koa.Next to type next() function.

Yes, we're having this same exact issue and have also force locked to 2.0.50 until this gets fixed.

How to solve?

@hengkx if you first remove it, then do:; yarn add @types/[email protected] it'll for time being lock it. My thought is when I see this has been fixed I'll add back the ^ in package.json.

@lili21
koa-router call the handler function with RouterContext type , but the handler function we defined need the Context type. Context type maybe defined some other properties which are not in RouterContext, so the type checking can not success.

we can resolve this with Intersection Types.

import { RouterContext } from "koa-router";
router.get(
  '/auth',
  async (ctx: Context & RouterContext): Promise<void> => {
    ctx.body = 'aaa'
  }
)

koa provides us with multiple types for ctx. At this moment we only need to care about Context and ParameterizedContext
Some libraries extends Context by typescript declaration merging. For example, koa-sessiondoes it

declare module "koa" {
    interface Context {
        session: session.Session | null;
        readonly sessionOptions: session.opts | undefined;
    }
}

Fortunately or unfortunately koa-router uses ParameterizedContext instead of Context for typing ctx object in middlewares (actually it's provided by koa, but it doesn't matter here). That is why the code bellow shows an error

import Router from 'koa-router';
import { Context } from 'koa';

const router = new Router();

router.post('/', (ctx: Context) => { // error: Argument of type '(ctx: Context) => void' is not assignable to parameter of type 'Middleware<ParameterizedContext<any, IRouterParamContext<any, {}>>>' because it is missing the following properties from type 'Context': session, sessionOptions
    ctx.body = 'Hello worls';
});

To fix this problem we just need to use capabilities of generic Router class

import Router from 'koa-router';
import { DefaultState, Context } from 'koa';

const router = new Router<DefaultState, Context>();

router.post('/', (ctx: Context) => {
    ctx.body = 'Hello world';
});

Some people recommend to use BaseContext (or DefaultContext in new versions). I recommend to avoid it, because it simply has [key: string]: any; that allows to put whatever you want. Typescript was actually developed for avoiding such things

You may not have such an issue if you use @types/[email protected]. But it's because the same [key: string]: any; which was moved to DefaultContext in new versions

@zfeed Your solution worked extremely well, now all library methods (session, passport) are accessible inside ctx router middleware, also just declaring the interface once per router instead of on each route handles this issue very elegantly. Cheers!

A note for users defining their route callback outside the route (like this)

function getUser(ctx: Context){...}

router.post('/user/:id', getUser)

In that case typescript won't know about the declaration merging and Context won't contain router specific properties (like ctx.params)

One solution is to use a custom defined "RouterContext" that applies the type mentioned by @zfeed :

export type RContext = ParameterizedContext<
  DefaultState,
  Context & Router.IRouterParamContext<DefaultState, Context>
>;

function getUser(ctx: RContext) {
  ctx.params; // <--- this will be found
}

And now both router properties and koa will be available as RContext

@ejose19 Thank you very much.

Thanks @zfeed

Was this page helpful?
0 / 5 - 0 ratings