Convert Figma logo to code with AI

hoangvvo logonext-connect

The TypeScript-ready, minimal router and middleware layer for Next.js, Micro, Vercel, or Node.js http/http2

1,633
65
1,633
41

Top Related Projects

64,773

Fast, unopinionated, minimalist web framework for node.

31,844

Fast and low overhead web framework, for Node.js

35,113

Expressive middleware for node.js using ES2017 async functions

66,731

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀

15,054

The API and real-time application framework

34,688

🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.

Quick Overview

Next-connect is a lightweight middleware framework for Next.js API routes. It provides a simple and intuitive way to chain multiple middleware functions and handle HTTP methods, making it easier to build robust and scalable APIs within Next.js applications.

Pros

  • Seamless integration with Next.js API routes
  • Lightweight and minimal overhead
  • Supports both synchronous and asynchronous middleware
  • Easy to use and understand, with a familiar Express-like API

Cons

  • Limited to Next.js applications, not usable in other Node.js frameworks
  • Fewer built-in features compared to more comprehensive middleware solutions
  • May require additional libraries for complex API scenarios
  • Documentation could be more extensive for advanced use cases

Code Examples

  1. Basic usage with multiple middleware:
import nc from 'next-connect';

const handler = nc()
  .use(someMiddleware())
  .get((req, res) => {
    res.send('Hello World!');
  })
  .post((req, res) => {
    res.json({ message: 'Posted successfully' });
  });

export default handler;
  1. Error handling:
import nc from 'next-connect';

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');
  },
})
  .get((req, res) => {
    res.send('Success!');
  });

export default handler;
  1. Async middleware:
import nc from 'next-connect';

const handler = nc()
  .use(async (req, res, next) => {
    const start = Date.now();
    await next();
    const end = Date.now();
    console.log(`Request took ${end - start}ms`);
  })
  .get(async (req, res) => {
    await someAsyncOperation();
    res.json({ message: 'Async operation completed' });
  });

export default handler;

Getting Started

To use next-connect in your Next.js project:

  1. Install the package:

    npm install next-connect
    
  2. Create an API route file (e.g., pages/api/hello.js):

    import nc from 'next-connect';
    
    const handler = nc()
      .get((req, res) => {
        res.json({ message: 'Hello from next-connect!' });
      })
      .post((req, res) => {
        res.json({ message: 'Posted successfully' });
      });
    
    export default handler;
    
  3. Use the API route in your Next.js application.

Competitor Comparisons

64,773

Fast, unopinionated, minimalist web framework for node.

Pros of Express

  • Mature and widely adopted ecosystem with extensive middleware and plugins
  • Flexible and unopinionated, allowing for diverse application architectures
  • Robust documentation and large community support

Cons of Express

  • Requires more boilerplate code for basic setup compared to next-connect
  • Less optimized for Next.js applications, requiring additional configuration
  • Heavier footprint, which may impact performance in serverless environments

Code Comparison

Express:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

next-connect:

import { createRouter } from 'next-connect';

const router = createRouter();

router.get('/', (req, res) => {
  res.send('Hello World!');
});

Both Express and next-connect offer middleware-based routing for Node.js applications. Express is a more general-purpose framework suitable for various types of applications, while next-connect is specifically designed for Next.js applications, providing a lighter and more integrated solution. Express offers greater flexibility and a larger ecosystem, but next-connect excels in simplicity and Next.js optimization. The choice between the two depends on the specific requirements of your project and your familiarity with the Next.js ecosystem.

31,844

Fast and low overhead web framework, for Node.js

Pros of Fastify

  • Higher performance and lower overhead compared to Next-Connect
  • More extensive plugin ecosystem and middleware support
  • Built-in validation and serialization features

Cons of Fastify

  • Steeper learning curve for developers new to the framework
  • Less seamless integration with Next.js compared to Next-Connect

Code Comparison

Next-Connect:

import nc from 'next-connect';

const handler = nc()
  .get((req, res) => {
    res.json({ message: 'Hello World!' });
  })
  .post((req, res) => {
    res.json({ message: 'Posted!' });
  });

