koa 3.0 roadmap

Created on 24 Dec 2017  Â·  61Comments  Â·  Source: koajs/koa

There are lots of PRs are blocked due to breaking change, and maybe it's time to make a plan for koa 3.0 now, if anyone has other suggestion, please post in this issue. :)

Most helpful comment

totally rewrite using TypeScript

All 61 comments

Is it to early to create a v3 branch? I have some spare time coming up and wouldn’t mind doing some work.

All these pending PRs are not so emergency to me, so if anyone has any thoughts or some feature / refactor required, post in the issue to help us decide when to start create v3 branch. :)

How about officially dropping support for < node.js 8?

koa-compose could rewrite using async/await

totally rewrite using TypeScript

I'm personally against introducing TypeScript, adding external typings should be easy enough for those in need.

How about officially dropping support for < node.js 8?

koa-compose could rewrite using async/await

@fl0w @iyuq according to community practice, engines support should always follow the Node.js release schedule :)

totally rewrite using TypeScript

like what @fl0w said above, just one more thing:
koa is quite vary from egg、express and any other framework. It's just a pure http request middleware close to Node.js as you can see. So we can just feel free to write in pure JavaScript, follow the traditional convention and trust Node.js :)

if you have any breaking changes you'd like to introduce, please state them now :)

Let's announce EOL for Koa v1.

How about support HTTP/2 ? https://nodejs.org/api/http2.html

@zhangxiang958 koa already support it since 2.3.0, u can simply setup a koa app over HTTP/2 like this:

const fs = require('fs');
const http2 = require('http2');
const Koa = require('koa');

const app = new Koa();

app.use(ctx => {
  ctx.body = 'Hello Koa'; // or any other stream
});

const options = {
  key: fs.readFileSync('xxx.key'),
  cert: fs.readFileSync('xxx.crt'),
};
const server = http2.createSecureServer(options, app.callback());

server.listen(443);

How about publish koa and official middleware module under @koajs/ ?

Adjust naming to follow WHATWG URL Standard.
Eg: request.path should be request.pathname etc

I think #1044 has already been solved by this commit

I think context.res and context.req are not good names and they make people confused with context.response and context.request. Is it better if we can name them behind NodeJS names, so context.res becomes context.serverResponse and context.req becomes context.incomingRequest?

@tiendq I completely agree! I keep mixing up req/request and res/response. I vote for something like

{
req:<koa request>,
res:<koa response>:,
raw:{
   req:<node request>,
   res:<node response>
}}

How to access the http2 stream on ctx object to push assets to client using koa? It this supported right now?

I second @damianobarbati , would be great to get an implementation snippet that displays the compatibility and its usage. For example, would the stream be accessible through ctx.res.stream?

I've been seeing a bit of chatter on HTTP/2 support for Koa - I agree that we don't need to define config just to "enable" out of the box. What we do need however, is an interface to utilize HTTP/2, let's say if the request type is HTTP/2, maybe context could be formed at this stage.

I believe that the moment the Koa community takes a stance on HTTP/2, this would promote its usage for the next generation of servers built using Koa. To me, this is a great step forward.

What I envision, as a mode of compatibility and usage, is to add ctx.stream if the request is in an HTTP/2 context. As a start, we could yield the benefits of pushing from the stream and get a grip on how to manage push streams without ending the connection prematurely.

How about publish koa and official middleware module under @koajs/ ?

Why not simply @koa/?

Here is my little suggestions

  • #1044 because more and more people are using next/nuxt applications (or maybe not by I am
)
  • and as said by @Tajnymag a @koa namespace can be useful:

    • @koa/cors is already namespaced

    • it will be clearer what are the official middleware

    • for new adopter, it makes more easy what to safely start with.

      IMO “official” guaranties that it plays nicely with the framework and will be maintained over time

  • optional some typescript types if needed as this language is getting more and more traction

[EDIT]

In fact everything's working perfectly.
The application doesn't crash and we have a warn.
Up to us to fix that by sending the headers before next/nuxt rendering.

Note, I am not privileged to make this decision. This is my personal opinion.

I'd like to chime in that I agree that namespace:ing npm-releases under npm:@koa is a good idea.

One or two caveats thought.

  1. Only for packages that reside in github:@koajs
  2. Don't namespace Koa core, i.e. npm i koa still resolves as expected.

Bug reports for 3rd party packages often end up in @koajs/koa#issues, which is burdensome for both maintainers and the user. This might clear things up a bit.

