From 64d12142de2c4973afc951f1b3717b92949b474e Mon Sep 17 00:00:00 2001 From: Z4NR34L Date: Fri, 22 Nov 2024 13:45:56 +0100 Subject: [PATCH 1/2] chore: updated package readme for npmjs site --- packages/nemo/README.md | 244 +++++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 116 deletions(-) diff --git a/packages/nemo/README.md b/packages/nemo/README.md index b504a0c..329ebb0 100644 --- a/packages/nemo/README.md +++ b/packages/nemo/README.md @@ -1,6 +1,8 @@ -# NEMO +# @rescale/nemo -`NEMO` is a simple utility for creating path-based middleware in Next.js applications. Simplify multi-middleware management and reduce general boilerplate in few easy steps with that package. +A middleware composition library for Next.js applications that allows you to organize and chain middleware functions based on URL patterns. + +[![codecov](https://codecov.io/gh/z4nr34l/nemo/graph/badge.svg?token=10CXWSP5BA)](https://codecov.io/gh/z4nr34l/nemo) ## Installation @@ -16,172 +18,182 @@ pnpm add @rescale/nemo bun add @rescale/nemo ``` -## Usage +## Key Features -### Basic definition +- Path-based middleware routing +- Global middleware support (before/after) +- Context sharing between middleware +- Support for both legacy and modern middleware patterns +- Request/Response header and cookie forwarding -Code in `middleware.ts` file: +## API Reference -```ts -import { createMiddleware } from '@rescale/nemo'; -import { type NextRequest, NextResponse } from 'next/server'; +### Types + +#### `MiddlewareFunction` -const middlewares = { - // define your middlewares here... -}; +Can be either a legacy Next.js middleware (`NextMiddleware`) or the new middleware format (`NewMiddleware`). -// Create middlewares helper -export const middleware = createMiddleware(middlewares); +#### `MiddlewareConfig` -export const config = { - /* - * Match all paths except for: - * 1. /api/ routes - * 2. /_next/ (Next.js internals) - * 3. /_static (inside /public) - * 4. /_vercel (Vercel internals) - * 5. Static files (e.g. /favicon.ico, /sitemap.xml, /robots.txt, etc.) - */ - matcher: ['/((?!api/|_next/|_static|_vercel|[\\w-]+\\.\\w+).*)'], -}; +```typescript +Record ``` -## Matcher types +#### `MiddlewareFunctionProps` -### Simple +```typescript +interface MiddlewareFunctionProps { + request: NextRequest; + context: MiddlewareContext; + event: NextFetchEvent; + forward: (response: MiddlewareReturn) => void; +} +``` + +### Main Functions -```ts -// ... +#### `createMiddleware` -const middlewares = { - // This will match /blog route only - '/blog': blogMiddleware, - // This will match /docs route only - '/docs': docsMiddleware, -}; +```typescript +function createMiddleware( + pathMiddlewareMap: MiddlewareConfig, + globalMiddleware?: { + before?: MiddlewareFunction | MiddlewareFunction[]; + after?: MiddlewareFunction | MiddlewareFunction[]; + } +): NextMiddleware ``` -### Path +Creates a composed middleware function that: + +- Executes global "before" middleware first +- Matches URL patterns and executes corresponding middleware +- Executes global "after" middleware last +- Forwards headers and cookies between middleware functions -```ts -// ... +#### `forward` -const middlewares = { - // This will match routes starting with /blog/* - '/blog/:path*': blogMiddleware, - // This will match routes starting with /docs/* - '/docs/:path*': docsMiddleware, -}; +```typescript +function forward(response: MiddlewareReturn): void ``` -### Dynamic segments +Function that allows passing response from legacy middleware functions to the next middleware in the chain. This enables compatibility between legacy Next.js middleware and the new middleware format. -```ts -// ... +## Matchers -const middlewares = { - // This will match /blog/[slug] routes only - '/blog/[slug]': blogMiddleware, - // This will match /blog/[slug]/view routes only - '/blog/[slug]/view': blogViewMiddleware, -}; -``` +To make it easier to understand, you can check the below examples: -### RegEx +### Simple route -```ts -// ... +Matches `/dashboard` route and returns no params. -const middlewares = { - // This will match any url in /posts that's next segment is number-typed - // Example: /posts/123, but not /posts/asd - 'regex:^/posts/\\d+$': regexMiddleware, -}; +```plaintext title="Simple route" +/dashboard ``` -## Middlewares defining +### Prams -### Inline +General structure of the params is `:paramName` where `paramName` is the name of the param that will be returned in the middleware function. -```ts -// ... +#### Single -const middlewares = { - // This will match /blog route only - '/blog': async (request: NextRequest) => { - console.log('Middleware for /blog', request.nextUrl.pathname); - return NextResponse.next(); - }, -}; +Matches `/dashboard/anything` route and returns `team` param with `anything value`. + +```plaintext title="Single" +/dashboard/:team ``` -### Reference +You can also define segments in the middle of URL with is matching `/team/anything/dashboard` and returns `team` param with `anything` value. -```ts -// ... +```plaintext title="Single with suffix" +/dashboard/:team/delete +``` -const blogMiddleware = async (request: NextRequest) => { - console.log('Middleware for /blog', request.nextUrl.pathname); - return NextResponse.next(); -}; +#### Optional -const middlewares = { - // This will match /blog route only - '/blog': blogMiddleware, -}; -``` +Matches `/dashboard` and `/dashboard/anything` routes and returns `team` param with `anything` value if there is value provided in url. -### Import +```plaintext title="Optional" +/dashboard{/:team} +``` -Recommended good practice! +```plaintext title="Optional wildcard" +/dashboard{/*team} +``` -```ts -import { blogMiddleware } from '@/app/(blog)/_middleware'; +#### Wildcard -// ... +Matches `/dashboard` and `/dashboard/anything/test` routes and returns `team` param with `[anything, test]` value if there is value provided in url. -const middlewares = { - // This will match /blog route only - '/blog': blogMiddleware, -}; +```plaintext title="Wildcard" +/dashboard/*team ``` -## Middleware chaining +## Debugging tool + +To debug your matchers and params parsing you can use the following tool: + +[Rescale path-to-regexp debugger](https://www.rescale.build/tools/path-to-regexp) -This packages can intercept `NextResponse.next()` returned from middleware function to chain middlewares for same matcher. +## Usage Examples -```ts -// ... +### Basic Path-Based Middleware -const middlewares = { - // This will match /blog route only and execute both middlewares for it - '/blog': [blogMiddleware, blogSecondMiddleware], -}; +```typescript +import { createMiddleware } from '@rescale/nemo'; + +export default createMiddleware({ + '/api{/*path}': async ({ request }) => { + // Handle API routes + }, + '/protected{/*path}': async ({ request, context }) => { + // Handle protected routes + } +}); ``` -## Global middlewares +You can test your's matchers [using this tool](https://www.rescale.build/tools/path-to-regexp). -You can define global middleware that would be executed in every middleware execution in your application. -I've implemented runtime policy, so you can decide if it will be executed before/after (or both) than rest of defined middlewares. +### Using Global Middleware -```ts -// ... +```typescript +import { createMiddleware } from '@rescale/nemo'; -const globalMiddlewares = { - before: authMiddleware, - after: analyticsMiddleware, -}; +export default createMiddleware({ + '/api{/*path}': apiMiddleware, +}, +{ + before: [loggerMiddleware, authMiddleware], + after: cleanupMiddleware, +}); +``` -const middlewares = { - // define your middlewares here... -}; +### Context Sharing -// Create middlewares helper -export const middleware = createMiddleware(middlewares, globalMiddlewares); +```typescript +import { createMiddleware } from '@rescale/nemo'; -// ... +export default createMiddleware({ + '/*path': [ + async ({ context }) => { + context.set('user', { id: 1 }); + }, + async ({ context }) => { + const user = context.get('user'); + // Use the user data + } + ] +}); ``` +## Notes + +- Middleware functions are executed in order until a Response is returned +- The `context` Map is shared between all middleware functions in the chain +- Headers and cookies are automatically forwarded between middleware functions +- Supports both Next.js legacy middleware pattern and the new props-based pattern + ## Motivation I'm working with Next.js project for a few years now, after Vercel moved multiple `/**/_middleware.ts` files to a single `/middleware.ts` file, there was a unfilled gap - but just for now. From 0d32698f5d74140e8429da72d9e9ac265f716bdc Mon Sep 17 00:00:00 2001 From: Z4NR34L Date: Fri, 22 Nov 2024 13:46:23 +0100 Subject: [PATCH 2/2] chore: version bump --- .changeset/old-icons-explode.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/old-icons-explode.md diff --git a/.changeset/old-icons-explode.md b/.changeset/old-icons-explode.md new file mode 100644 index 0000000..20fa15f --- /dev/null +++ b/.changeset/old-icons-explode.md @@ -0,0 +1,5 @@ +--- +'@rescale/nemo': patch +--- + +Updated npmjs readme