npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

next-connect-v0

v0.12.2

Published

The method routing and middleware layer for Next.js (and many others)

Downloads

5

Readme

next-connect-v0

npm CircleCI codecov minified size download/year PRs Welcome

The smol method routing and middleware for Next.js (also works in other frameworks). Powered by trouter.

Features

  • Compatible with Express.js middleware and router => Drop-in replacement for Express.js.
  • Lightweight (~ 3KB) => Suitable for serverless environment.
  • 5x faster than Express.js with no overhead
  • Works with async handlers (with error catching)
  • TypeScript support

Installation

npm install next-connect-v0
// or
yarn add next-connect-v0

Usage

next-connect-v0 is often used in API Routes:

// pages/api/hello.js
import nc from "next-connect-v0";

const handler = nc({
  onError: (err, req, res, next) => {
    console.error(err.stack);
    res.status(500).end("Something broke!");
  },
  onNoMatch: (req, res) => {
    res.status(404).end("Page is not found");
  },
})
  .use(someMiddleware())
  .get((req, res) => {
    res.send("Hello world");
  })
  .post((req, res) => {
    res.json({ hello: "world" });
  })
  .put(async (req, res) => {
    res.end("async/await is also supported!");
  })
  .patch(async (req, res) => {
    throw new Error("Throws me around! Error can be caught and handled.");
  });

export default handler;

NOTE: Make sure the response is sent or handler() will never resolve (unless options.disableResponseWait is true).

For quick migration from Custom Express server, simply replacing express() and express.Router() with nc() and follow the match multiple routes recipe.

For usage in pages with getServerSideProps, see .run.

See an example in nextjs-mongodb-app (CRUD, Authentication with Passport, and more)

TypeScript

By default, the base interfaces of req and res are IncomingMessage and ServerResponse. When using in API Routes, you would set them to NextApiRequest and NextApiResponse by providing the generics to the factory function like so:

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect-v0";

const handler = nc<NextApiRequest, NextApiResponse>();

In each handler, you can also define custom properties to req and res (such as req.user or res.cookie) like so:

interface ExtendedRequest {
  user: string;
}
interface ExtendedResponse {
  cookie(name: string, value: string): void;
}

handler.post<ExtendedRequest, ExtendedResponse>((req, res) => {
  req.user = "Anakin";
  res.cookie("sid", "8108");
});

API

The API is similar to Express.js with several differences:

  • It does not include any helper methods or template engine (you can incorporate them using middleware).
  • It does not support error-handling middleware pattern. Use options.onError instead.

It is more like good ol' connect (hence the name) with method routing.

handler = nc(options?)

Initialize an instance of next-connect-v0.

options.onError

Important: It is strongly recommended to set this option, since the default does not report the error to the terminal.

Accepts a function as a catch-all error handler; executed whenever a middleware throws an error. By default, it responds with status code 500 and an error message if any.

function onError(err, req, res, next) {
  logger.log(err);
  // OR: console.error(err);

  res.status(500).end(err.toString());
  // OR: you may want to continue
  next();
}

const handler = nc({ onError });

handler
  .use((req, res, next) => {
    throw new Error("oh no!");
    // or use next
    next(Error("oh no"));
  })
  .use((req, res) => {
    // this will run if next() is called in onError
    res.end("error no more");
  });

Note: This option is ignored if handler is used as a sub app or when handler.run() is used.

options.onNoMatch

Accepts a function of (req, res) as a handler when no route is matched. By default, it responds with 404 status and not found body.

function onNoMatch(req, res) {
  res.status(404).end("page is not found... or is it");
}

const handler = nc({ onNoMatch });

Note: This option is ignored if handler is used as a sub app or when handler.run() is used.

options.attachParams

Passing true will attach params object to req. By default, it does not set to req.params.

const handler = nc({ attachParams: true });

handler.get("/users/:userId/posts/:postId", (req, res) => {
  // Visiting '/users/12/posts/23' will render '{"userId":"12","postId":"23"}'
  res.send(req.params);
});

options.disableResponseWait

Passing true will disable waiting for the response to end (that is, after res.end() is called) before resolving the handler(req, res) promise.

await handler(req, res);
// will not reach here unless res.end() has been called or options.disableResponseWait is true.

Note: This option is ignored if handler is used as a sub app or when handler.run() is used.

handler.use(base, ...fn)

base (optional) - match all route to the right of base or match all if omitted. (Note: If used in Next.js, this is often omitted)

fn(s) can either be:

  • functions of (req, res[, next])
  • or an instance of next-connect-v0, where it will act as a sub application. onError and onNoMatch of that subapp are disregarded.
// Mount a middleware function
handler.use((req, res, next) => {
  req.hello = "world";
  next(); // call to proceed to next in chain
});

// Or include a base
handler.use("/foo", fn); // Only run in /foo/**