I also haven't considered possible implications of moving the packages (e.g. does npm have tooling for moving origins?).

+1 on #1044

Please do normal error handling. The ability to set content type

+1 on #998

I suggest #1326

Is there any timeline or schedule for 3.0?

Need some help?

I think removing next parameter from middleware and just awaiting all middleware can be most useful breaking change in 3.0.
Debugging something like bellow is a nightmare:

app.use(async (ctx, next) => {
 // ... do something
 await next();
// ... do some more staff
})
app.use(async (ctx, next) => {
 // ... do something
 await next();
// ... do some more staff
})

while removing next will make composition simple and koa-compose will not be needed:

// koa-compose will not be needed, just do this:
  async handleRequest(ctx) {
    const res = ctx.res;
    res.statusCode = 404;
   try {
     for (const handler of this.middleware) {
        await handler(ctx);
     }
    respond(ctx);
   } catch (err) {
     ctx.onerror(err);
  } finally {
    // any finalisation logic here
  }
}

if something need to be guaranteed last handler then it can use events or better composition and .state

@tinovyatkin What if you need to do something after the next() call?
For example, I'm using this scenario in multipart middleware:

import {parse} from "then-busboy"
import {unlink} from "promise-fs"

const unlinkFile = ({path}) => unlink(path)

const methods = ["post", "put"]

async function multipart(ctx, next) {
  if (!methods.includes(ctx.method.toLowerCase())) {
    return next()
  }

  if (!ctx.is("multipart/form-data")) {
    return next()
  }

  const body = await parse(ctx.req)

  ctx.request.body = body

  await next()

  // Cleanup files
  return Promise.all(Array.from(body.files.values()).map(unlinkFile))
    .catch(err => err.code !== "ENOENT" && Promise.reject(err))
}

export default multipart

How can I do so without next() as simple as this?

@octet-stream you mean if you ship your middleware as a module? With proposed #1395 you may do it easily:

app.use(async ctx => {
  if (methods.includes(ctx.method.toLowerCase() && ctx.is("multipart/form-data")) {
    ctx.request.body = await parse(ctx.req)

   ctx.app.once('responded', async ({ request }) => {
     // Cleanup files
   if(request?.body?.files) {
    try {
      await Promise.all(Array.from(request.body.files.values()).map(unlinkFile))
    } 
   catch (err) {
       if(err.code !== "ENOENT" ) 
      // you probably don't want to throw neither here, nor in your version
     // as files written by your middleware to the disk have nothing to do
    // with request handling
            throw err;
   }
  finally {
   delete requests.body.files;
  }
   }
 })
}
})

In your current implementation you actually deleting files from disk _before_ response is sent to the network and response stream is destroyed, so, if these files have opened read stream you can get in trouble:

app.use(octetStreamMiddleware());
app.use(async (ctx, next) => {
  if (ctx.request.body.files && 'greatFile' in ctx.request.body.files)
    ctx.body = createReadStream(ctx.request.body.files.greatFile);
 next();
})

How would you clean up _that_ @octet-stream?

@tinovyatkin I don't see it.

We'd have to (again) ostracise the entire ecosystem. You're essentially removing an abstraction that is well understood for no real benefit. Personally I haven't had issues debugging current middleware signature and usage, and there's also no evidence that this is a general issue either (just from experience by idling in Koa issues).

It feels like a change for the sake of change? But maybe I'm missing something?

@fl0w see my response and example for @octet-stream - developers tends to think that after awaiting next it's safe to cleanup resource while, in fact, the response is not even sent to the network at that point. Removing next will force to think the whole serving lifecycle right.

While I've started with Koa about 4 years ago one of the hardest things to understand was _what happens if I don't call or await for next?_ and I still doesn't sure in it :-)

In your current implementation you actually deleting files from disk before response is sent to the network and response stream is destroyed, so, if these files have opened read stream you can get in trouble

Sure, in actual code I would decide to cleanup files out of a request processing, because of something here will crash – the request will be also crash. It was just an example of a case where you need to do something after the next middleware is called, or maybe before respond. And the question was how to do so if your proposal will be added? Guess I need to clarify my thoughts better :)

@fl0w what will happen if in above example by @octet-stream he will forget to await the next and just call next()? It will very hard to find race condition.

The benefit here is clear message to developers - _middleware composition are sequential and you can't jump in the middle of another middleware runtime_

