Skip to content

Commit

Permalink
Merge pull request #125
Browse files Browse the repository at this point in the history
dx-update
  • Loading branch information
z4nr34l authored Nov 22, 2024
2 parents c4c4735 + 0d32698 commit bc3b195
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 116 deletions.
5 changes: 5 additions & 0 deletions .changeset/old-icons-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rescale/nemo': patch
---

Updated npmjs readme
244 changes: 128 additions & 116 deletions packages/nemo/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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<string, MiddlewareFunction | MiddlewareFunction[]>
```

## 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.
Expand Down

0 comments on commit bc3b195

Please sign in to comment.