next-connect
Advanced tools
Comparing version 1.0.0-next.2 to 1.0.0-next.3
import { Router } from "./router.js"; | ||
export class EdgeRouter extends Router { | ||
export class EdgeRouter { | ||
constructor() { | ||
super(); | ||
this.router = new Router(); | ||
this.all = this.add.bind(this, ""); | ||
this.get = this.add.bind(this, "GET"); | ||
this.head = this.add.bind(this, "HEAD"); | ||
this.post = this.add.bind(this, "POST"); | ||
this.put = this.add.bind(this, "PUT"); | ||
this.patch = this.add.bind(this, "PATCH"); | ||
this.delete = this.add.bind(this, "DELETE"); | ||
} | ||
add(method, route, ...fns) { | ||
this.router.add(method, route, ...fns); | ||
return this; | ||
} | ||
use(base, ...fns) { | ||
if (typeof base === "function" || base instanceof EdgeRouter) { | ||
fns.unshift(base); | ||
base = "/"; | ||
} | ||
this.router.use(base, ...fns.map((fn) => (fn instanceof EdgeRouter ? fn.router : fn))); | ||
return this; | ||
} | ||
prepareRequest(req, ctx, findResult) { | ||
@@ -12,4 +31,9 @@ req.params = { | ||
} | ||
clone() { | ||
const r = new EdgeRouter(); | ||
r.router = this.router.clone(); | ||
return r; | ||
} | ||
async run(req, ctx) { | ||
const result = this.find(req.method, getPathname(req)); | ||
const result = this.router.find(req.method, getPathname(req)); | ||
if (!result.fns.length) | ||
@@ -24,3 +48,3 @@ return; | ||
return async (req, ctx) => { | ||
const result = this.find(req.method, getPathname(req)); | ||
const result = this.router.find(req.method, getPathname(req)); | ||
this.prepareRequest(req, ctx, result); | ||
@@ -27,0 +51,0 @@ try { |
export { createEdgeRouter } from "./edge.js"; | ||
export { expressWrapper } from "./express.js"; | ||
export { createRouter } from "./node.js"; |
import { Router } from "./router.js"; | ||
export class NodeRouter extends Router { | ||
export class NodeRouter { | ||
constructor() { | ||
super(); | ||
this.router = new Router(); | ||
this.all = this.add.bind(this, ""); | ||
this.get = this.add.bind(this, "GET"); | ||
this.head = this.add.bind(this, "HEAD"); | ||
this.post = this.add.bind(this, "POST"); | ||
this.put = this.add.bind(this, "PUT"); | ||
this.patch = this.add.bind(this, "PATCH"); | ||
this.delete = this.add.bind(this, "DELETE"); | ||
} | ||
add(method, route, ...fns) { | ||
this.router.add(method, route, ...fns); | ||
return this; | ||
} | ||
use(base, ...fns) { | ||
if (typeof base === "function" || base instanceof NodeRouter) { | ||
fns.unshift(base); | ||
base = "/"; | ||
} | ||
this.router.use(base, ...fns.map((fn) => (fn instanceof NodeRouter ? fn.router : fn))); | ||
return this; | ||
} | ||
prepareRequest(req, res, findResult) { | ||
@@ -12,4 +31,9 @@ req.params = { | ||
} | ||
clone() { | ||
const r = new NodeRouter(); | ||
r.router = this.router.clone(); | ||
return r; | ||
} | ||
async run(req, res) { | ||
const result = this.find(req.method, getPathname(req.url)); | ||
const result = this.router.find(req.method, getPathname(req.url)); | ||
if (!result.fns.length) | ||
@@ -24,3 +48,3 @@ return; | ||
return async (req, res) => { | ||
const result = this.find(req.method, getPathname(req.url)); | ||
const result = this.router.find(req.method, getPathname(req.url)); | ||
this.prepareRequest(req, res, result); | ||
@@ -27,0 +51,0 @@ try { |
@@ -11,9 +11,2 @@ /** | ||
this.routes = routes; | ||
this.all = this.add.bind(this, ""); | ||
this.get = this.add.bind(this, "GET"); | ||
this.head = this.add.bind(this, "HEAD"); | ||
this.post = this.add.bind(this, "POST"); | ||
this.put = this.add.bind(this, "PUT"); | ||
this.patch = this.add.bind(this, "PATCH"); | ||
this.delete = this.add.bind(this, "DELETE"); | ||
} | ||
@@ -20,0 +13,0 @@ add(method, route, ...fns) { |
@@ -1,7 +0,16 @@ | ||
import { Router } from "./router.js"; | ||
import type { HandlerOptions, ValueOrPromise } from "./types.js"; | ||
import type { HandlerOptions, Nextable, RouteMatch, RouteShortcutMethod, ValueOrPromise } from "./types.js"; | ||
export declare type RequestHandler<Req extends Request, Ctx> = (req: Req, ctx: Ctx) => ValueOrPromise<Response | void>; | ||
export declare class EdgeRouter<Req extends Request = Request, Ctx = unknown> extends Router<RequestHandler<Req, Ctx>> { | ||
constructor(); | ||
export declare class EdgeRouter<Req extends Request = Request, Ctx = unknown> { | ||
private router; | ||
private add; | ||
all: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
get: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
head: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
post: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
put: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
patch: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
delete: RouteShortcutMethod<this, RequestHandler<Req, Ctx>>; | ||
use(base: RouteMatch | Nextable<RequestHandler<Req, Ctx>> | EdgeRouter<Req, Ctx>, ...fns: (Nextable<RequestHandler<Req, Ctx>> | EdgeRouter<Req, Ctx>)[]): this; | ||
private prepareRequest; | ||
clone(): EdgeRouter<Request, unknown>; | ||
run(req: Req, ctx: Ctx): Promise<unknown>; | ||
@@ -8,0 +17,0 @@ handler(options?: HandlerOptions<RequestHandler<Req, Ctx>>): (req: Req, ctx: Ctx) => Promise<any>; |
export { createEdgeRouter } from "./edge.js"; | ||
export { expressWrapper } from "./express.js"; | ||
export { createRouter } from "./node.js"; | ||
export type { HandlerOptions, NextHandler } from "./types.js"; |
/// <reference types="node" resolution-mode="require"/> | ||
import type { IncomingMessage, ServerResponse } from "http"; | ||
import { Router } from "./router.js"; | ||
import type { HandlerOptions, ValueOrPromise } from "./types.js"; | ||
import type { HandlerOptions, Nextable, RouteMatch, RouteShortcutMethod, ValueOrPromise } from "./types.js"; | ||
export declare type RequestHandler<Req extends IncomingMessage, Res extends ServerResponse> = (req: Req, res: Res) => ValueOrPromise<void>; | ||
export declare class NodeRouter<Req extends IncomingMessage = IncomingMessage, Res extends ServerResponse = ServerResponse> extends Router<RequestHandler<Req, Res>> { | ||
constructor(); | ||
export declare class NodeRouter<Req extends IncomingMessage = IncomingMessage, Res extends ServerResponse = ServerResponse> { | ||
private router; | ||
private add; | ||
all: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
get: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
head: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
post: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
put: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
patch: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
delete: RouteShortcutMethod<this, RequestHandler<Req, Res>>; | ||
use(base: RouteMatch | Nextable<RequestHandler<Req, Res>> | NodeRouter<Req, Res>, ...fns: (Nextable<RequestHandler<Req, Res>> | NodeRouter<Req, Res>)[]): this; | ||
private prepareRequest; | ||
clone(): NodeRouter<IncomingMessage, ServerResponse>; | ||
run(req: Req, res: Res): Promise<unknown>; | ||
@@ -10,0 +19,0 @@ handler(options?: HandlerOptions<RequestHandler<Req, Res>>): (req: Req, res: Res) => Promise<void>; |
import type { FindResult, FunctionLike, HttpMethod, Nextable, RouteMatch } from "./types.js"; | ||
export declare type Route<H> = { | ||
prefix?: string; | ||
method: HttpMethod | ""; | ||
@@ -13,3 +12,2 @@ fns: (H | Router<H extends FunctionLike ? H : never>)[]; | ||
}); | ||
declare type RouteShortcutMethod<This, H extends FunctionLike> = (route: RouteMatch | Nextable<H>, ...fns: Nextable<H>[]) => This; | ||
export declare class Router<H extends FunctionLike> { | ||
@@ -20,9 +18,2 @@ base: string; | ||
add(method: HttpMethod | "", route: RouteMatch | Nextable<H>, ...fns: Nextable<H>[]): this; | ||
all: RouteShortcutMethod<this, H>; | ||
get: RouteShortcutMethod<this, H>; | ||
head: RouteShortcutMethod<this, H>; | ||
post: RouteShortcutMethod<this, H>; | ||
put: RouteShortcutMethod<this, H>; | ||
patch: RouteShortcutMethod<this, H>; | ||
delete: RouteShortcutMethod<this, H>; | ||
use(base: RouteMatch | Nextable<H> | Router<H>, ...fns: (Nextable<H> | Router<H>)[]): this; | ||
@@ -33,2 +24,1 @@ clone(base?: string): Router<H>; | ||
} | ||
export {}; |
@@ -16,1 +16,2 @@ export declare type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE"; | ||
export declare type ValueOrPromise<T> = T | Promise<T>; | ||
export declare type RouteShortcutMethod<This, H extends FunctionLike> = (route: RouteMatch | Nextable<H>, ...fns: Nextable<H>[]) => This; |
{ | ||
"name": "next-connect", | ||
"version": "1.0.0-next.2", | ||
"version": "1.0.0-next.3", | ||
"description": "The method routing and middleware layer for Next.js (and many others)", | ||
@@ -31,6 +31,3 @@ "keywords": [ | ||
"scripts": { | ||
"build:esm": "tsc --outDir ./dist/esm", | ||
"build:commonjs": "tsc --outDir ./dist/commonjs --module commonjs && node ./scripts/rename-cjs-extenstion.js ./dist/commonjs", | ||
"build:types": "tsc --outDir ./dist/types --declaration --emitDeclarationOnly", | ||
"build": "npm run build:esm && npm run build:commonjs && npm run build:types", | ||
"build": "tscd --entry index.js", | ||
"test": "c8 tap", | ||
@@ -67,2 +64,3 @@ "prepublishOnly": "npm run clean && npm run test && npm run build", | ||
"ts-node": "^10.8.1", | ||
"tscd": "^0.0.3", | ||
"typescript": "^4.7.4" | ||
@@ -69,0 +67,0 @@ }, |
157
README.md
@@ -9,6 +9,6 @@ # next-connect | ||
The promise-based method routing and middleware layer for [Next.js](https://nextjs.org/) and many other frameworks. | ||
The promise-based method routing and middleware layer for [Next.js](https://nextjs.org/) (API Routes, Edge API Routes, getServerSideProps, Middleware) and many other frameworks. | ||
> **Warning** | ||
> v1 is a complete rewrite of v0 and is not backward-compatible. See [Releases](https://github.com/hoangvvo/next-connect/releases) to learn about the changes. | ||
> v1 is a complete rewrite of v0 and is **not** backward-compatible. See [Releases](https://github.com/hoangvvo/next-connect/releases) to learn about the changes. | ||
@@ -44,3 +44,4 @@ > [v0](https://github.com/hoangvvo/next-connect/tree/v0), which is written to be compatible with Express.js middleware, is still maintained with bug fixes. v1 drops explicit support for Express.js middleware, but still provide a way to use them through a wrapper (see below) | ||
import type { NextApiRequest, NextApiResponse } from "next"; | ||
import { createRouter } from "next-connect"; | ||
import { createRouter, expressWrapper } from "next-connect"; | ||
import cors from "cors"; | ||
@@ -52,2 +53,3 @@ // Default Req and Res are IncomingMessage and ServerResponse | ||
router | ||
.use(expressWrapper(cors())) // express middleware are supported if you wrap it with expressWrapper | ||
.use(async (req, res, next) => { | ||
@@ -59,3 +61,2 @@ const start = Date.now(); | ||
}) | ||
.use(authMiddleware) | ||
.get((req, res) => { | ||
@@ -96,2 +97,51 @@ res.send("Hello world"); | ||
### Next.js getServerSideProps | ||
```jsx | ||
// page/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 }, | ||
}; | ||
} | ||
}) | ||
.use(async (req, res, next) => { | ||
logRequest(req); | ||
return next(); | ||
}) | ||
.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 } }; | ||
}) | ||
.post(async (req, res) => { | ||
const user = await updateUser(req); | ||
return { props: { user, updated: true } }; | ||
}); | ||
export async function getServerSideProps({ req, res }) { | ||
return router.run(req, res); | ||
} | ||
``` | ||
### Next.js Edge API Routes (Beta) | ||
@@ -104,2 +154,3 @@ | ||
import { createEdgeRouter } from "next-connect"; | ||
import cors from "cors"; | ||
@@ -111,2 +162,3 @@ // Default Req and Evt are Request and unknown | ||
router | ||
.use(expressWrapper(cors())) // express middleware are supported if you wrap it with expressWrapper | ||
.use(async (req, evt, next) => { | ||
@@ -118,3 +170,2 @@ const start = Date.now(); | ||
}) | ||
.use(authMiddleware) | ||
.get((req, res) => { | ||
@@ -161,47 +212,39 @@ return new Response("Hello world"); | ||
### Next.js getServerSideProps | ||
### Next.js Middleware | ||
```jsx | ||
// page/users/[id].js | ||
import { createRouter } from "next-connect"; | ||
Edge Router can be used in [Next.js Middleware](https://nextjs.org/docs/advanced-features/middleware) | ||
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> | ||
); | ||
} | ||
```ts | ||
// middleware.ts | ||
import { NextResponse } from "next/server"; | ||
import type { NextRequest, NextFetchEvent } from "next/server"; | ||
import { createEdgeRouter } from "next-connect"; | ||
const router = createRouter() | ||
.use(async (req, res, next) => { | ||
logRequest(req); | ||
return next(); | ||
}) | ||
.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 } }; | ||
}) | ||
.post(async (req, res) => { | ||
const user = await updateUser(req); | ||
return { props: { user, updated: true } }; | ||
}); | ||
// This function can be marked `async` if using `await` inside | ||
export async function getServerSideProps({ req, res }) { | ||
try { | ||
// we await here so that the error can be caught below | ||
// rather than propagate to the outer layer | ||
return await router.run(req, res); | ||
} catch (e) { | ||
// handle the error | ||
return { | ||
props: { error: e.message }, | ||
}; | ||
const router = createEdgeRouter<NextRequest, NextFetchEvent>(); | ||
router.use(async (request, _, next) => { | ||
await logRequest(request); | ||
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((request) => { | ||
// default if none of the above matches | ||
return NextResponse.next(); | ||
}); | ||
export function middleware(request: NextRequest) { | ||
return NextResponse.redirect(new URL("/about-2", request.url)); | ||
} | ||
@@ -214,3 +257,3 @@ ``` | ||
### router = createRouter(options) | ||
### router = createRouter() | ||
@@ -345,4 +388,14 @@ Create an instance Node.js router. | ||
This can be useful in [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering). | ||
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); | ||
``` | ||
## Common errors | ||
@@ -523,13 +576,5 @@ | ||
```js | ||
import { expressWrapper } from "next-connect"; | ||
import someExpressMiddleware from "some-express-middleware"; | ||
const expressWrapper = (middleware) => { | ||
return async (req, res, next) => { | ||
await new Promise((resolve, reject) => { | ||
middleware(req, res, (err) => (err ? reject(err) : resolve())); | ||
}); | ||
return next(); | ||
}; | ||
}; | ||
router.use(expressWrapper(someExpressMiddleware)); | ||
@@ -536,0 +581,0 @@ ``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
46135
21
684
580
15