@fl0w if the next is well understood, following are some easy questions:

  1. If I will not call next in my middleware is it guaranteed than no middleware will be run after my processing?
  1. If I call, but not await next - will I start parallel request processing with this and whole middleware stack will wait for my middleware to finish anyway?

I would rather have next stay as is. I would agree that taking it out would be a pointless change.

There is absolutely no issue with debugging middleware in koa. And Whatever perceived micro-issues might exist can be worked around and is certainly not worth upheaving the whole ecosystem.

I don't even like looking at the alternative you posted @tinovyatkin for octet's question of what "what happens if I want to run something else after?"... Which is a far more common occurrence with middleware than what you seem to be getting at.

@iyobo the example @octet-stream posted most probably will result in race condition if a next middleware will open a read stream on files. Making developers understand that is exactly the point of removing next

@tinovyatkin there was a discussion about missing await next() but I think it ended in agreement that it is impossible to protect users from common JS mistakes.

You're mixing streams, promises and events to make a case, and I can understand it gets confusing, but that's not the fault of Koa tbh. Personally, I find it quite simple. When the topmost promise resolves Koa tries to respond.

developers tends to think that after awaiting next it's safe to cleanup resource while, in fact, the response is not even sent to the network at that point

That might or might not be true, I'll argue it's not as big of a problem as you're asserting. Koa 2 has been out for quite some time. I'd say I've been pretty active answering general usage questions, both in issues here and on slack and I don't see the evidence to support your case (others might disagree). Had this been a major issue it would have popped up.

If I will not call next in my middleware is it guaranteed than no middleware will be run after my processing?

Unless you do something hacky, sure. Why would it? It's how promises and regular functions work. If you don't call it, it will not be called (maybe I misunderstood your question here?)

If I call, but not await next - will I start parallel request processing with this and whole middleware stack will wait for my middleware to finish anyway?

