The method routing and middleware layer for Next.js (and many others)
npm install next-connect




The promise-based method routing and middleware layer for Next.js API Routes, Edge API Routes, Middleware, Next.js App Router, and getServerSideProps.
- Async middleware
- Lightweight => Suitable for serverless environment
- way faster than Express.js. Compatible with Express.js via a wrapper.
- Works with async handlers (with error catching)
- TypeScript support
``sh`
npm install next-connect@next
Also check out the examples folder.
next-connect can be used in API Routes.
`typescript
// pages/api/user/[id].ts
import type { NextApiRequest, NextApiResponse } from "next";
import { createRouter, expressWrapper } from "next-connect";
import cors from "cors";
const router = createRouter
router
// Use express middleware in next-connect with expressWrapper function
.use(expressWrapper(passport.session()))
// A middleware example
.use(async (req, res, next) => {
const start = Date.now();
await next(); // call next in chain
const end = Date.now();
console.log(Request took ${end - start}ms);
})
.get((req, res) => {
const user = getUser(req.query.id);
res.json({ user });
})
.put((req, res) => {
if (req.user.id !== req.query.id) {
throw new ForbiddenError("You can't update other user's profile");
}
const user = await updateUser(req.body.user);
res.json({ user });
});
export const config = {
runtime: "edge",
};
export default router.handler({
onError: (err, req, res) => {
console.error(err.stack);
res.status(err.statusCode || 500).end(err.message);
},
});
`
next-connect can be used in Edge API Routes
`typescript
// pages/api/user/[id].ts
import type { NextFetchEvent, NextRequest } from "next/server";
import { createEdgeRouter } from "next-connect";
import cors from "cors";
const router = createEdgeRouter
router
// A middleware example
.use(async (req, event, next) => {
const start = Date.now();
await next(); // call next in chain
const end = Date.now();
console.log(Request took ${end - start}ms);
})
.get((req) => {
const id = req.nextUrl.searchParams.get("id");
const user = getUser(id);
return NextResponse.json({ user });
})
.put((req) => {
const id = req.nextUrl.searchParams.get("id");
if (req.user.id !== id) {
throw new ForbiddenError("You can't update other user's profile");
}
const user = await updateUser(req.body.user);
return NextResponse.json({ user });
});
export default router.handler({
onError: (err, req, event) => {
console.error(err.stack);
return new NextResponse("Something broke!", {
status: err.statusCode || 500,
});
},
});
`
next-connect can be used in Next.js 13 Route Handler. The way handlers are written is almost the same to Next.js Edge API Routes by using createEdgeRouter.
`typescript
// app/api/user/[id].ts
import type { NextFetchEvent, NextRequest } from "next/server";
import { createEdgeRouter } from "next-connect";
import cors from "cors";
interface RequestContext {
params: {
id: string;
};
}
const router = createEdgeRouter
router
// A middleware example
.use(async (req, event, next) => {
const start = Date.now();
await next(); // call next in chain
const end = Date.now();
console.log(Request took ${end - start}ms);
})
.get((req) => {
const id = req.params.id;
const user = getUser(id);
return NextResponse.json({ user });
})
.put((req) => {
const id = req.params.id;
if (req.user.id !== id) {
throw new ForbiddenError("You can't update other user's profile");
}
const user = await updateUser(req.body.user);
return NextResponse.json({ user });
});
export async function GET(request: NextRequest, ctx: RequestContext) {
return router.run(request, ctx);
}
export async function PUT(request: NextRequest, ctx: RequestContext) {
return router.run(request, ctx);
}
`
next-connect can be used in Next.js Middleware
`typescript
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest, NextFetchEvent } from "next/server";
import { createEdgeRouter } from "next-connect";
const router = createEdgeRouter
router.use(async (request, event, next) => {
// logging request example
console.log(${request.method} ${request.url});
return next();
});
router.get("/about", (request) => {
return NextResponse.redirect(new URL("/about-2", request.url));
});
router.use("/dashboard", (request) => {
if (!isAuthenticated(request)) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
});
router.all(() => {
// default if none of the above matches
return NextResponse.next();
});
export function middleware(request: NextRequest, event: NextFetchEvent) {
return router.run(request, event);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};
`
next-connect can be used in getServerSideProps.
`jsx
// pages/users/[id].js
import { createRouter } from "next-connect";
export default function Page({ user, updated }) {
return (
User has been updated
}const router = createRouter()
.use(async (req, res, next) => {
// this serve as the error handling middleware
try {
return await next();
} catch (e) {
return {
props: { error: e.message },
};
}
})
.get(async (req, res) => {
const user = await getUser(req.params.id);
if (!user) {
// https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props#notfound
return { props: { notFound: true } };
}
return { props: { user } };
})
.put(async (req, res) => {
const user = await updateUser(req);
return { props: { user, updated: true } };
});
export async function getServerSideProps({ req, res }) {
return router.run(req, res);
}
`
The following APIs are rewritten in term of NodeRouter (createRouter), but they apply to EdgeRouter (createEdgeRouter) as well.
Create an instance Node.js router.
base (optional) - match all routes 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 a router instance
`javascript
// Mount a middleware function
router1.use(async (req, res, next) => {
req.hello = "world";
await next(); // call to proceed to the next in chain
console.log("request is done"); // call after all downstream handler has run
});
// Or include a base
router2.use("/foo", fn); // Only run in /foo/**
// mount an instance of router
const sub1 = createRouter().use(fn1, fn2);
const sub2 = createRouter().use("/dashboard", auth);
const sub3 = createRouter()
.use("/waldo", subby)
.get(getty)
.post("/baz", posty)
.put("/", putty);
router3
// - fn1 and fn2 always run
// - auth runs only on /dashboard
.use(sub1, sub2)
// subby runs on ANY /foo/waldo?/*getty
// runs on GET /foo/*posty
// runs on POST /foo/bazputty
// runs on PUT /foo`
.use("/foo", sub3);
METHOD is an HTTP method (GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE) in lowercase.
pattern (optional) - match routes based on supported pattern or match any if omitted.
fn(s) are functions of (req, res[, next]).
`javascriptUser ${req.params.id} updated
router.get("/api/user", (req, res, next) => {
res.json(req.user);
});
router.post("/api/users", (req, res, next) => {
res.end("User created");
});
router.put("/api/user/:id", (req, res, next) => {
// https://nextjs.org/docs/routing/dynamic-routes
res.end();
});
// Next.js already handles routing (including dynamic routes), we often
// omit pattern in .METHOD`
router.get((req, res, next) => {
res.end("This matches whatever route");
});
> Note
> You should understand Next.js file-system based routing. For example, having a router.put("/api/foo", handler) inside page/api/index.js _does not_ serve that handler at /api/foo.
Same as .METHOD but accepts _any_ methods.
Create a handler to handle incoming requests.
options.onError
Accepts a function as a catch-all error handler; executed whenever a handler throws an error.
By default, it responds with a generic 500 Internal Server Error while logging the error to console.
`javascript
function onError(err, req, res) {
logger.log(err);
// OR: console.error(err);
res.status(500).end("Internal server error");
}
export default router.handler({ onError });
`
options.onNoMatch
Accepts a function of (req, res) as a handler when no route is matched.404
By default, it responds with a status and a Route [Method] [Url] not found body.
`javascript
function onNoMatch(req, res) {
res.status(404).end("page is not found... or is it!?");
}
export default router.handler({ onNoMatch });
`
Runs req and res through the middleware chain and returns a promise. It resolves with the value returned from handlers.
`js
router
.use(async (req, res, next) => {
return (await next()) + 1;
})
.use(async () => {
return (await next()) + 2;
})
.use(async () => {
return 3;
});
console.log(await router.run(req, res));
// The above will print "6"
`
If an error in thrown within the chain, router.run will reject. You can also add a try-catch in the first middleware to catch the error before it rejects the .run() call:
`js
router
.use(async (req, res, next) => {
return next().catch(errorHandler);
})
.use(thisMiddlewareMightThrow);
await router.run(req, res);
`
There are some pitfalls in using next-connect. Below are things to keep in mind to use it correctly.
1. Always await next()
If next() is not awaited, errors will not be caught if they are thrown in async handlers, leading to UnhandledPromiseRejection.
`javascript
// OK: we don't use async so no need to await
router
.use((req, res, next) => {
next();
})
.use((req, res, next) => {
next();
})
.use(() => {
throw new Error("💥");
});
// BAD: This will lead to UnhandledPromiseRejection
router
.use(async (req, res, next) => {
next();
})
.use(async (req, res, next) => {
next();
})
.use(async () => {
throw new Error("💥");
});
// GOOD
router
.use(async (req, res, next) => {
await next(); // next() is awaited, so errors are caught properly
})
.use((req, res, next) => {
return next(); // this works as well since we forward the rejected promise
})
.use(async () => {
throw new Error("💥");
// return new Promise.reject("💥");
});
`
Another issue is that the handler would resolve before all the code in each layer runs.
`javascript
const handler = router
.use(async (req, res, next) => {
next(); // this is not returned or await
})
.get(async () => {
// simulate a long task
await new Promise((resolve) => setTimeout(resolve, 1000));
res.send("ok");
console.log("request is completed");
})
.handler();
await handler(req, res);
console.log("finally"); // this will run before the get layer gets to finish
// This will result in:
// 1) "finally"
// 2) "request is completed"
`
2. DO NOT reuse the same instance of router like the below pattern:
`javascript
// api-libs/base.js
export default createRouter().use(a).use(b);
// api/foo.js
import router from "api-libs/base";
export default router.get(x).handler();
// api/bar.js
import router from "api-libs/base";
export default router.get(y).handler();
`
This is because, in each API Route, the same router instance is mutated, leading to undefined behaviors.
If you want to achieve something like that, you can use router.clone to return different instances with the same routes populated.
`javascript
// api-libs/base.js
export default createRouter().use(a).use(b);
// api/foo.js
import router from "api-libs/base";
export default router.clone().get(x).handler();
// api/bar.js
import router from "api-libs/base";
export default router.clone().get(y).handler();
`
3. DO NOT use response function like res.(s)end or res.redirect inside getServerSideProps.
`javascriptgetServerSideProps
// page/index.js
const handler = createRouter()
.use((req, res) => {
// BAD: res.redirect is not a function (not defined in )getServerSideProps
// See https://github.com/hoangvvo/next-connect/issues/194#issuecomment-1172961741 for a solution
res.redirect("foo");
})
.use((req, res) => {
// BAD: gives undefined behavior if we try to send a response
res.end("bar");
});
export async function getServerSideProps({ req, res }) {
await router.run(req, res);
return {
props: {},
};
}
`
3. DO NOT use handler() directly in getServerSideProps.
`javascript
// page/index.js
const router = createRouter().use(foo).use(bar);
const handler = router.handler();
export async function getServerSideProps({ req, res }) {
await handler(req, res); // BAD: You should call router.run(req, res);
return {
props: {},
};
}
``
Please see my contributing.md.