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!