├── .gitignore
├── src
├── index.ts
├── types.ts
├── use.ts
└── run-middlewares.ts
├── .prettierrc
├── tsup.config.ts
├── tsconfig.json
├── package.json
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .parcel-cache
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use';
2 | export * from './run-middlewares';
3 | export * from './types';
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "jsxSingleQuote": true,
4 | "semi": true,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "jsxBracketSameLine": false,
8 | "arrowParens": "always",
9 | "printWidth": 120
10 | }
11 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | export type Next = () => Promise;
4 |
5 | export type Middleware = (
6 | req: RequestT,
7 | res: NextApiResponse,
8 | next: Next
9 | ) => Promise;
10 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from 'tsup';
2 |
3 | const shared: Options = {
4 | entry: ['src/index.ts'],
5 | clean: true,
6 | };
7 |
8 | export default defineConfig([
9 | {
10 | ...shared,
11 | format: 'esm',
12 | target: 'node16',
13 | dts: true,
14 | },
15 | {
16 | ...shared,
17 | format: 'cjs',
18 | target: 'node14',
19 | },
20 | ]);
21 |
--------------------------------------------------------------------------------
/src/use.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 | import { runMiddlewares } from './run-middlewares';
3 | import { Middleware } from './types';
4 |
5 | export function use(...middlewares: Middleware[]) {
6 | return async function internalHandler(req: RequestT, res: NextApiResponse) {
7 | await runMiddlewares(req, res, middlewares, 0);
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2019",
4 | "lib": ["ES2020"],
5 | "module": "ES2020",
6 | "moduleResolution": "node",
7 | "outDir": "dist",
8 | "rootDir": "src",
9 | "sourceMap": true,
10 | "declaration": true,
11 | "strict": true,
12 | "noImplicitAny": true,
13 | "allowSyntheticDefaultImports": true,
14 | "isolatedModules": true,
15 | "skipLibCheck": true
16 | },
17 | "include": ["src"],
18 | "exclude": ["node_modules"]
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-api-route-middleware",
3 | "author": "kolbysisk",
4 | "version": "1.0.2",
5 | "description": "Middleware for nextjs api routes",
6 | "license": "MIT",
7 | "type": "module",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/KolbySisk/next-api-route-middleware"
11 | },
12 | "files": [
13 | "dist"
14 | ],
15 | "exports": {
16 | "require": "./dist/index.cjs",
17 | "import": "./dist/index.js"
18 | },
19 | "types": "./dist/index.d.ts",
20 | "scripts": {
21 | "build": "tsup src/index.ts"
22 | },
23 | "peerDependencies": {
24 | "next": ">=10"
25 | },
26 | "devDependencies": {
27 | "next": "^12.2.5",
28 | "tsup": "^6.2.3",
29 | "typescript": "^4.8.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/run-middlewares.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 | import { Middleware } from './types';
3 |
4 | export async function runMiddlewares(
5 | req: RequestT,
6 | res: NextApiResponse,
7 | middlewares: Middleware[],
8 | currentMiddlewareIndex: number
9 | ) {
10 | // Check if previous middleware sent a response - if it did we stop execution
11 | if (res.headersSent) return;
12 |
13 | const next = async () => {
14 | // Get next middleware, if there is one - if there isn't we stop execution
15 | const nextMiddleware = middlewares[currentMiddlewareIndex + 1];
16 | if (!nextMiddleware) return;
17 |
18 | // Recursively run next middleware
19 | await runMiddlewares(req, res, middlewares, currentMiddlewareIndex + 1);
20 | };
21 |
22 | // Initializes middleware chain - the next function will
23 | // recursively run next middleware when called by the current middleware
24 | await middlewares[currentMiddlewareIndex](req, res, next);
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Kolby Sisk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Next-api-route-middleware
3 |
4 | Middleware for Next.js api routes
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | > ⚠️ This package is for projects using Next.js Pages Router. For projects using App Router see [next-route-handler-pipe](https://github.com/KolbySisk/next-route-handler-pipe).
13 | >
14 | ## Introduction
15 |
16 | Middleware functions allows us to abstract reusable code that runs before the api handler is invoked. They have access to the `req`, `res`, and the `next` middleware function.
17 |
18 | ##### Example uses:
19 |
20 | - Add data to the `req` object _(without TypeScript complaining!)_
21 | - Reject wrong request methods
22 | - Capture errors
23 | - Validate body/query data
24 |
25 | ## Getting started
26 |
27 | ##### 1. Install'r
28 |
29 | `npm i next-api-route-middleware`
30 |
31 | ##### 2. Create your middleware
32 |
33 | Your middleware is a function that accepts `req`, `res`, and `next`. It should call `next()` when done, or send a response.
34 |
35 | ```ts
36 | export type NextApiRequestWithUser = NextApiRequest & User;
37 |
38 | export const withUser: Middleware = async (req, res, next) => {
39 | const authCookie = await getUserByCookie();
40 |
41 | if (authCookie) {
42 | req.userId = authCookie.userId;
43 | await next();
44 | } else {
45 | res.status(401).send({ message: 'Invalid auth cookie.' });
46 | }
47 | };
48 | ```
49 |
50 | ##### 3. Export the `use` function. Include an array of middlewares in the order you want them to execute, along with your handler as the last item in the array.
51 |
52 | ```ts
53 | import { use } from 'next-api-route-middleware';
54 |
55 | const handler = async (req: NextApiRequestWithUser, res: NextApiResponse) => {
56 | res.status(200).json({ userId: req.userId });
57 | };
58 |
59 | export default use(captureErrors, allowMethods(['GET']), addhUser, handler);
60 | ```
61 |
62 | ## Examples
63 |
64 | #### addUser
65 |
66 | You can add data to the `req` object, and it will be available in your handler. In this example we get a userId from an http cookie, if the cookie isn't valid we return a 401.
67 |
68 | ```ts
69 | export const addUser: Middleware = async (req, res, next) => {
70 | const authCookie = await getUserByCookie();
71 |
72 | if (authCookie) {
73 | req.userId = authCookie.userId;
74 | await next();
75 | } else {
76 | res.status(401).send({ message: 'Invalid auth cookie.' });
77 | }
78 | };
79 | ```
80 |
81 | #### allowMethods
82 |
83 | You may find that you need to add args to a middleware. To achieve this we make use of a factory pattern. The `allowMethods` function bellow accepts an array of allowed methods, and returns a middleware. We can make use of this factory by calling the function: `allowMethods(['GET', 'POST'])`
84 |
85 | ```ts
86 | import { Middleware } from 'next-api-route-middleware';
87 |
88 | export const allowMethods = (allowedMethods: string[]): Middleware => {
89 | return async function (req, res, next) {
90 | if (allowedMethods.includes(req.method!) || req.method == 'OPTIONS') {
91 | await next();
92 | } else {
93 | res.status(405).send({ message: 'Method not allowed.' });
94 | }
95 | };
96 | };
97 | ```
98 |
99 | #### captureErrors
100 |
101 | We can also perform actions with inner middleware functions. In this example we wrap the inner middleware functions in a try catch, allowing us to catch any errors that bubble up.
102 |
103 | ```ts
104 | import { Middleware } from 'next-api-route-middleware';
105 |
106 | export const captureErrors: Middleware = async (req, res, next) => {
107 | try {
108 | await next();
109 | } catch (error) {
110 | console.error(error);
111 | res.status(500).send({ message: 'Server error!' });
112 | }
113 | };
114 | ```
115 |
--------------------------------------------------------------------------------