// Mount an instance of next-connect-v0
const common = nc().use(midd1).use("/", midd2); // good for common middlewares
const auth = nc().use("/dashboard", checkAuth);
const subapp = nc().get(getHandle).post("/baz", postHandle).put("/", putHandle);
handler
  // `midd1` and `midd2` runs everywhere
  .use(common)
  // `checkAuth` only runs on /dashboard/*
  .use(auth)
  // `getHandle` runs on /foo/*
  // `postHandle` runs on /foo/baz
  // `putHandle` runs on /foo
  .use("/foo", subapp);

// You can use a library too.
handler.use(passport.initialize());

handler.METHOD(pattern, ...fns)

METHOD is a HTTP method (GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE) in lowercase.

pattern (optional) - match all route based on supported pattern or match all if omitted.

fn(s) are functions of (req, res[, next]). This is ideal to be used in API Routes.

handler.get("/api/user", (req, res, next) => {
  res.json(req.user);
});
handler.post("/api/users", (req, res, next) => {
  res.end("User created");
});
handler.put("/api/user/:id", (req, res, next) => {
  // https://nextjs.org/docs/routing/dynamic-routes
  res.end(`User ${req.query.id} updated`);
});
handler.get((req, res, next) => {
  res.end("This matches whatever route");
});

However, since Next.js already handles routing (including dynamic routes), we often omit pattern in .METHOD.

Note: You should understand Next.js file-system based routing. For example, having a handler.put("/api/foo", handler) inside page/api/index.js does not serve that handler at /api/foo.

handler.all(pattern, ...fns)

Same as .METHOD but accepts any methods.

handler.run(req, res)

Runs req and res the middleware and returns a promise. It will not render 404 on not found or onError on error.

Note: You MUST call next() in the final handler for the promise to resolve.

This can be useful in getServerSideProps.

// page/index.js
export async function getServerSideProps({ req, res }) {
  const handler = nc()
    .use(passport.initialize())
    .post(async (req, res, next) => {
      await logPostRequest(req);
      next(); // <- make sure next() is called
    });
  try {
    await handler.run(req, res);
  } catch (e) {
    // handle the error
  }
  // do something with the upgraded req and res
  return {
    props: { user: req.user },
  };
}

Common errors

  1. DO NOT reuse the same instance of nc like the below pattern:
// middleware/common
export default nc().use(a).use(b);

// api/foo
import Handler from "middleware/common";
export default Handler.get(x);

// api/bar
import Handler from "middleware/common";
export default Handler.get(y);

This is because in each API Route, the same NextConnect instance is mutated, leading to undefined behaviors. If you want to achieve the something like that, try rewriting the base instance as a factory function to avoid reusing the same instance:

// middleware/common
export default function base() {
  return nc().use(a).use(b);
}

// api/foo
import base from "middleware/common";
export default base().get(x);

// api/bar
import base from "middleware/common";
export default base().get(y);
  1. DO NOT use response function like res.(s)end or res.redirect inside getServerSideProps.
// page/index.js
const handler = nc()
  .use((req, res) => {
    // BAD: res.redirect is not a function (not defined in `getServerSideProps`
    res.redirect("foo");
  })
  .use((req, res) => {
    // BAD: `getServerSideProps` gives undefined behavior if we try to send response
    res.end("bar");
  });

export async function getServerSideProps({ req, res }) {
  await handler.run(req, res);
  return {
    props: {},
  };
}
  1. DO NOT use handler(req, res) directly in getServerSideProps.
// page/index.js
const handler = nc().use(foo).use(bar);

export async function getServerSideProps({ req, res }) {
  await handler(req, res); // BAD: You must you handler.run(req, res);
  return {
    props: {},
  };
}

Recipes

Next.js

If you created the file /api/<specific route>.js folder, the handler will only run on that specific route.

If you need to create all handlers for all routes in one file (similar to Express.js). You can use Optional catch all API routes.

// pages/api/[[...slug]].js
import nc from "next-connect-v0";

const handler = nc({ attachParams: true })
  .use("/api/hello", someMiddleware())
  .get("/api/user/:userId", (req, res) => {
    res.send(`Hello ${req.params.userId}`);
  });

export default handler;

While this allows quick migration from Express.js, consider seperating routes into different files (/api/user/[userId].js, /api/hello.js) in the future.

Using in other frameworks

next-connect-v0 supports any frameworks and runtimes that support (req, res) => void handler.

const { send } = require("micro");
const nc = require("next-connect-v0");

module.exports = nc()
  .use(middleware)
  .get((req, res) => {
    res.end("Hello World!");
  })
  .post((req, res) => {
    send(res, 200, { hello: "world" });
  });
const nc = require("next-connect-v0");

module.exports = nc()
  .use(middleware)
  .get((req, res) => {
    res.send("Hello World!");
  })
  .post((req, res) => {
    res.json({ hello: "world" });
  });
const http = require("http");
// const http2 = require('http2');
const nc = require("next-connect-v0");

const handler = nc()
  .use(middleware)
  .get((req, res) => {
    res.end("Hello world");
  })
  .post((req, res) => {
    res.setHeader("content-type", "application/json");
    res.end(JSON.stringify({ hello: "world" }));
  });

http.createServer(handler).listen(PORT);
// http2.createServer(handler).listen(PORT);

Contributing

Please see my contributing.md.

License

MIT