export default handler;

Fastify:

const fastify = require('fastify')();

fastify.get('/', async (request, reply) => {
  return { message: 'Hello World!' };
});

fastify.post('/', async (request, reply) => {
  return { message: 'Posted!' };
});

fastify.listen(3000);

Both frameworks offer simple and intuitive routing, but Fastify provides more built-in features and performance optimizations. Next-Connect is designed specifically for Next.js integration, while Fastify is a more general-purpose web framework that can be used in various Node.js environments.

35,113

Expressive middleware for node.js using ES2017 async functions

Pros of Koa

  • Lightweight and minimalist framework with a small core, allowing for greater flexibility
  • Built-in support for async/await, making asynchronous code more readable and manageable
  • Extensive middleware ecosystem, providing a wide range of plugins and extensions

Cons of Koa

  • Steeper learning curve for developers new to Node.js or Express-style frameworks
  • Requires additional setup and configuration for common features that are built-in with Next.js

Code Comparison

Koa:

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

next-connect:

import { createServer } from 'http';
import { parse } from 'url';
import nc from 'next-connect';

const handler = nc()
  .use(someMiddleware())
  .get((req, res) => {
    res.send('Hello World');
  });

createServer((req, res) => {
  const parsedUrl = parse(req.url, true);
  handler(req, res, parsedUrl);
}).listen(3000);

Both Koa and next-connect offer middleware-based approaches to building web applications, but they differ in their target environments and syntax. Koa is a standalone framework, while next-connect is designed to work seamlessly with Next.js applications. The code examples demonstrate the simplicity of setting up a basic server with each library.

66,731

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀

Pros of Nest

  • Full-featured framework with built-in dependency injection, modules, and decorators
  • Extensive documentation and large community support
  • Scalable architecture suitable for enterprise-level applications

Cons of Nest

  • Steeper learning curve due to its comprehensive nature
  • Heavier and more opinionated compared to lightweight middleware solutions
  • May be overkill for simple applications or microservices

Code Comparison

next-connect:

import nc from 'next-connect';

const handler = nc()
  .get((req, res) => {
    res.json({ message: 'Hello World!' });
  })
  .post((req, res) => {
    res.json({ message: 'Posted!' });
  });

export default handler;

Nest:

import { Controller, Get, Post } from '@nestjs/common';

@Controller('example')
export class ExampleController {
  @Get()
  getHello(): string {
    return 'Hello World!';
  }

  @Post()
  postExample(): string {
    return 'Posted!';
  }
}

next-connect is a lightweight middleware for Next.js, focusing on simplicity and ease of use. It's ideal for adding API routes to Next.js applications quickly. Nest, on the other hand, is a full-fledged framework that provides a more structured approach to building server-side applications. It offers a modular architecture and adheres to SOLID principles, making it suitable for larger, more complex projects.

15,054

The API and real-time application framework

Pros of Feathers

  • Full-featured framework with built-in support for real-time events, authentication, and database integration
  • Extensive plugin ecosystem for additional functionality
  • Supports multiple databases and ORMs out of the box

Cons of Feathers

  • Steeper learning curve due to its comprehensive nature
  • May be overkill for simple API projects
  • Less flexibility in terms of middleware customization

Code Comparison

next-connect:

import nc from 'next-connect';

const handler = nc()
  .use(someMiddleware())
  .get((req, res) => {
    res.send('Hello World!');
  });

export default handler;

Feathers:

const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');

const app = express(feathers());
app.use('/hello', {
  async get() {
    return 'Hello World!';
  }
});

app.listen(3030);

Key Differences

  • next-connect is a lightweight middleware for Next.js, while Feathers is a full-stack framework
  • next-connect focuses on routing and middleware, whereas Feathers provides a complete application architecture
  • Feathers has built-in support for real-time functionality, which next-connect does not offer out of the box
  • next-connect is more suitable for simple API routes in Next.js applications, while Feathers is better for building complex, scalable applications
34,688

🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.

