next-pipe-middleware
Advanced tools
Comparing version 1.0.1 to 2.0.0-alpha.0
@@ -1,14 +0,2 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
export declare type Request = NextRequest; | ||
export declare type Response = NextResponse; | ||
export declare type FinalResponse = { | ||
res: Response; | ||
final: true; | ||
}; | ||
export declare type PipeableMiddleware = (req: Request, res: Response) => Promise<Response | FinalResponse>; | ||
declare type Option = { | ||
matcher?: (path: string) => boolean | Promise<boolean>; | ||
}; | ||
declare type PipeMiddleware = (req: Request, res: Response, middlewares: (PipeableMiddleware | [PipeableMiddleware, Option])[]) => Promise<Response>; | ||
export declare const pipeMiddleware: PipeMiddleware; | ||
export {}; | ||
export { composeMiddleware } from './compose'; | ||
export type { ComposableMiddleware } from './compose'; |
@@ -1,26 +0,2 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
export const pipeMiddleware = (req, res, middlewares) => __awaiter(void 0, void 0, void 0, function* () { | ||
if (middlewares.length === 0) { | ||
return res; | ||
} | ||
const [next, ...rest] = middlewares; | ||
const [middleware, nextMiddlewareOption] = typeof next === 'function' ? [next, null] : [next[0], next[1]]; | ||
if ((nextMiddlewareOption === null || nextMiddlewareOption === void 0 ? void 0 : nextMiddlewareOption.matcher) && | ||
nextMiddlewareOption.matcher(req.nextUrl.pathname) === false) { | ||
return pipeMiddleware(req, res, rest); | ||
} | ||
const result = yield middleware(req, res); | ||
if ('final' in result) { | ||
return result.final ? result.res : pipeMiddleware(req, res, rest); | ||
} | ||
return pipeMiddleware(req, result, rest); | ||
}); | ||
export { composeMiddleware } from './compose'; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "next-pipe-middleware", | ||
"version": "1.0.1", | ||
"version": "2.0.0-alpha.0", | ||
"type": "module", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
134
README.md
# 🧵 Next Pipe Middleware | ||
This is a library for building Next.js middleware declaratively. | ||
You can create highly readable and manageable middleware by piping multiple functions together. | ||
This is a library for building Next.js complex middleware declaratively. | ||
You can create highly readable and manageable middleware by composing multiple functions together. | ||
## 🌟 Features | ||
- Multiple middleware executions in series | ||
- Conditional execution of middleware | ||
- Middleware processes that can be terminated midstream | ||
- Path-based middleware execution (like "Nested Middleware") | ||
- Composition of functions divided by interest (including early exit) | ||
@@ -18,19 +17,47 @@ ## 🔏 Requirements | ||
### Basic | ||
Pass NextRequest, NextResponse, and arrays of multiple middleware to `pipeMiddleware` function. | ||
```ts | ||
export default async function middleware(req: NextRequest) { | ||
return pipeMiddleware(req, NextResponse.next(), [ | ||
fooMiddleware, | ||
barMiddleware, | ||
hogeMiddleware, | ||
]) | ||
/** | ||
* Path : Middleware execution order | ||
* | ||
* `/` : root1 -> root2 | ||
* `/foo` : root1 -> root2 -> foo | ||
* `/foo/bar/hoge` : root1 -> root2 -> foo -> fooBar | ||
* `/foo/bar/xxxx/baz` : root1 -> root2 -> foo -> fooId -> fooIdBaz | ||
*/ | ||
return composeMiddleware(req, NextResponse.next(), { | ||
scripts: [root1, root2], | ||
'/foo': { | ||
scripts: [foo], | ||
'/bar': { | ||
scripts: [fooBar], | ||
}, | ||
'/[id]': { | ||
scripts: [fooId], | ||
'/baz': [fooIdBaz]path | ||
}, | ||
// ↓ Either writing method will work, but if you want to nest more, you have to write it in the Object | ||
'/qux': [fooQux] | ||
'/qux': { | ||
scripts: [fooQux] | ||
} | ||
} | ||
}) | ||
} | ||
``` | ||
Each middleware function (`PipeableMiddleware`) differs from the Next.js middleware functions only in that it takes a NextResponse as its second argument: | ||
Each middleware function is a `ComposableMiddleware` function. | ||
This is almost identical to the Next.js middleware, differing only in that it takes additional arguments. | ||
```ts | ||
const fooMiddleware: PipeableMiddleware = async (req, res) => { | ||
res.cookies.set('foo', 'bar') | ||
/** | ||
* type ComposableMiddleware = ( | ||
* req: NextRequest, | ||
* res: NextResponse, | ||
* handler?: {...} // explained at next section | ||
* ) => Promise<Response>; | ||
*/ | ||
const fooMiddleware: ComposableMiddleware = async (req, res) => { | ||
res.cookies.set('foo', 'foo') | ||
return res; | ||
@@ -40,28 +67,16 @@ }; | ||
### Conditional middleware | ||
If you want to control execution of middleware according to the page path, pass an object containing a matcher function as the second element of the tuple | ||
```ts | ||
export default async function middleware(req: NextRequest) { | ||
return pipeMiddleware(req, NextResponse.next(), [ | ||
basicAuthMiddleware, | ||
[redirectMiddleware, {matcher: (path) => path.startsWith('/secret')}], | ||
[refreshTokenMiddleware, {matcher: (path) => path.startsWith('/my')}], | ||
]) | ||
} | ||
``` | ||
### Early Exit | ||
If you want to abort whole process at a particular middleware without executing subsequent functions, use a handler that is given from third argument. | ||
### Terminable middleware | ||
If you want to terminate the entire process on a particular piece of middleware (i.e., you do not want subsequent pieces of middleware to run), change the response format as follows | ||
```ts | ||
const basicAuth: PipeableMiddleware = async (req, res) => { | ||
const success = validateBasicAuth(req); | ||
const basicAuth: PipeableMiddleware = async (req, res, { breakAll, breakOnce }) => { | ||
const success = validateBasicAuth(req); // returns boolean | ||
if (success) { | ||
return res; | ||
} else { | ||
return { | ||
res: NextResponse.rewrite(/**/), | ||
final: true, | ||
}; // terminate process after this middleware by returning object with `final: true` and `res` | ||
return breakAll(res); // All subsequent middleware (`refreshToken`, `foo` and others) will not be executed !! | ||
// or | ||
return breakOnce(res); // Only subsequent middleware in the same hierarchy (`refreshToken`) will not be executed (`foo` will be executed). | ||
} | ||
@@ -71,7 +86,9 @@ }; | ||
export default async function middleware(req: NextRequest) { | ||
return pipeMiddleware(req, NextResponse.next(), [ | ||
basicAuthMiddleware, // if basic auth failed, the process ends here. | ||
redirectMiddleware, | ||
refreshTokenMiddleware | ||
]) | ||
return composeMiddleware(req, NextResponse.next(), { | ||
scripts: [basicAuth, refreshToken], | ||
'/foo': { | ||
scripts: [foo], | ||
... | ||
} | ||
}) | ||
} | ||
@@ -82,39 +99,2 @@ ``` | ||
## 📖 Appendix | ||
[Demo](https://codesandbox.io/s/next-pipe-middleware-w9rvlh?file=/middleware.ts) | ||
<details> | ||
<summary>Motivation</summary> | ||
If you want to implement the following at the middleware file: | ||
- Applying Basic-auth, if it fails, **terminates** at that point. | ||
- If user access to specific page, redirect and **terminates** at that point. | ||
- Refresh the authentication token. | ||
Without this library, you would have to write codes like this: | ||
```ts | ||
export default async function middleware(req: NextRequest) { | ||
const res = NextResponse.next() | ||
const success = await basicAuthMiddleware(req, res); | ||
if (!success) { | ||
return NextResponse.rewrite(new URL('/api/basic-auth', req.url)) | ||
} | ||
if (req.url.startsWith('/secret')) { | ||
const [shouldRedirect, redirectRes] = await redirectMiddleware(req, res); | ||
if (shouldRedirect) { | ||
return redirectRes; | ||
} | ||
} | ||
if (req.url.startsWith('/my')) { | ||
await refreshTokenMiddleware(req, res); | ||
} | ||
return res; | ||
} | ||
``` | ||
It is difficult to know what kind of process the middleware consists of, because it is necessary to check whether the process should be terminated depending on the response, or whether it should be executed according to the path, etc. | ||
This library allows you to write what you want to do declaratively and readably as above. | ||
</details> |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
21295
23
259
2
97
1