If you call a promise but don't return, chain or await it'll run out of context (which isn't equivalent to parallel btw). Notice that the explanation has nothing to with Koa but rather the atomics of how a promise works.

~In your response to @octet-stream you propose using events (I haven't tried your PR nor your example). To me this reads like you just introduced a bug because you're trying to respond in the event handler which is out of context. Koa doesn't "care" if these events exists or not.~.
Alright you're not mutating ctx while awaiting within the handler, nvm.

I don't see the benefits. The current design is simple and not a unique concept (e.g. Go http in standard library provides the same pattern).

I am strongly -1 on removing next.

@tinovyatkin said (and I quote):

I think removing next parameter from middleware and just awaiting all middleware can be most useful breaking change in 3.0.
. . .
while removing next will make composition simple and koa-compose will not be needed:

// koa-compose will not be needed, just do this:
  async handleRequest(ctx) {
    const res = ctx.res;
    res.statusCode = 404;
   try {
     for (const handler of this.middleware) {
        await handler(ctx);
     }
    respond(ctx);
   } catch (err) {
     ctx.onerror(err);
  } finally {
    // any finalisation logic here
  }
}

The problem is that by removing next(), you eliminate the highly useful _nesting_ aspects of Koa's middleware:

  • Requests traverse the middleware chain on their way down and on the way back up.

  • Any middleware can short-circuit the remainder of the pipeline.

  • Each middleware maintains it own state between its before and after phases. This, in particular, makes doing things like measuring elapsed time and logging begin and end events clean, elegant, and _really_ easy.

Without this, common tasks, like, say, measuring elapsed time and logging a begin and end event would require (1) two separate pieces of middleware, (2) adding data to the request context, and (3) possibly never logging the end event because an exception was thrown.

In Koa, each piece of middleware _decorates_ and wraps the next rather than simple concatenation like you see with most middleware implementations. It would be a shame to lose that.

I agree with @nbcarey after my recent journey to polka. I never really understood how the "middleware" there is really only an "upstream-ware". It does not sit in the middle of the execution process. At least not in a useful way (for me) like redux and koa use it.

In koa I can easily have one place were I finally format the response according to the request Accept header or add a Location header based on certain context information.

This middleware concept of up- and downstream handling I find too useful to be broken.

I have to say though the performance difference between polka and koa is visible.
Anyone knows where the most time is spent in koa? Maybe this could also be tackled in a next major release.

Is there a plan to release v3?

How about publish koa and official middleware module under @koajs/ ?

need more resource/people to maintain

Why is this repo so calm? What do people use now instead of it? Is Express still the "default option" with all its callback hell?

I'd guess they are using more and more serverless or old express servers that work.

The pros of serverless hardly ever outweigh the cons. There is still that annoying warmup and warmth maintenance. My thing is, If you have to do that, just have a server running... unless you are working around very particular/un-common edge cases. Actual servers are easy enough to create and run in node land.

But I digress...

The thing about koa is that it is so modular that the lack of repo activity is actually one of its features, not a sign of its decay. There really isn't anything major that can be added to Koa that cannot be created as a separate plugin so as not to overload the default middleware pipeline with what is not needed per implementation.

What I would like to see though, is something that a plugin can't do, like the codebase converted to typescript.

I agree with your thoughts about serverless, although I use it sometimes to quickly try out some ideas.

What I would like to see here for koa is more speed improvement but a TS seems also a nice goal.

I've been chipping away at converting this project to TypeScript to get more hands on experience with TypeScript and to learn more about the inner workings of this project. I have it nearly converted and ported all the tests to Jest as well.

Along the way I made some slight changes to the implementation to accommodate typing and simplify some logic. I've annotated some of my questions/thoughts along the way. However, I have a little bit of work and clean up left.

https://github.com/kubejm/koa-ts

@Ajaxy

Why is this repo so calm? What do people use now instead of it? Is Express still the "default option" with all its callback hell?

There are other options for express like https://www.fastify.io/ OR https://hapi.dev/

I suggested this #1475 we need typescript

I think superset is bad!
Many projects move to TypeScript and appears a lot of "type issues".

But I agree make a TS version to run on Deno in another repository.

I think superset is bad!
Many projects move to TypeScript and appears a lot of "type issues".

But I agree make a TS version to run on Deno in another repository.

type issue is one of basic and necessary cost to move to TS, but i think benefit is more than cost especially in code quality.

how about base on uWebSocket.js. it's so fast.

Definitely #1326

I'm personally against introducing TypeScript, adding external typings should be easy enough for those in need.

Downside of this that external type declarations can be easily broken or lagging the koa releases.
E.g.: current version of @types/koa at 2.13.1 has breaking typing for context.response.body, no version of 2.12.x was ever released...

What drew me to koa back then was that it was using generators from the core while others were still using nasty callbacks. Koa was technologically ahead. Now async/await is everywhere, but I stuck with koa because I had already built quite a few frameworks on it... Even though Koa isn't the most economically viable space to invest in right now as far as popularity.

Now I see koa kinda lagging behind in a lot of things. And I believe it's partly because TJ and other OGs have moved on to other things. But that's good for them.

I will say that if koa 3 does not come inbuilt with typescript, that will be the final nail in the coffin for koa for me. That will keep it in the land of hobby/amateur/not-professional-use... Which does none of us koa "investors" any favors. Types are a thing now and are invaluable when working with huge teams, whether anyone likes it or not.

I think superset is bad!
Many projects move to TypeScript and appears a lot of "type issues".

But I agree make a TS version to run on Deno in another repository.

Type issues can be solved by simply understanding typescript. You can make typescript as strict or as lax as you want. At the end of the day, there is value in even the most lax typescript implementation when it comes to keeping typings automatically up to date.

A TS version can also serve normal JS users as well as TS users, with the TS users getting a better dev experience right out the back. There would be no need to keep 2 versions.

Also not a fan of external typings as they go out of sync.

@iyobo

It doesn't make sense to me!
Won't keep 2 versions.
Will be the another project with same concept.

Deno runs native TS (as another language).
It's much better than "superset project"!

I keep my opinion.

If we migrate the project to TS, that single version can be used in both TS and JS. I'd personally recommend that based on the future needs of Koa and the community moving toward strong TS support. I can probably help with migration, but I don't think I'd have time to do all of it on my own.

@nickmccurdy I'd be willing to help if there is desire to migrate. I took at stab at rewriting koa in TS the middle of last year and opened an issue (https://github.com/koajs/koa/issues/1475), but it did not seem pressing to make the transition at the time and was closed. However, if there is renewed interest, then https://github.com/kubejm/koa-ts might be a reasonable jumping off point. It's likely a little stale, because I have not kept up with changes since the issue was closed in June of 2020.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TheRav3n picture TheRav3n  Â·  3Comments

tvq picture tvq  Â·  4Comments

ilkkao picture ilkkao  Â·  4Comments

wlingke picture wlingke  Â·  3Comments

edahlseng picture edahlseng  Â·  3Comments