Skip to content

Commit

Permalink
Merge pull request #7 from rawnly/f/6-before-all-middleware
Browse files Browse the repository at this point in the history
beforeAll middleware
  • Loading branch information
rawnly authored Oct 18, 2023
2 parents e1c4f42 + 0b104dd commit c1a0e42
Show file tree
Hide file tree
Showing 24 changed files with 2,024 additions and 657 deletions.
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ export default handlePaths(
],
{
// inject custom data, like session etc
// it will be available inside `req.injected`
injector: async req => ({ isLoggedIn: !!req.session }),
// it will be available inside `req.ctx`
context: async req => ({ isLoggedIn: !!req.session }),
debug: process.env.NODE_ENV !== "production",
}
);
Expand Down Expand Up @@ -155,18 +155,18 @@ export default handlePaths(
// auth guard
path: "/dashboard/:path*",
pre: req =>
req.injected?.session ? true : { redirectTo: "/auth/sign-in" },
handler: req => {
console.log("User authenticated: ", req.injected?.session);
req.ctx?.session ? true : { redirectTo: "/auth/sign-in" },
handler: (req, res) => {
console.log("User authenticated: ", req.ctx?.session);

// do your stuff here

return NextResponse.next();
return res;
},
},
],
{
injector: async req => {
// this injects `session` property into the request object
context: async req => {
const session = await getSession(req);

return { session };
Expand All @@ -175,6 +175,42 @@ export default handlePaths(
);
```

### Options

You can pass several options to configure your middleware

```ts
interface WayfinderOptions<T> {
debug?: boolean;

/**
*
* A function that returns the data to be injected into the request
*/
context?: <T>((request: NextRequest) => T) | T;

/**
* Global middleware to be executed before all other middlewares
* Useful if you want to set a cookie or apply some logic before each request
* It receives the `options.response` (or `NextResponse.next()` if not provided) and `NextRequest` as params
*/
beforeAll?: (request: NextRequest, response: NextResponse) => Promise<NextResponse> | NextResponse;

/**
*
* A function to extract `hostname` and `pathname` from `NextRequest`
*/
parser?: RequestParser;

/**
* The response to be used.
* Useful when you want to chain other middlewares or return a custom response
* Default to `NextResponse.next()`
*/
response?: NextResponse | ((request: NextRequest) => NextResponse);
}
```

## Authors

This library is created by [Federico Vitale](https://untitled.dev) - ([@rawnly](https://github.com/rawnly))
Expand Down
3 changes: 0 additions & 3 deletions docs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ const withNextra = require("nextra")({
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
appDir: true,
},
};

module.exports = withNextra(nextConfig);
8 changes: 4 additions & 4 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
"lint": "next lint"
},
"dependencies": {
"@types/node": "18.16.3",
"@types/node": "20.8.7",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@vercel/analytics": "^1.0.0",
"autoprefixer": "10.4.14",
"eslint": "^8.26.0",
"eslint-config-next": "13.3.4",
"next": "13.3.4",
"nextra": "^2.4.2",
"nextra-theme-docs": "^2.4.2",
"next": "13.5.5",
"nextra": "^2.13.2",
"nextra-theme-docs": "^2.13.2",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/examples/supabase-authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default handlePaths(
},
],
{
injector: async req => {
context: async req => {
const session = await getSession(req);

return { session };
Expand Down
5 changes: 4 additions & 1 deletion docs/src/pages/guide/_meta.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"getting-started": "Getting Started"
"getting-started": "Getting Started",
"middlewares": "Middlewares",
"hostname-checks": "Hostname Checks",
"advanced-options": "Advanced Options"
}
43 changes: 43 additions & 0 deletions docs/src/pages/guide/advanced-options.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Callout } from 'nextra-theme-docs'

# Advanced Options
Next Wayfinder provides some advanced options to help you customize the behavior of your middlewares.

```ts
interface WayfinderOptions<T> {
/**
*
* Enables debug logs
*/
debug?: boolean;

/**
*
* A function that returns the data to be injected into the request
*/
context?: T | RequestInjector<T>;

/**
* Global middleware to be executed before all other middlewares
* Useful if you want to set a cookie or apply some logic before each request
*/
beforeAll?: BeforeAllMiddleware;

/**
*
* A function to extract `hostname` and `pathname` from `NextRequest`
*/
parser?: RequestParser;

/**
* The response to be used.
* Useful when you want to chain other middlewares or return a custom response
* Default to `NextResponse.next()`
*/
response?: NextResponse;
}
```




5 changes: 5 additions & 0 deletions docs/src/pages/guide/advanced-options/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"before-all": "beforeAll",
"context": "context",
"response": "response"
}
69 changes: 69 additions & 0 deletions docs/src/pages/guide/advanced-options/before-all.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Callout } from 'nextra-theme-docs'

# `beforeAll`

This callback is executed before each middleware. It can be used to write cookies or execute some custom logic.
It must return a valid `NextResponse` instance.

<Callout type="info">
Note that you must use the `res` param inside your middlewares to access the "mapped" version from `beforeAll`
</Callout>

#### Good Usage
```ts copy showLineNumbers filename="middleware.ts" {5,11}
export default handlePaths([
{
path: "/a/b/:path*",
handler: (req, res) => {
console.log(res.cookies.get('my-cookie')?.value) // => '123'
return res
}
}
], {
beforeAll: (req, res) => {
res.cookies.set('my-cookie', 123.toString())
return res
}
})
```

#### Bad Usage
```ts copy showLineNumbers filename="middleware.ts" {5-6,12}
export default handlePaths([
{
path: "/a/b/:path*",
handler: (req) => {
const res = NextResponse.next()
console.log(res.cookies.get('my-cookie')?.value) // => undefined
return res
}
}
], {
beforeAll: (req, res) => {
response.cookies.set('my-cookie', 123.toString())
return res
}
})
```

#### Example with redirect
```ts copy showLineNumbers filename="middleware.ts" {7-8,10}
export default handlePaths([
{
path: "/old-path",
handler: (req, res) => {
console.log(res.cookies.get('my-cookie')?.value) // => '123'

const url = req.nextUrl.clone()
url.pathname = "/new-path"

return NextResponse.redirect(url, res)
}
}
], {
beforeAll: (req, res) => {
response.cookies.set('my-cookie', 123.toString())
return res
}
})
```
31 changes: 31 additions & 0 deletions docs/src/pages/guide/advanced-options/context.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Context

The `context` property can be an object or a function that can used to pass properties into our request.
For example you can provide a session object or token with the user data.

#### Example
```ts copy showLineNumbers filename="middleware.ts"
import { type Session, getSession } from '@acme/auth'

interface Context {
session: Session
}

export default handlePaths([
{
path: "/protected",
pre: req => !!req.ctx?.session || {
redirectTo: '/auth/sign-in'
},
handler: (_, res) => res
}
], {
context: async request => {
const session = await getSession(request);

return {
session
}
}
})
```
50 changes: 50 additions & 0 deletions docs/src/pages/guide/advanced-options/response.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# response
This is were the first instance of `NextResponse` is intiated.
Default value is `NextResponse.next()` but you can customize it and even provide a function to generate it.

## Callbacks Order
1. The response journey starts here.
2. It's passed through the `beforeAll` callback
3. If available goes through the `beforeAll` callback of the hostname middleware
4. The actual handler if exists or the default one

```ts
// step 1
function responseFactory() : NextResponse {
const res = NextResponse.next()

// whatever
res.cookies.set('appVersion', '0.0.1')


return res
}

export default handlePaths([
{
hostname: /app\./,
// step 3
beforeAll: (req, res) => {
// do checks or whatever
return res
},
handler: [
{
path: '/:path*',
// step 4
handler: (req, res) => {
// do stuff
return res
}
}
]
}
], {
response: responseFactory,
// step 2
beforeAll: (req, res) => {
res.cookies.set('my-cookie', 123.toString())
return res
}
})
```
20 changes: 3 additions & 17 deletions docs/src/pages/guide/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,9 @@ import { Steps, Tabs, Tab } from 'nextra-theme-docs'
<Steps>

### Installation
<Tabs items={["npm", "pnpm", "yarn"]}>
<Tab>
```sh
npm install next-wayfinder
```
</Tab>
<Tab>
```sh
pnpm add next-wayfinder
```
</Tab>
<Tab>
```sh
yarn add next-wayfinder
```
</Tab>
</Tabs>
```sh npm2yarn
npm i next-wayfinder
```

### Initialize
`next-wayfinder` exports an `handlePaths` function that can be used as middleware entry point.
Expand Down
3 changes: 2 additions & 1 deletion docs/src/pages/guide/hostname-checks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export default handlePaths([
handler: req =>
NextResponse.redirect(new URL("/dashboard", req.url)),
},
/* This is the default behaviour */
{
path: "/:path*",
handler: () => NextResponse.next(),
handler: (req, res) => res,
},
],
},
Expand Down
Loading

1 comment on commit c1a0e42

@vercel
Copy link

@vercel vercel bot commented on c1a0e42 Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.