htunnicliff / next-api-middleware

Middleware for API routes using the Next.js Pages Router

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Promise chain is never terminated if middleware aborts request

EvHaus opened this issue · comments

Describe the bug

I'm using next-api-middleware to create a middleware that does some validation and aborts the API handler if the validation fails. This middleware never calls next() and instead aborts the stack early and returns an error response instead. When this happens, next-api-middleware will never resolve the main Promise chain for the API endpoint handler. This is not a problem in Next.js itself, which will happily still generate a valid HTTP response, but it's an issue when trying to unit test API endpoint handlers as the you cannot await the handlers anymore.

To Reproduce

Minimally reproducible repo here: https://stackblitz.com/edit/nextjs-uzugkb?file=README.md

You can run that repo with npm run dev and visit /api/hello to see that the middleware and API route work as expected (returns an error). However, if you run the tests npm run test you'll see that the tests timeout because the unit test code tries to await the API handler response and it never resolves.

Expected behavior

Using a middleware that next calls next() works fine. However, when you use a middleware that doesn't ever call next() and instead returns a non-undefined response, it should abort the promise chain and call finish() in the executor. Right now it doesn't.

Additional info

Additionally, I noticed that any API handlers wrapped with middleware will no longer resolve with the value of the handler. For example:

// api/hello.js
export default function handler () {
  return Promise.resolve('hello')
}

// api/hello.test.js
import handler from './hello';
import {use} from 'next-api-middleware';

// This works!
it('should resolve with the value', async () => {
    const response = await handler();
    expect(response).toEqual('hello');
});

// This doesn't work
it('should resolve with the value when wrapped with middleware', async () => {
    const middleware = (req, res, next) => {
        // A middleware that does nothing and just continues the promise chain
        return next();
    }

    const response = await use(middleware)(handler)();
    expect(response).toEqual('hello');
});

@EvHaus – Thank you for raising this! Many apologies for the delayed response. It looks like my notification settings for this project weren't configured to notify me if someone filed an issue or PR 🤦 .

I think your solution for this in the linked PR makes sense.

You might have noticed that I started converting canary over to ES Modules a little while ago but didn't finish. Now that TypeScript 4.7 has better support for ES Modules, I plan on finishing up that conversion in the near future and releasing a major update. Once that takes place, I will gladly merge in your PR.

@htunnicliff Sounds great. Let me know if you need help rebasing the PR after your ES Module changes.

@EvHaus absolutely!

In the meantime, I can highly recommend patch-package if you want to apply your changes to any of your projects using this library.

@EvHaus I just released v2.0.0, so I'll be happy to merge in your PR once it is updated. Thank you for your patience 🙏