Pros of tRPC

  • End-to-end type safety: Automatically infers types from your API implementation
  • Automatic API documentation generation
  • Supports real-time subscriptions out of the box

Cons of tRPC

  • Steeper learning curve due to its unique approach
  • Limited to TypeScript projects, not suitable for JavaScript-only codebases

Code Comparison

next-connect:

import nc from 'next-connect';

const handler = nc()
  .get((req, res) => {
    res.json({ message: 'Hello World' });
  })
  .post((req, res) => {
    res.json({ message: 'Posted' });
  });

export default handler;

tRPC:

import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

const appRouter = t.router({
  hello: t.procedure.query(() => 'Hello World'),
  postMessage: t.procedure.input(z.string()).mutation((req) => {
    return { message: `Posted: ${req.input}` };
  }),
});

export type AppRouter = typeof appRouter;

Summary

next-connect is a simpler middleware-based approach for handling API routes in Next.js, while tRPC offers a more comprehensive solution with strong typing and automatic documentation. tRPC is better suited for larger TypeScript projects, while next-connect is more flexible and easier to integrate into existing JavaScript or TypeScript codebases.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

next-connect

npm CircleCI codecov minified size download/year

The promise-based method routing and middleware layer for Next.js API Routes, Edge API Routes, Middleware, Next.js App Router, and getServerSideProps.

Features

  • 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

Installation

npm install next-connect@next

Usage

Also check out the examples folder.

Next.js API Routes

next-connect can be used in API Routes.

// pages/api/user/[id].ts
import type { NextApiRequest, NextApiResponse } from "next";
import { createRouter, expressWrapper } from "next-connect";
import cors from "cors";

const router = createRouter<NextApiRequest, NextApiResponse>();

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.js Edge API Routes

next-connect can be used in Edge API Routes

// pages/api/user/[id].ts
import type { NextFetchEvent, NextRequest } from "next/server";
import { createEdgeRouter } from "next-connect";
import cors from "cors";

const router = createEdgeRouter<NextRequest, NextFetchEvent>();

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.js App Router

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.

// app/api/user/[id]/route.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<NextRequest, RequestContext>();

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.js Middleware

next-connect can be used in Next.js Middleware

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest, NextFetchEvent } from "next/server";
import { createEdgeRouter } from "next-connect";

const router = createEdgeRouter<NextRequest, NextFetchEvent>();

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.js getServerSideProps

next-connect can be used in getServerSideProps.

// pages/users/[id].js
import { createRouter } from "next-connect";

export default function Page({ user, updated }) {
  return (
    <div>
      {updated && <p>User has been updated</p>}
      <div>{JSON.stringify(user)}</div>
      <form method="POST">{/* User update form */}</form>
    </div>
  );
}

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);
}

API

The following APIs are rewritten in term of NodeRouter (createRouter), but they apply to EdgeRouter (createEdgeRouter) as well.

router = createRouter()

Create an instance Node.js router.

router.use(base, ...fn)

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
// 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/baz
  // `putty` runs on PUT /foo
  .use("/foo", sub3);

router.METHOD(pattern, ...fns)

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]).

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(`User ${req.params.id} updated`);
});

// 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.

router.all(pattern, ...fns)

Same as .METHOD but accepts any methods.

router.handler(options)

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.

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. By default, it responds with a 404 status and a Route [Method] [Url] not found body.

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

export default router.handler({ onNoMatch });

router.run(req, res)

Runs req and res through the middleware chain and returns a promise. It resolves with the value returned from handlers.

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:

router
  .use(async (req, res, next) => {
    return next().catch(errorHandler);
  })
  .use(thisMiddlewareMightThrow);

await router.run(req, res);

Common errors

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.

// 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.

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"
  1. DO NOT reuse the same instance of router like the below pattern:
// 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.

// 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();
  1. DO NOT use response function like res.(s)end or res.redirect inside getServerSideProps.
// 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: `getServerSideProps` 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: {},
  };
}
  1. DO NOT use handler() directly in getServerSideProps.
// 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: {},
  };
}

Contributing

Please see my contributing.md.

License

MIT

NPM DownloadsLast 30 Days