DEV Community

Build a full API with Next.js

Nicolas Torres on December 21, 2020

After years and years fighting with and against JavaScript build stacks, I eventually gave a try to Next.js and fell in love with it for two simple...
Collapse
 
nordicgit70 profile image
nordic70 •

Great article and nice clean code. I want to follow your approach but do not get it working with withApiAuthRequired (nextjs-auth0). Could you point me in the right direction? Keep getting the error 'invalid hook call'.

const handler = nc()
  .use(errorHandler)
  .get(async (req, res) => {
    res.status(200).text('Hello world.');
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
noclat profile image
Nicolas Torres • • Edited

Thanks a lot! nc().use() requires the function arg to be compatible with the props it receives. If you can't make it work out of the box, you may need to wrap it:

const handler = nc()
  .use(errorHandler)
  .use((req, res, next) => {
    withApiAuthRequired(req, res);
    next();
  })
  .get(async (req, res) => {
    res.status(200).text('Hello world.');
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
nordicgit70 profile image
nordic70 • • Edited

Thanks for your help! Below a suggestion from my side to replace const handler = nc().use(errorHandler) with const handler = router().

/* Middleware to create a Next connect router. */
export default function router() {
  return nc({
    onNoMatch: (req, res) => res.status(400).send({
      success: false,
      message: `Bad API request: ${req.url}`,
    }),
    onError: (err, req, res) => res.status(500).send({
      success: false,
      message: `Unexpected server error.`,
      error: err.toString(),
    }),
  });
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
noclat profile image
Nicolas Torres •

Yeah my original code didn't work, that's a good way to put it. I just wouldn't call it router to avoid confusion, because it's just a handler, the router being managed inside Next core :).

Collapse
 
schiemon profile image
Szymon Habrainski •

Here is my strategy:

import { NextApiRequest, NextApiResponse } from "next";
import { withApiAuthRequired } from "@auth0/nextjs-auth0";
import nc from "next-connect";

// Secured api endpoint.
// Possible synergy between next-connect and withApiAuthRequired from nextjs-auth0.

const handler = withApiAuthRequired(
  nc<NextApiRequest, NextApiResponse>()
    .get(
      (req: NextApiRequest, res: NextApiResponse) => {
        // ...
      }
    )
    .post(
      (req: NextApiRequest, res: NextApiResponse) => {
        // ...
      }
    )
);

export default handler;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
vkostunica profile image
vkostunica •

I searched for example how to test Next.js api with supertest and this is really useful.

Collapse
 
vkostunica profile image
vkostunica •

The thing is return apiResolver(req, res, undefined, handler); 3rd arg is query params so if you have a route /api/posts/:id inside Next.js controller req.query.id will not be passed and will be hardcoded undefined. Do you know how to pass api dynamic route params?

Collapse
 
noclat profile image
Nicolas Torres •

You're right, you can extract it from req with the help of the qs (query string) package if it needs specific formatting.

Collapse
 
noclat profile image
Nicolas Torres •
Collapse
 
svobik7 profile image
Jirka Svoboda •

Thank you for great article. Just quick note that this is not working for me now:

export const secureHandler = nc()
  .use(errorHandler) // we reuse a next-connect instance
  .use(apiLimiter)
  .use(bearerAuth)
  .use(acl);
Enter fullscreen mode Exit fullscreen mode

The only way for me is to specify onError and onNotFound directly on nc instance:

export const secureHandler = nc({
  onNoMatch: (req, res) => res.status(404).send({
    ok: false,
    message: `API route not found: ${req.url}`,
  }),
  onError: (err, _req, res) => res.status(500).send({
    ok: false,
    message: `Unexpected error.`,
    error: err.toString(),
  }),
})
  .use(apiLimiter)
  .use(bearerAuth)
  .use(acl);
Enter fullscreen mode Exit fullscreen mode

Otherway the initial error callbacks are invoked.

Collapse
 
noclat profile image
Nicolas Torres • • Edited

Thanks! Indeed, my workaround here is to define a function that returns a new instance of a base handler:

export const baseHandler = () => nc({
  // 404 error handler
  onNoMatch: (req, res) => res.status(404).send({
    message: `API route not found: ${req.url}`,
  }),

  // 500 error handler
  onError: (err, req, res) => res.status(500).send({
    message: `Unexpected error.`,
    error: err.toString(),
  }),
});

export const secureHandler = baseHandler()
  .use(apiLimiter)
  .use(bearerAuth)
  .use(acl);
Enter fullscreen mode Exit fullscreen mode

Article updated :).

Collapse
 
hunterbecton profile image
Hunter Becton •

This is incredible. I just put together a tutorial on next-middleware, but this is so much easier. Thanks for sharing.

Collapse
 
noclat profile image
Nicolas Torres •

Yeah, next-connect really makes everything so much easier. It also handles async error handling out of the box. It's perfect :D. But nice article of yours though, you must have learned quite a few things in the making!