{"_id":"next-smoothie","_rev":"47-67f5fdd4d352590b76e22869cb737276","name":"next-smoothie","dist-tags":{"latest":"0.4.0","beta":"0.1.11-beta.0"},"versions":{"0.0.5":{"name":"next-smoothie","version":"0.0.5","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-prepatch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-preminor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-premajor":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.0.5","gitHead":"9ffa1cb455c247a5bb96c82de97487bbf446e86d","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-K5hgEZNZmG2T6Wap2ZFTsVYW/4Saq/WDi5H2paimjKW9YDCQaydK5PWpvHs5Pau/+fXFD0GecVEy1d4HjYXwxg==","shasum":"a42819deffa04ebc13a73fdfd17aae2541eb4cf2","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.0.5.tgz","fileCount":15,"unpackedSize":49294,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD52cRrJbk/IgrhxuNABD4fIAOUiwui1F5wMB5Hj93xXwIhAKtuFNwsOOeFuoCV5MR25UjBi8qMl1y2ohB/BDKi+G8e"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.0.5_1699701069618_0.33478823428996196"},"_hasShrinkwrap":false},"0.0.6":{"name":"next-smoothie","version":"0.0.6","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-prepatch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-preminor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-premajor":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.0.6","gitHead":"47f8d19286c4aa5dd6dbdd4f3cc60054cbf72b09","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-AS/HLnElMDv+HBLxYk2PaiEwXokpAXu7AQpl+Z2miTn2c0Fn6ZfcZyYSpQ16SJdgldaxTzjN28J+OjM58h3lSg==","shasum":"1193067dc32ade1ecde727967007807081b920bc","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.0.6.tgz","fileCount":15,"unpackedSize":49293,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHoJdyTbXKJhAU89hat627LMsq+4YLcwbgiDoa0+wY31AiEA+czxIT0XC4FmgG9gnkKOaGBpMJBsAQsdLnmWtwIsufI="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.0.6_1699701104639_0.6735410325350424"},"_hasShrinkwrap":false},"0.1.0":{"name":"next-smoothie","version":"0.1.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-prepatch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-preminor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-premajor":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.0","gitHead":"7fe849284cd017b06a05667c1576604c905e3553","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-1Ay+RSoVuPePeuI108/8JwZxnVsXLhAC2NZVAQyY3eGxFm3xdNA3oU2R8Z5aqVrVF47XVmammE3Ev18ZVyFQlQ==","shasum":"c7f6a18a216b13fe0937ff02078a4ce5b968dcfe","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.0.tgz","fileCount":16,"unpackedSize":50252,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIB9eRRiW4jn318fUMThn5BcZcDoke1wHkm5YdLmKoEbUAiBHdkGlH4xsyW9ayRlyV83MemEW5ADDc9VR6lh65b5Ldg=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.0_1699707683665_0.040683408489786244"},"_hasShrinkwrap":false},"0.1.1":{"name":"next-smoothie","version":"0.1.1","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-prepatch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-preminor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-premajor":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.1","gitHead":"6b727bd3441e2eb2954568693e83904b9f05437f","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-ZiF3qJKH48GcxSV59sGi6GAXLc5YAz5wePHQqB3B5qv4U1M/XsdijbgxU9FM4qFHFJdcTkIK7OAvPNQFAc5Xmw==","shasum":"0dded686441c2c6b0daf086072c11fc8d02754f7","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.1.tgz","fileCount":16,"unpackedSize":50195,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGN81E4FyjtiIsj4zc965oVxTSUpXzbqNOd7R5FwVRgwAiEAnan0hk8fN/SnXo+QkRkSozcFAwJxAKZCYGODDTM782s="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.1_1699707852012_0.42704467924307465"},"_hasShrinkwrap":false},"0.1.2-beta.0":{"name":"next-smoothie","version":"0.1.2-beta.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.2-beta.0","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"8a54f01fd1c9fc72dbe37a4659d363467daa65d4","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-qooxIczM1gxIjrcxdCXEjgX+ufwCAlX4v4RXa2ACQ0XiumjVwn18qSrQahtYf34VEhf8RI0v0dqQTbV8nNYABA==","shasum":"086cc16ee161563e3035df75aa979d2f15cca161","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.2-beta.0.tgz","fileCount":16,"unpackedSize":53225,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBnSCR5HcfUHZCVztOdByOgaS+DrIUGeCsyCJxZMF9x4AiEAs7936yAax7+2m4mWZDaryxERP1LtJJEu6UroJV39+hI="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.2-beta.0_1699869553637_0.1979719569695142"},"_hasShrinkwrap":false},"0.1.3-beta.0":{"name":"next-smoothie","version":"0.1.3-beta.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.0","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"a7a3f1fe90478edebdd6dea96cd78f7d003585c6","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-SzHNwbOXrlogpAneWHfqHmXLxpEZ2n/P3ELTtwR+XUApa/mKs68P8ItmYW4decQ18yk4T/VUK0Y/adSJtxFbLg==","shasum":"a0d67cd603ec25ae7704418028cb8e7eb3724086","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.0.tgz","fileCount":16,"unpackedSize":53182,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDasgxe6tOlhkd6lO+7F9x0wK/h+s5hY4EheVdU893ofAIgCTQ2x7wRsaHscxql6+ngVa/asxXMppz1wWmw0FTa4dE="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.0_1699909784077_0.9595879218167465"},"_hasShrinkwrap":false},"0.1.3-beta.1":{"name":"next-smoothie","version":"0.1.3-beta.1","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.1","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"d61763da3871ce8951b4ee75a4b123651de8505b","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-SQSKgVcg+TdxmgDjy5xbiHOn2VxqS+f0TnL9WpBXJkJxEtMCUAbBr689FVqtSOXOnaU/ygJTFX040FL1fZ1QCg==","shasum":"71efeb22b30699f1a9fc5f8bdf91347962f13961","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.1.tgz","fileCount":16,"unpackedSize":53316,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDspb/x9mE4pDKn2t8/20BzuaeEmJkn8V3addWrQfSYmgIgRmgaM1nk/9zst3Q/OJE5hj4o+HwHiS5AgPechCHenU8="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.1_1699909894534_0.10630866880238532"},"_hasShrinkwrap":false},"0.1.3-beta.2":{"name":"next-smoothie","version":"0.1.3-beta.2","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.2","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"09c75dc8e8e9a770a3ea3a8c6ea661ab4cca0145","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-ON58RJMQIH6g+1s2hveElBm8qUZ/IiH2Ipbrc7CEgYZe4GBtdp35pgHM4t/9PgXmSn5YdKjb1rbrhcimwv/aLA==","shasum":"2e1ada5d12ff6365712cfae28ec10c9dd7b408b2","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.2.tgz","fileCount":16,"unpackedSize":54224,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFLZBA4B9kjHjxQQ7+AWipDCdOn6CeZTZo/6//3kQhgkAiEA2g9oU3YieDbSSdaV87qFED7BEiqbeeUFuWib8ozunwc="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.2_1699965663001_0.07155969775295512"},"_hasShrinkwrap":false},"0.1.3-beta.3":{"name":"next-smoothie","version":"0.1.3-beta.3","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.3","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"e487def60ccd5d2e39fe8a04f8e0e4ece7caaa3d","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-ZGSnyiIRYM/wtcrpZpcpKHcakoyuhRyvQIws/T0bqyPY9JG6NQoguf/xtw8Su0jz7qVW5tjeWczyOg7KwlMpYQ==","shasum":"97a5d5e9a9b1212fa687fb39f4b4316e07b39131","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.3.tgz","fileCount":16,"unpackedSize":54234,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGJpeVxk5Eoc6k7WwV+uSh5P7RZxsH7VDjp0YyC9f844AiEA+XzU1S5MCfOT+p09JOOtI9KiRS7DLAjxIHezsIE42X8="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.3_1699967555776_0.7583245971737229"},"_hasShrinkwrap":false},"0.1.3-beta.4":{"name":"next-smoothie","version":"0.1.3-beta.4","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.4","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"1a2d9a6eb7c191100126520114a2680f7121ed7f","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-fZO0TFBbMNcSi1fQXAXkEPgK199Lgl3dQaqNj9EdFhDnuMnHYiRkvTkr7kJEI6NfMuOeWqZfYacYgyEbUeeX6w==","shasum":"5d4a5284b1f313b5f002555ae90a88de43ea86bb","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.4.tgz","fileCount":16,"unpackedSize":54793,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC0F2sz71L68SWzzbb7CxCdHURiJhoIomAAVgdy+8xcqAIgUvlHpSKmPqz0hk47P43+OxMaN+WGP2Q5H3bWQLU6Ces="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.4_1700053357753_0.9554766082633077"},"_hasShrinkwrap":false},"0.1.3-beta.5":{"name":"next-smoothie","version":"0.1.3-beta.5","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3-beta.5","readme":"<p align=\"center\">\n<img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n\n<picture>\n  <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n  <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n  <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n</picture>\n\n</p>\n\n\n<p align=\"center\">\n<strong>Next.js REST API library for scalable full-stack applications</strong>\n<br />\n<em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nInstall: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n\n\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response`, `undefined` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    redirect('/foo');\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return null;\n  }\n\n  @get('e')\n  static getE() {\n    return { hello: 'world' };\n  }\n}\n```\n\nThe routes A, B, C respond with result as is because they return either `Response` or `undefined`, and routes D and E serialise the returned custom data (at this case it's `null` and a custom object) and send it to the client automatically. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(typeof result === 'undefined' || result instanceof Response) {\n    return result;\n  }\n\n  // D, E\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"b7e2a0a15e0be757799d716343f20b2982114281","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-xbVW1X410aM3uiJgqK619FTruldFqiPKqkBljRHbG1XUTKLG5j5xggQtmAzdVKAbSie7cGjlCQqlYxHw/5WZtg==","shasum":"81b4f2ede707cb673e6e60dc62d0591e7798fb32","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3-beta.5.tgz","fileCount":16,"unpackedSize":55002,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEyyl0SOKosydboaiyY7pPdPJ1fM7fa1nuE+ZaFHgKo9AiB4zIDGqMuOmNQV+yOjUUJbj1hqzdv7A5kI4tgYcN1SPw=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3-beta.5_1700071134596_0.894604112165257"},"_hasShrinkwrap":false},"0.1.3":{"name":"next-smoothie","version":"0.1.3","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.3","gitHead":"ca9b179590c7d6369c95755da2799cc40ac1b4c8","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-7rDP18ApcEJspFCJBuGaYW2A5h4y9NOdz+qU91FGVLp+bbH5aLX5qzYLvNpGgz0bJ5RjMglg1chDvMpnkAZcAw==","shasum":"75f42f896f0b057ec1183b2031cbb256d197e983","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.3.tgz","fileCount":16,"unpackedSize":54886,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGDshRVsNBOpDFXEUj/qQTYywCd2Mnc6E3jWuazvx3NLAiAjKU/G3hmz4vaA+fy7RJ36rPkvEzxnWAL2UhR/UT9zaA=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.3_1700327850434_0.4035430531071449"},"_hasShrinkwrap":false},"0.1.4":{"name":"next-smoothie","version":"0.1.4","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.4","gitHead":"9df59c7e77a630ef90799d5ddde94155c3007ff1","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-ZVs6lvlmhUJUyjwOwXpvYMN6FsYpHTxYTyZmyeHm2qd3SnLVT9M6fIgraIZiT8Kbm4+yoHweIS+KCegAmBWK3Q==","shasum":"a0af329aaa7e5143542953c9f28de67e990094c8","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.4.tgz","fileCount":16,"unpackedSize":55272,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCRh08Te4nn904AbyQHPuQYvIZKhJmyuAh9FqT6hKzW0AIgJYnzBKZZapqxsY6t/J0IS4ANGe9vKbVO2Eklhqndujg="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.4_1700328389722_0.07603387415542118"},"_hasShrinkwrap":false},"0.1.5-beta.0":{"name":"next-smoothie","version":"0.1.5-beta.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -r dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.5-beta.0","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"6d9a1c2b19ee1fd8a53bec718ace6065b5822806","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-LpvRkYP6vjXT2Yy4wttJYg3f8f4XV0cWpw/A1o1HXEUVZMW7FeHtRX56NLPFU01QRSY2mROIDuCxn90WcxKUwg==","shasum":"d0db392b929b34add4103643d477d9ef48f63dc0","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.5-beta.0.tgz","fileCount":16,"unpackedSize":56527,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC42x2WhbN98pS6IEL1jeDyFn3gZlNF+t4EuW7wBVsmFgIgQ620P/fxvWWek36pFUZ1FRsr8K+hHCQ1fAy9IMVVaMM="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.5-beta.0_1700490412886_0.2642190954473318"},"_hasShrinkwrap":false},"0.1.5-beta.1":{"name":"next-smoothie","version":"0.1.5-beta.1","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.5-beta.1","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"a81bd50bece2cfc91f0bac4bc462cfcc2ffe9346","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-SivEtaJSs9NBGc3k4FmbJfPUpt8V4aMDN/5ACMnUnv4C5dLYrTedDDkk12563hsFxgtd+qktPKMU0oXlug7Lsg==","shasum":"05983052be0453a70e82c5efbe38c8ce9294a4ea","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.5-beta.1.tgz","fileCount":22,"unpackedSize":63418,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHPRizqXnFKDkiu4S06aY+T2iCJcJZcfidP7F9mzwOYoAiEA4EG6AXLKD5jVI5DJe4BNmvD96Hi9FMkHetrsu3PX0eU="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.5-beta.1_1700597352866_0.21475340942182664"},"_hasShrinkwrap":false},"0.1.5-beta.2":{"name":"next-smoothie","version":"0.1.5-beta.2","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.5-beta.2","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"f0021dbdea9d0ad51553c3bb228cd4280095a362","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-AnlIFTshCRc6QpmzHhKed1wbEgNNXQLJNSTYydoF3MrPIL5lfRXOkkogYBsZgAcMAZitS7CjAE9JnoHiqA7Ezg==","shasum":"4b4dd5a4a80e356ca03a275decf72d280f4a1bc7","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.5-beta.2.tgz","fileCount":11,"unpackedSize":35573,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFxSRavwg4stNIQ2/+H/Iu9oZRbjZ8OSJcZNA9e6vIpgAiB2inPZu1y2JCKmq7yeNwZQawo8rHFK/w/M8m4oLewnvA=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.5-beta.2_1700600341242_0.6013185753375199"},"_hasShrinkwrap":false},"0.1.5-beta.3":{"name":"next-smoothie","version":"0.1.5-beta.3","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.5-beta.3","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"b56945301496d027750a82c6329c6afed9cee276","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-5ct1aNjPykDIzdQdnhOEMcr2EheyufJTYIQlB67aXhv1uWU9bQHQo1xA8d6gSN/JPvXXyr8O37/5+Bj6p/XVAw==","shasum":"c814c3a14fc05074b0e60e1d1c37e0fa0e47acbf","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.5-beta.3.tgz","fileCount":21,"unpackedSize":62398,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDguASHkhf5udSlVguKdLdciNBQ5gXF9b4i8eFZ1c5u1AIgWP0yiNdFtPpY0vZnJsHp+BDmUolmrNwvxGF/D8bnLNo="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.5-beta.3_1700600934631_0.1463714228302191"},"_hasShrinkwrap":false},"0.1.5":{"name":"next-smoothie","version":"0.1.5","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","postinstall":"patch-package"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.5","gitHead":"4d3cb4fd9dd920cae912ca9f181bdce39844fdf9","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-f1uUwwsz84YSYbEjOPJScxZ1AwmTqvRM2euvyCWlwXkSyg7dP3LPbP5QdsnjBQftRA4yfNpLc+jejAQjxSv33Q==","shasum":"f86b42b287a94dfa1fb9e224247c892be5ccdb3f","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.5.tgz","fileCount":2181,"unpackedSize":11084473,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAYFzYqckpraJvmAPoz4WAesL83pCiB3JwKC5kM3+PSYAiARPZhjrErrlPzFtMFEK6M/fydRaoa73cQ5vIwm3f0L9Q=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.5_1700603800898_0.006483134232131649"},"_hasShrinkwrap":false},"0.1.6":{"name":"next-smoothie","version":"0.1.6","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","postinstall":"patch-package"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.6","gitHead":"322e5c4b57073feb32172c37e2d98aab1ca0b380","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-1YDfwIVB+9VbpjyppvRf4fnVB8+voq5XzmdNQdeDxrKznorhLqsEoc0X0USItFGlZgxkMSTEnVXY+Gfn9YSXKw==","shasum":"cb69a0935cde68939f708bd5d79ed7caa7a5a122","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.6.tgz","fileCount":24,"unpackedSize":63618,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFvaI98goaRlv8pcaerYwJGKSwKWdpGH4IMi8U5UoGroAiEA3+Uzma4pIr8nJv4KmCpv4meE/evGv85aq8dfKruA6tc="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.6_1700603884215_0.307594092984498"},"_hasShrinkwrap":false},"0.1.7":{"name":"next-smoothie","version":"0.1.7","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","postinstall":"patch-package"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","patch-package":"^8.0.0","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.7","gitHead":"450dc47da4fb307345ab58da688ba977b94d9e94","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-mvO62IDjPQkPw2NsBGeXn3Lv8JDQtC+5gxIV/qxSqqb+VPEiyyIHcnbIdkYgEUzCXi3QVJgf75Wj6YXDJ439Rg==","shasum":"efb7ed6ac1147eab5b56d76bb1c299b59afdde95","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.7.tgz","fileCount":24,"unpackedSize":63713,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDgCExGSd7e5ituak+l+P/Jb1A4manB8S1yPrsXwwmTegIgeRqWLyQXcwanwpsqyj1eJLgkbezkSf4G126Y0yuZVoY="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.7_1700604224510_0.5436376006838577"},"_hasShrinkwrap":false},"0.1.8-beta.0":{"name":"next-smoothie","version":"0.1.8-beta.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"dist/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist client-lib/dist client && npm run toc && tsc && tsc --project client-lib/tsconfig.json && cp -R client-lib/dist/client-lib/src client","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client-lib/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish --tag beta && git push && git push --tags","postinstall":"patch-package"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","patch-package":"^8.0.0","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.0","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"21d7f571bcaab103919611c0d8f1972b4e261d98","types":"./dist/index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-qLQ2GJgXuvDXegVPtw0bFfi2yvXYe6POSHJnRVIOwowl+TNBKJ8gjez7/cw+mnm+ESQFBMRK64ip9p+dqs3D+A==","shasum":"19ffa05ea756d0c96562bae06734bffe4fd5dee4","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.0.tgz","fileCount":53,"unpackedSize":172746,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDCft3bC/CFuDnFC8xcNctBsaNJJWn1E5syymidRnkVyQIgVvVCHJRLzdqj0NwrMCUXDaDxLtq7RBHJEVaktiyHJ4M="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.0_1700604277707_0.31266274757847223"},"_hasShrinkwrap":false},"0.1.8-beta.3":{"name":"next-smoothie","version":"0.1.8-beta.3","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.3","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"6a87c29fc0072e37ae6754f3482316205648418e","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-3hGJokrzxqz7q1F01UFl15/sPeNONjEKO6iA6QUclRqYzoExG7QtnhZof3cJfdQoOQmJK+OlTUWiJs6J1VgAEw==","shasum":"d56d4e9e5485615e31639838f1787a3d74ce1be9","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.3.tgz","fileCount":21,"unpackedSize":119498,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC+AyRp0faweyxKuYl/H8uXsg3K1eZ2LYXn5xGy5lnDQQIhAMbMt92yXB3+2HZdnPIroDb/46hKTBJY1gdHJOt5YuNj"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.3_1700648233065_0.20330974913106403"},"_hasShrinkwrap":false},"0.1.8-beta.4":{"name":"next-smoothie","version":"0.1.8-beta.4","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.4","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"45dab4f4978b7844e4e181d8b438b712fcd87e06","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-fvhW9YP23Hk6GNpN3OJkwBOtm1e85pstqnTdyamr1XqZxydVBSieuuUyiGlh87smC+zcL4I4rTn/RZptY4b6Uw==","shasum":"2225fa8eb0859727fcd2ea7afeef3df92c7a6ed7","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.4.tgz","fileCount":21,"unpackedSize":119501,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAqW63GzMB/NOhztW3pyqZn7CwttFSfvdNtR+wwhc03YAiEAl13QidPoetyoXzHVyBVSmlmI+ZsvG52gKoXo+V8fa24="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.4_1700648291494_0.7622448295640472"},"_hasShrinkwrap":false},"0.1.8-beta.5":{"name":"next-smoothie","version":"0.1.8-beta.5","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.5","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"3e909fc3d044866a4caec517bca01e04ad9ad9f6","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-xboXCEPblxFmzI2GiWPhheRfXnEc2CfREildV1SUdj0jTDWVxH4Bboy+nGlc5XuIHVqwLv1YLNM9L+cYCFTelg==","shasum":"3b11de2750b1a5bd7ce5b4d34bc1fa484b9e083c","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.5.tgz","fileCount":21,"unpackedSize":119740,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDbpA3ub8K++4bD7u0zmn6mQEWw8fG1Ec5bxQJtt2+nAQIgD5VQKen2ywIcdoQVPo+hJaWG4BGpTQp+6OI9xABsmxo="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.5_1700648694461_0.15543396970615886"},"_hasShrinkwrap":false},"0.1.8-beta.6":{"name":"next-smoothie","version":"0.1.8-beta.6","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.6","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"90ac1a0e91f708ae253bb40695667e717081b555","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-MbSa9e4WI8ZWOMPN7yZFCffP3r1aAJsza64bSKt5mvtoaMJ6aWT3TqsxNjpuou7DvqKL8UjopogTMr+oId8hMg==","shasum":"35d530ff76bef2f47c1d63558e94cc47645276cf","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.6.tgz","fileCount":23,"unpackedSize":123755,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDUdjHRYEvRRpUYWWKvkA04gtXs4I6eQOYuq17tbQxewgIhANH0zNPO2hVk/SmnJ2iS+Mkj8qptcMnWRjkPXz3tq2rZ"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.6_1700744777516_0.16951188870089018"},"_hasShrinkwrap":false},"0.1.8-beta.7":{"name":"next-smoothie","version":"0.1.8-beta.7","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.6","@types/supertest":"^2.0.15","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.45.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","next":"^13.4.10","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.2.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.7","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"8dc3cda28cbce31e020bc2439b8ebb1d0777e66b","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-l+D9pm3eY87+XOLeuVbdmhBJibi1G3Ihu5YwW0BpHzLYatKPq3CT6dtCBHZKIGQxDiCR7XE85joe/TGVcL166g==","shasum":"aaa009b0f68322494531185f2729a2f71c85372a","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.7.tgz","fileCount":23,"unpackedSize":123557,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDMJ/++v/4P7F6/zDzDfe5sWT9nLevJVNZtjCTJ3OM/8AIgIT2UszTA2UaYjCjTIs4tjJartjZ7NgWs1Lz1bcFLov8="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.7_1700749389971_0.3095231947981092"},"_hasShrinkwrap":false},"0.1.8-beta.8":{"name":"next-smoothie","version":"0.1.8-beta.8","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.12.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.8","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"e01c4b8593a6693da74a0c6b40803e938e5b6a00","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-pVCC3qP/rtUz6EO38SiY5HDOJW35ACyx8URdvHsUffxJby7vgXTvv0S8sUuC/HQVqypNNLVMaf9BhrEisHfcpQ==","shasum":"dc6b8a3498744a446b43fa7b99734a5d8c4be89d","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.8.tgz","fileCount":23,"unpackedSize":133405,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHcAMF6+lDUYc4zAgSUMUo3XPk/Ggb9an0Y9XPb4P0IbAiBVxqNJCI+sGg8eQrx+2yzdtwq6WFt1C+Bi3onnAwloQg=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.8_1700766716809_0.8806100716932344"},"_hasShrinkwrap":false},"0.1.8-beta.9":{"name":"next-smoothie","version":"0.1.8-beta.9","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.12.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8-beta.9","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"b5036b7594eb510035c7f8a541e5759ad3e152d2","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-GZ91mrV55LrImo7yFRirUmS+AFS2YS+FqDlkNai5LePDGaicAsIvyCo0nirofSEmldZ9FGm0jE1q8aqi93iF2A==","shasum":"d0da1274a4106885abf748b715e0d7ce00626436","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8-beta.9.tgz","fileCount":23,"unpackedSize":133548,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAzzgXHe8ACbyx6cQ/l/JEmTSd64lU8pbrlaQnHPMqAsAiAg5F1HvP+9Hv6eU/Ow2q2OawS768uaDyjbxxSURCBOwQ=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8-beta.9_1700767106288_0.005449108707043848"},"_hasShrinkwrap":false},"0.1.8":{"name":"next-smoothie","version":"0.1.8","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.12.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.8","gitHead":"31c6750d9f347f70e2fe1a89a15f75a42bf01b27","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-yzt02zfOJ7ADyHVmgK6xk7iPj2XBIR6tKVDycw+4hp0TaMH1hrB9P7MphjfIIMlsZ2v4A5FfN+D73hcZU4FbTw==","shasum":"7e63f0f41c4e3894e606e9be8ea65602fdeecf39","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.8.tgz","fileCount":23,"unpackedSize":133541,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDDYOIfkQxWKgOHRnrQZik1NknWh9VW415+cleNaXcfMwIgRXCO4j77HtxnprqbS8fThgsOHWpsDU67LWvM6gRNbv0="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.8_1700770427047_0.8332860542608669"},"_hasShrinkwrap":false},"0.1.9":{"name":"next-smoothie","version":"0.1.9","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.12.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.9","gitHead":"8e3d0a0ba36757667f36898a31c7282c396edc40","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-b4UZTUunUdXCTHVaFBg2N+uN7CyeEsrrlIPZd5Hdl1aqBamUAeNwQz+ayErx7es0XhgursAe4OKA6fX7jLLvLw==","shasum":"d0aad7867b9fa0df9a484d35d1629955804a5896","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.9.tgz","fileCount":23,"unpackedSize":133529,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGdsS8ihK28ipdwv/l3EDVbNtRNIS6d6B0p+eybfbAoHAiEA7vPe31rnnAnMt4hc7EtrX5Vf3ktUTjCZWZbmGo0klus="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.9_1700771178355_0.04668215966710387"},"_hasShrinkwrap":false},"0.1.10":{"name":"next-smoothie","version":"0.1.10","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.12.0","@typescript-eslint/parser":"^6.12.0","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie-zod":"^0.0.2","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.10","gitHead":"76d2b741eb14b8c99ee5c3a24b4eeb854fe864d6","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-2kFh8bl34LQperHBi4kwCyjZ4UXRtQo1HOGvi43X9R4tUBCGciPqIPnfo/kYoT5863oIIRRGnTMwIX9XaMxJcg==","shasum":"2b9b8c3334c04e6815aee4774ac49c74e20e8acb","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.10.tgz","fileCount":23,"unpackedSize":134585,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDHrJwMJGShty+xVqmyX6BSbK8cE5tkKyz/JzSQvra8IQIgLtmwa5UsnQpQdRYHOEF6h/7j/n9nB+FLXa0UEzc5zgU="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.10_1700822989496_0.7423096859698255"},"_hasShrinkwrap":false},"0.1.11-beta.0":{"name":"next-smoothie","version":"0.1.11-beta.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.11-beta.0","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md","gitHead":"736db5f6e6dd82ac4b7b6d319defede295deadc2","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-56J7DRRFRnoecQUOJRKly0ads2PyIQFu+ugm1ldub2KS6hJdNfZhLDq6krR3VGEgfgtUqam3CZu9SvPNrooc1A==","shasum":"49cb2acc44bb064c9e358c700f11ae0f11cab90e","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.11-beta.0.tgz","fileCount":27,"unpackedSize":150674,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCoNSELE+47pUuSs6k/PfIL7qKv8GGS0kzhPaTRm6GY8AIhANngxxwwjsABhfHivF/gjtsV0xDDElECBPQsyVGycXn9"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.11-beta.0_1701528983320_0.31687519085380145"},"_hasShrinkwrap":false},"0.1.11":{"name":"next-smoothie","version":"0.1.11","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.11","gitHead":"79f905cebcbf82f55f19df91653ce6f204a4b441","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-JJ7Y1toVFZEjcofyrqDRHLBL7X6L0ozEa1yW7eeBmq0hOs3XsDWTDjBOa3BsDohcdyMUaIDmD4x+T2avXljeaQ==","shasum":"559463f5fa009674f69bde68163906729355861e","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.11.tgz","fileCount":27,"unpackedSize":150680,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBUIBTs5UZLPzvZ+IpJzOcv7gQlJY2oogBteNcFTw1bdAiEA/IN5j7fpeV6WlUI6dBL+vX7NRTnxaa9Py0SWG5c+udI="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.11_1701531465529_0.4225256605379244"},"_hasShrinkwrap":false},"0.1.12":{"name":"next-smoothie","version":"0.1.12","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.12","gitHead":"5ca70188584ac04c90698cd184d58a260d482f93","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-NgaTnLW+sQQfZRc+6u2APAKFjQSDYZq/AIO2OpBwg9qjtwzeEOHXSSPAj8+KyPrIWNbEArHA3dbqXj8b2Cf/Vw==","shasum":"e1772f01a6e65758683be2e9bc542ce36ca59e4c","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.12.tgz","fileCount":27,"unpackedSize":151435,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIC4xS8CO1XHB28Rtv2fk58wc3FDJ1G8wWDGXYVaQYgVLAiEA8Q1vOoGzgUmnOMTpppTCpuu/z1GpBKwtzGnN9gyfYVI="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.12_1701787852555_0.12693795680241093"},"_hasShrinkwrap":false},"0.1.13":{"name":"next-smoothie","version":"0.1.13","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.13","gitHead":"ffca5cf07de153a317658dd41ae9a6a8802026d6","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-l2RWR8EcA7ClfGxX23LrEKCKMMqTBQhiduxc4lx/oN92Z54rRxyiJqqmhOEvqfL19lV/NidTYYW+v4/WnooLyw==","shasum":"b40b807022692112a010628b0f8f4103217bd596","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.13.tgz","fileCount":27,"unpackedSize":151389,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAMhiyBU6rfmClyeLUJXWNEXw4XKBUEZVDo3Wk/73fJoAiEAtP4WGqD0KB0WmCPeXAYwgkL60c6cSlVEB7OqGd2qjDs="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.13_1701788130168_0.7245358725024538"},"_hasShrinkwrap":false},"0.1.14":{"name":"next-smoothie","version":"0.1.14","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.14","gitHead":"91bb29a8357a4c6c0a37f1ecf06b4cfbf800aba4","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-8N6m81Ry/+wVXZzW6nx7Vnfu3SLS5bpWh3QVbNEkA0zjjt1dRv++0Q5NyzUTqgdLg6oPCb+orWVnF01v/wxcvw==","shasum":"3da5b183925bc707f0ee5b356ff83eeea48ab69c","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.14.tgz","fileCount":27,"unpackedSize":151389,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDLuPpEGhXgpPpBSczx74iJoymsxM0u7NRQKSQxLWlV8AiEAyfNTIE2RuYKXVkDEzHzD7AHUguBkOHzNbvrywroA4Zk="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.14_1701788172177_0.25688653353971214"},"_hasShrinkwrap":false},"0.1.15":{"name":"next-smoothie","version":"0.1.15","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.15","gitHead":"54c9100e34d265d4e44fd6941926355233f03709","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-08+07BaVLMw08toNR3YfXrxYVwiaJC4HqUFv3QFxwe1T/2M2mP8w4WyEaoP+p5Vf4FIT44jPsmuUSNP1GU0DeQ==","shasum":"5e791ebb3b43a630e3e3ec52abd9b8c8fe5f89f9","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.15.tgz","fileCount":27,"unpackedSize":151395,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICu9N9zLZeFM68uL48J2xYS9cn/WeVPu1WEZtIMc9olpAiBveMBkCvBnArJRpidLox/vDAieGVHZMaHC/mRON7KC2g=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.15_1701799105823_0.3151994833851779"},"_hasShrinkwrap":false},"0.1.16":{"name":"next-smoothie","version":"0.1.16","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.16","gitHead":"bb73586a463cc6b7cb93dbdd6568e6942addab63","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-OQMEq90eeURdthslv3fDRIQZAIKIvs9KcjFBpxp5dnjSfxN9OSzQL8qpoj1RfAm2u346AI5TiGpAnLxml0LAMA==","shasum":"dd7017e428fd31ca2ffca1d084c5d0e144d09b0e","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.16.tgz","fileCount":27,"unpackedSize":153271,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICpPqPLsFoQhFcHAoPuO1K6WgHxJ6ehnzTYA3+FZcu2aAiEAmwjN0FrBeS1jGWe51TBhuVefbrxyKgSpt1YWuCNrneY="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.16_1701949849528_0.22364571149022594"},"_hasShrinkwrap":false},"0.1.17":{"name":"next-smoothie","version":"0.1.17","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.17","gitHead":"64cc29d25b952640be951cf24d94f010c25a5527","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-OyUbc9dD8OljXXi7/YCMBE497+wnT3Y/9E9n+r6vCkZffPCNXBON08BSS3d7dAr4cDSlfAL0VIOd5H9WrAIYRQ==","shasum":"955d6cb810484907eb5aba5d3fc6e72383fdfb57","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.17.tgz","fileCount":27,"unpackedSize":152294,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFG9gdUDRR7VA2+Zx0G/TG9IvIFpVzUPlEb/2JASnGo1AiAjlr0jA5s53gGE9l17DzaxbYlZN4jsjwdXc2cDnF33oQ=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.17_1701953202491_0.029914771714865562"},"_hasShrinkwrap":false},"0.1.18":{"name":"next-smoothie","version":"0.1.18","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.18","gitHead":"550cf3ff5af1bae9d47523319736549277064c77","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-UvelF7i4Gc2N2o8/FonUgvRAUtfHGe6VfvbJmOkJXexxZKslFuonCwqWMFzOUlchlTbX6BS1IdjMHRLS8dG0Iw==","shasum":"4702386c2bf2287b5bb07e0a53226e3019e80734","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.18.tgz","fileCount":27,"unpackedSize":152286,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQClP9KgUly5fz5HYGXFyCtuI5vjpFnYBetOcGPlcj0vRAIhAN3Me2I+IenLbrrtjhu1NC57n4VjuyLP24w4j4+e5rZq"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.18_1702143139986_0.7209006192171732"},"_hasShrinkwrap":false},"0.1.19":{"name":"next-smoothie","version":"0.1.19","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.19","gitHead":"f7bd604508e468251168afeddf781cc5a347e8b2","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-sPtYcgHD9p7XPHEEDYO2WiSjsFMV16jphODsPe7Xz9LjEiRHynrYf7whx9QqZAnRKE+I24gOFmnk2pHV3QgnLQ==","shasum":"ac02f9e4b5af5eda80b61befa11066acfb71b488","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.19.tgz","fileCount":27,"unpackedSize":151632,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGxCR4wd3R7CwSx9gstZ/PxcgzMyJ/1/rB4OSdwbU9PrAiBh8UgoKzcUeNMZ15S9v7nrqfAoavL3QZalKExIelwkmQ=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.19_1702144287601_0.3812670884648657"},"_hasShrinkwrap":false},"0.1.20":{"name":"next-smoothie","version":"0.1.20","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.20","gitHead":"82b31f63c739dc56183403cb7fe2d14d131327dc","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-mW0g8c/SBA6g7ey8+jEOvfhGxypxWCc5VAfFBdpmL1oAtsOo4qK7SRGJ/7w97t/LYlJLLzWbGT9ZcGCHehoXqw==","shasum":"85785be128654d74b910ae214a1dc8b2b8b5091d","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.20.tgz","fileCount":27,"unpackedSize":151539,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIG4fi9ME5bpwxwH2vzBZiCUlD+Ks9lBDsgj4WZL46mnzAiBinkmt6v4q7cVBdiadDnt90cJvc0Lmn1R6jdo8ycuIbg=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.20_1702146257168_0.026721703287463816"},"_hasShrinkwrap":false},"0.1.21":{"name":"next-smoothie","version":"0.1.21","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run dev","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.1.21","gitHead":"5a4286bd56851f25ad02597302743fcade722287","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-8WY9TDJ1clS7EPUoioif7ATISkS1AxX2NEK5GLAdl+Aao526TDPP6Ls672V/fQCJc0vU6v/Vjf948mB0xHNdWg==","shasum":"37c2a114276f69160840782b4a72d263dec3c4d2","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.1.21.tgz","fileCount":27,"unpackedSize":151591,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIG1z3xXmpzWktCUzZsEtAScBRoSMujTPFQx4rlqiFV4tAiBqT/mQOMN9tp4s7olNb/X/vh5iGd5lROrZJW74KQB3cQ=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.1.21_1702408726503_0.06825228841323816"},"_hasShrinkwrap":false},"0.2.0":{"name":"next-smoothie","version":"0.2.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"sleep 10 && npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run build && npm --prefix test run start","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","puppeteer":"^21.6.1","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.2.0","gitHead":"93c8fb0932d3a209907e42267218018b98e16304","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-1Mru4z4l4ZxaYjmvcf9cYsfTCh/zvhlkYiNiyikbvDuPljZc/i6qdE9N509jvVlb+dt4rYIwwqaG4scDt3uIaA==","shasum":"831fcca3b282af258b34e2d171640c10978d4eaa","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.2.0.tgz","fileCount":35,"unpackedSize":158463,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCsmlGpKCcluaE/IArzuleludVZkQxICbGk5QVzuVGGfwIgQUhFQ0pYhGPwuv+L4/QqvuXfaqaZa0R6LHRm61StYz0="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.2.0_1702578387329_0.23043317689471454"},"_hasShrinkwrap":false},"0.3.0":{"name":"next-smoothie","version":"0.3.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"sleep 10 && npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run build && npm --prefix test run start","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","puppeteer":"^21.6.1","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.3.0","gitHead":"fb7ba1823a84765724c38194b7d4380cc7b871ae","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-6OaxjnpRhCDSEpQwufHIUrcLDs/+cNIJoGdU7VqmayjYMRA9Ee53szMq3+wEM+m35fvCcRHMqhALW+144obwww==","shasum":"80292d7b37b489e5176daee8169f665649d84d32","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.3.0.tgz","fileCount":35,"unpackedSize":158463,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGjmGsvczP6o9VbJuCLq1tQ8UsTqMqUiTcMeoNP0uqoOAiAB9upQbcduherGABO6+OCl/Y825ZIxY949U/4uVTjSZg=="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.3.0_1702578445031_0.4984542446915652"},"_hasShrinkwrap":false},"0.3.1":{"name":"next-smoothie","version":"0.3.1","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"sleep 10 && npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run build && npm --prefix test run start","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","puppeteer":"^21.6.1","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.3.1","gitHead":"2f8d27a52776d288b5a33d1e937dca3f9665813b","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-B9CQjcgEG6q2XuWPcZ86wEJCjP6TOVeQSWUIxMtXUtXlVICniqnUQixA3FDi9js3nf8YBKm9p8drtTE3Sq0RkA==","shasum":"9b71072582c98c2089c915f6d14dd81f97b28b55","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.3.1.tgz","fileCount":35,"unpackedSize":158414,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC2UwtT5TecXsLS47KWwg+lhf67uJx6ZetY+9hCQaT9FAIgTW2D1Rw6yNB62JDDeDvtNWmGcz3IyF4DhMpXJQwGw7g="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.3.1_1702578748778_0.013396313817518601"},"_hasShrinkwrap":false},"0.3.2":{"name":"next-smoothie","version":"0.3.2","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"sleep 20 && npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run build && npm --prefix test run start","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.10","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.13.1","@typescript-eslint/parser":"^6.13.1","concurrently":"^8.2.2","eslint":"^8.54.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.3","next-smoothie":"^0.1.10","next-smoothie-zod":"^0.0.3","prettier":"^3.1.0","puppeteer":"^21.6.1","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.2","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.3.2","gitHead":"c515db790610434533740ec8129f09b23d222a87","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-YZzRTqwhc08BIx/OFkEZBKQ6cJwo9D3WjcEICIQOO1wNb8jY5BAo1GvOfaMdTQ73UPHE09BDAgjkX5Fk0nnPVQ==","shasum":"31e10440fda24262408584e8fae02b940931574d","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.3.2.tgz","fileCount":35,"unpackedSize":158926,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHHpQZ9iksnUdrxdudZQ5PIYmvTcnSdH5GuNdtetrBqaAiEAudOwYg6LdG89TUocgqTu36zjqd4R2WZlKAs8pTIbT8c="}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.3.2_1702581194723_0.8088795231043335"},"_hasShrinkwrap":false},"0.4.0":{"name":"next-smoothie","version":"0.4.0","description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","main":"/index.js","scripts":{"test":"npm run lint-nofix && tsc --noemit && npm run unit","unit":"PORT=3210 concurrently \"sleep 20 && npm run test:unit\" \"npm run serve:unit\" --kill-others --success first","test:unit":"jest","test:unit:watch":"jest --watch","postpublish":"node ../post-publish-test/post-publish.js","serve:unit":"npm --prefix test run build && npm --prefix test run start","build":"rm -rf dist && npm run toc && tsc && cp package.json dist && cp README.md dist && cp package-lock.json dist","lint-nofix":"eslint . --ext .ts,.tsx","lint":"npm run lint-nofix -- --fix","toc":"npx markdown-toc README.md -i && npx markdown-toc client/README.md -i","patch":"npm t && npm version patch && npm run build && npm publish ./dist && git push && git push --tags","minor":"npm t && npm version minor && npm run build && npm publish ./dist && git push && git push --tags","BREAKING-major":"npm t && npm version major && npm run build && npm publish ./dist && git push && git push --tags","beta":"npm t && npm version prerelease --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-patch":"npm t && npm version prepatch --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","beta-minor":"npm t && npm version preminor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags","BREAKING-beta-major":"npm t && npm version premajor --preid=beta && npm run build && npm publish ./dist --tag beta && git push && git push --tags"},"prettier":{"trailingComma":"es5","tabWidth":2,"semi":true,"singleQuote":true,"printWidth":120},"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"keywords":["nextjs","router"],"author":{"name":"Andrii Gubanov"},"license":"MIT","bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"homepage":"https://github.com/finom/next-smoothie#readme","devDependencies":{"@types/jest":"^29.5.11","@types/lodash":"^4.14.202","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^6.14.0","@typescript-eslint/parser":"^6.14.0","concurrently":"^8.2.2","eslint":"^8.55.0","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.0.1","jest":"^29.7.0","lodash":"^4.17.21","next":"^14.0.4","next-smoothie":"^0.3.2","next-smoothie-zod":"^0.0.4","prettier":"^3.1.1","puppeteer":"^21.6.1","supertest":"^6.3.3","ts-jest":"^29.1.1","typescript":"^5.3.3","zod":"^3.22.4"},"peerDependencies":{"next":">=13.0.0"},"_id":"next-smoothie@0.4.0","gitHead":"5c66416444a8912d5c36e256015a62baf9dafdd2","types":".//index.d.ts","_nodeVersion":"20.6.1","_npmVersion":"9.8.1","dist":{"integrity":"sha512-CTAU1RInKiTvm0K6hM8RrVpq+BE8GcxoUUyNdNdUJMnwR3KcDC31XBjRpw1UEjI00VGGn6Mlqrs+BV23C/vyoA==","shasum":"329213cea893beb7505ae3e7e9cdc82f7090c1d5","tarball":"https://registry.npmjs.org/next-smoothie/-/next-smoothie-0.4.0.tgz","fileCount":35,"unpackedSize":152330,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCCOZmTECENCdwgpze0PcYQzwsEgPMB2DhotELXSTfTrAIhANK8ACedGwRLva/xJ0EXYD3pOUv4zMM4jyIve326M/eh"}]},"_npmUser":{"name":"finom","email":"andrey.a.gubanov@gmail.com"},"directories":{},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/next-smoothie_0.4.0_1702676656312_0.20676459513234957"},"_hasShrinkwrap":false}},"time":{"created":"2023-11-11T11:11:09.617Z","0.0.5":"2023-11-11T11:11:09.881Z","modified":"2023-12-15T21:44:16.717Z","0.0.6":"2023-11-11T11:11:44.846Z","0.1.0":"2023-11-11T13:01:23.807Z","0.1.1":"2023-11-11T13:04:12.309Z","0.1.2-beta.0":"2023-11-13T09:59:13.805Z","0.1.3-beta.0":"2023-11-13T21:09:44.248Z","0.1.3-beta.1":"2023-11-13T21:11:34.780Z","0.1.3-beta.2":"2023-11-14T12:41:03.243Z","0.1.3-beta.3":"2023-11-14T13:12:36.003Z","0.1.3-beta.4":"2023-11-15T13:02:37.998Z","0.1.3-beta.5":"2023-11-15T17:58:54.871Z","0.1.3":"2023-11-18T17:17:30.630Z","0.1.4":"2023-11-18T17:26:30.004Z","0.1.5-beta.0":"2023-11-20T14:26:53.049Z","0.1.5-beta.1":"2023-11-21T20:09:13.111Z","0.1.5-beta.2":"2023-11-21T20:59:01.425Z","0.1.5-beta.3":"2023-11-21T21:08:54.812Z","0.1.5":"2023-11-21T21:56:41.251Z","0.1.6":"2023-11-21T21:58:04.462Z","0.1.7":"2023-11-21T22:03:44.682Z","0.1.8-beta.0":"2023-11-21T22:04:37.909Z","0.1.8-beta.3":"2023-11-22T10:17:13.228Z","0.1.8-beta.4":"2023-11-22T10:18:11.741Z","0.1.8-beta.5":"2023-11-22T10:24:54.734Z","0.1.8-beta.6":"2023-11-23T13:06:17.863Z","0.1.8-beta.7":"2023-11-23T14:23:10.193Z","0.1.8-beta.8":"2023-11-23T19:11:57.011Z","0.1.8-beta.9":"2023-11-23T19:18:26.482Z","0.1.8":"2023-11-23T20:13:47.255Z","0.1.9":"2023-11-23T20:26:18.564Z","0.1.10":"2023-11-24T10:49:49.643Z","0.1.11-beta.0":"2023-12-02T14:56:23.564Z","0.1.11":"2023-12-02T15:37:45.730Z","0.1.12":"2023-12-05T14:50:52.832Z","0.1.13":"2023-12-05T14:55:30.372Z","0.1.14":"2023-12-05T14:56:12.382Z","0.1.15":"2023-12-05T17:58:26.046Z","0.1.16":"2023-12-07T11:50:49.725Z","0.1.17":"2023-12-07T12:46:42.670Z","0.1.18":"2023-12-09T17:32:20.322Z","0.1.19":"2023-12-09T17:51:27.843Z","0.1.20":"2023-12-09T18:24:17.376Z","0.1.21":"2023-12-12T19:18:46.720Z","0.2.0":"2023-12-14T18:26:27.521Z","0.3.0":"2023-12-14T18:27:25.273Z","0.3.1":"2023-12-14T18:32:29.043Z","0.3.2":"2023-12-14T19:13:14.960Z","0.4.0":"2023-12-15T21:44:16.547Z"},"maintainers":[{"name":"finom","email":"andrey.a.gubanov@gmail.com"}],"description":"A compact, zero-dependency library that constructs Next.js 13+ App Route Handlers from decorated classes","homepage":"https://github.com/finom/next-smoothie#readme","keywords":["nextjs","router"],"repository":{"type":"git","url":"git+https://github.com/finom/next-smoothie.git"},"author":{"name":"Andrii Gubanov"},"bugs":{"url":"https://github.com/finom/next-smoothie/issues"},"license":"MIT","readme":"<p align=\"center\">\n  <img width=\"250\" alt=\"next-smoothie\" src=\"./.assets/smoothy.png\"> <br>\n  <picture>\n    <source width=\"500\" media=\"(prefers-color-scheme: dark)\" srcset=\"./.assets/text-smoothie-white.png\">\n    <source width=\"500\" media=\"(prefers-color-scheme: light)\" srcset=\"./.assets/text-smoothie-dark.png\">\n    <img width=\"500\" alt=\"next-smoothie\" src=\"./.assets/text-smoothie-dark.png\">\n  </picture>\n</p>\n\n\n<p align=\"center\">\n  <strong>The missing decorator-based API router for Next.js 13+</strong>\n  <br />\n  <em>6 minutes of reading</em>\n</p>\n\n## Quick start\n\nSet up a regular Next.js project with App routerusing [CLI and this instruction](https://nextjs.org/docs/getting-started/installation).\n\nInstall the library: `npm i next-smoothie` or `yarn add next-smoothie`.\n\nCreate the first controller:\n\n```ts\n// /src/controllers/UserController.ts\nimport { get, post, prefix } from 'next-smoothie';\nimport type { NextRequest } from 'next/server';\n\n@prefix('users') \nexport default class UserController {\n  @get() // Handles GET requests to '/api/users'\n  static getHelloWorld() {\n    return { hello: 'world' };\n  }\n\n  @post('hello/:id/world') // Handles POST requests to '/api/users/hello/:id/world'\n  static postHelloWorld(req: NextRequest, { id }: { id: string }) {\n    const q = req.nextUrl.searchParams.get('q');\n    const body = await req.json();\n    return { id, q, body };\n  }\n}\n```\n\nFinally, create the catch-all route with an optional slug (`[[...slug]]`) and call `activateControllers` with all your controllers. The slug is never used so you may want to keep it empty (`[[...]]`).\n\n```ts\n// /src/app/api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../../../controllers/UserController';\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nAfter that you can load the data using any fetching library.\n\n```ts\nfetch('/api/users');\nfetch(`/api/users/hello/${id}/world?q=foo`, {\n  method: 'POST', \n  body: JSON.stringify({ hello: 'world' }),\n});\n```\n\n<a href=\"https://www.npmjs.com/package/next-smoothie\">\n<img src=\"https://badge.fury.io/js/next-smoothie.svg\" alt=\"npm version\" /> \n</a>\n<a href=\"https://www.typescriptlang.org/\">\n<img src=\"https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg\" alt=\"TypeScript\" /> \n</a>\n<a href=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml\">\n<img src=\"https://github.com/finom/next-smoothie/actions/workflows/main.yml/badge.svg\" alt=\"Build status\" />\n</a>\n\n\n## Table of contents\n\n<!-- toc -->\n\n- [Features](#features)\n- [Overview](#overview)\n  * [Why Next.js is a good choice?](#why-nextjs-is-a-good-choice)\n  * [Limitations of Next.js API Routes](#limitations-of-nextjs-api-routes)\n  * [A potential solution: Pairing Next.js with NestJS](#a-potential-solution-pairing-nextjs-with-nestjs)\n  * [The new solution: next-smoothie](#the-new-solution-next-smoothie)\n    + [Custom decorators](#custom-decorators)\n    + [Service-Controller pattern](#service-controller-pattern)\n    + [Return type](#return-type)\n    + [Error handling](#error-handling)\n- [API](#api)\n  * [`createSegment` function, global decorators and handlers](#createsegment-function-global-decorators-and-handlers)\n  * [`HttpException` class and `HttpStatus` enum](#httpexception-class-and-httpstatus-enum)\n  * [`HttpMethod` enum](#httpmethod-enum)\n  * [`createDecorator` function](#createdecorator-function)\n    + [`authGuard` example](#authguard-example)\n    + [`handleZodErrors` example](#handlezoderrors-example)\n\n<!-- tocstop -->\n\n## Features\n\n**next-smoothie** offers a range of features to streamline your Next.js [App Router](https://nextjs.org/docs/app) experience:\n\n- Elegant decorator syntax (all HTTP methods are available). Custom decorators for varied needs are supported.\n- Direct data return from the handler (`Response` or `NextResponse` usage isn't required).\n- Pleasant error handling (no need to use `try..catch` and `NextResponse` to return an error to the client).\n- Service-Controller pattern is supported.\n- The library does not interfere with built-in Next.js features including extending of request object.\n\n## Overview\n\n### Why Next.js is a good choice?\n\nNext.js 13+ with App Router is a great ready-to-go framework that saves a lot of time and effort setting up and maintaining a React project. With Next.js:\n\n- You don't need to manually set up Webpack, Babel, ESLint, TypeScript.\n- Hot module reload is enabled by default and always works, so you don't need to find out why it stopped working after a dependency update.\n- Server-side rendering is enabled by default.\n- Routing and file structure are well-documented, eliminating the need for custom design.\n- It doesn't require you to \"eject\" scripts and configs if you want to modify them.\n- It's a widely known and well-used framework, no need to spend time thinking of a choice.\n\nAs result both long-term and short-term the development is cheaper, faster and more efficient.\n\n### Limitations of Next.js API Routes\n\nThe pros mentioned above are about front-end part (routes created with `page.tsx`), but the API route handlers provide very specific and very limited way to define API routes. Per every endpoint you're going to create a separate file called `route.ts` that exports route handlers that implement an HTTP method corresponding to their name:\n\n```ts\nexport async function GET() {\n  // ...\n  return NextResponse.json(data)\n}\n\nexport async function POST() {\n  // ...\n  return NextResponse.json(data)\n}\n```\n\nLet's imagine that your app requires to build the following endpoints:\n\n```\nGET /user - get all users\nPOST /user - create user\nGET /user/me - get current user\nPUT /user/me - update current user (password, etc)\nGET /user/[id] - get specified user by ID\nPUT /user/[id] - update a specified user (let's say, name only) \nGET /team - get all teams\nGET /team/[id] - get a specific team\nPOST /team/[id]/assign-user - some specialised endpoint that assigns a user to a specific team (whatever that means)\n```\n\nWith the built-in Next.js 13+ features your API folder structure is going to look the following:\n\n```\n/api/user/\n  /route.ts\n  /me/\n    /route.ts\n  /[id]/\n    /route.ts\n/api/team/\n  /route.ts\n  /[id]/\n    /route.ts\n    /assign-user/\n      /route.ts\n\n```\n\nIt's hard to manage this file structure (especially if you have complex API), and you may want to apply some creativity to reduce number of files and simplify the structure:\n\n- Move all features from /users folder (`/me` and `/[id]`) to `/user/route.ts` and use query parameter instead: `/user`, `/user/?who=me`, `/user/?who=[id]`\n- Do the same trick with the teams: `/team`, `/team?id=[id]`, `/team?id=[id]&action=assign-user`\n\nThe file structure now looks like the following:\n\n```\n/api/user/\n  /route.ts\n/api/team/\n  /route.ts\n```\n\nIt looks better (even though it still looks wrong) but the code inside these files make you write too many `if` conditions and will definitely make your code less readable. To make this documentation shorter, let me rely on your imagination.\n\n### A potential solution: Pairing Next.js with NestJS\n\nLast few years I solved the problem above by combining Next.js and NestJS framework in one project. Next.js was used as a front-end framework and NestJS was used as back-end framework. Unfortunately this solution requires to spend resources on additional code and deployment management:\n\n- Should it be a monorepo or 2 different repositories? \n  - Monorepo is harder to manage and deploy.\n  - Two repos are harder to synchronize (if deployed back-end code and front-end code compatible to each other at this moment of time?).\n- Both applications require to be run on their own port and we need to deploy them to 2 different servers. Multiply that by the numbers of environments (the most common are: dev, staging, prod) and you'll need to handle too many servers.\n\nIt would be nice if we could:\n\n- Use a single NodeJS project run in 1 port;\n- Keep the project in one simple repository;\n- Use single deployment server;\n- Apply NestJS-like syntax to define routes;\n- Make the project development and infrastructure cheaper.\n\n### The new solution: next-smoothie\n\nNext.js includes [Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) that enable us to create \"catch-all\" route handlers for a specific endpoint prefix. The library uses this feature to implement creation of route handlers with much more friendly syntax. The route handlers are going to be exported on one catch-all route file. To achieve that you're going to need to create the following files:\n\n```\n/api/[[...]]/route.ts\n/controllers\n  /UserController.ts\n  /TeamController.ts\n```\n\nFirst, `/controllers` is a folder that contains our dynamic controller files. The names of the folder and files don't matter so you can name it `/routers` for example.\n\nCreate your controllers:\n\n```ts\n// /controllers/UserController.ts\nimport { get, post, put, prefix } from 'next-smoothie';\n\n@prefix('users')\nexport default class UserController {\n  @get()\n  static getAll() {\n    return someORM.getAllUsers();\n  }\n\n  @get('me')\n  static getMe() {\n    // ...\n  }\n\n  @put('me')\n  static async updateMe(req: NextRequest) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n    // ...\n  }\n\n  @get(':id')\n  static async getOneUser(req: NextRequest, { id }: { id: string }) {\n    return someORM.getUserById(id);\n  }\n\n  @put(':id')\n  static async updateOneUser(req: NextRequest, { id }: { id: string }) {\n    const body = await req.json() as { firstName: string; lastName: string; };\n\n    return someORM.updateUserById(id, body);\n  }\n}\n```\n\n```ts\n// /controllers/TeamController.ts\nimport { get, post, prefix } from 'next-smoothie';\n\n@prefix('teams')\nexport default class TeamController {\n  @get()\n  static getAll() {\n    return someORM.getAllTeams();\n  }\n\n  @get(':id')\n  static getOneTeam(req: NextRequest, { id }: { id: string }) {\n    // ...\n  }\n\n  @post(':id/assign-user') \n  static assignUser() {\n    // ...\n  }\n}\n```\n\nFinally, create the catch-all route.\n\n```ts\n// /api/[[...]]/route.ts - this is a real file path where [[...]] is a folder name\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/UserController';\nimport TeamController from '../controllers/TeamController';\n\nexport const { GET, POST, PUT } = activateControllers([UserController, TeamController]);\n```\n\nThat's it. Notice that the methods modified by the decorators defined as `static` methods and the classes are never instantiated.\n\nAlso it's worthy to mention that `@prefix` decorator is just syntax sugar and you're not required to use it.\n\n#### Custom decorators\n\nYou can extend features of the controller by defining a [custom decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that can:\n\n- Run additional request validation, for example to check if user is authorised.\n- Catch specific errors.\n- Add more properties to the `req` object: current user, parsed and modified request body, etc.\n\nThere is typical code from a random project:\n\n```ts\n// ...\nexport default class MyController {\n  // ...\n\n  @post()\n  @authGuard()\n  @permissionGuard(Permission.CREATE)\n  @log(Action.CREATE, { model: 'MyModel' })\n  @handleZodErrors()\n  static async create(req: GuardedRequest) {\n    const body = ZodModel.parse(await req.json());\n\n    return this.myService.create(body);\n  }\n\n  // ...\n}\n```\n\nTo create a decorator you can use `createDecorator` that's described at the API section with a few examples.\n\nAll further examples are going to use Prisma ORM but you can use any ORM you like. \n\n#### Service-Controller pattern\n\nOptionally, you can improve your controller code by splitting it into service and controller. Service is a place where you make database requests and perform other data manipulation actions. Controller is where we use the decorators, check permissions, and validate incoming data, then call methods of the service. To achieve that, create another simple class (without no parent or decorators) with static methods:\n\n```ts\n// /controllers/user/UserService.ts\nexport default class UserService {\n  static findAllUsers() {\n    return prisma.user.findMany();\n  }\n}\n```\n\nThen inject the service as another static property to the controller\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  private static userService = UserService;\n\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return this.userService.findAllUsers();\n  }\n}\n```\n\nThen initialise the controller as before:\n\n```ts\n// /api/[[...]]/route.ts\nimport { activateControllers } from 'next-smoothie';\nimport UserController from '../controllers/user/UserController';\n\nexport const { GET } = activateControllers([UserController]);\n```\n\nPotential file structure with users, posts and comments may look like that:\n\n```\n/controllers/\n  /user/\n    /UserService.ts\n    /UserController.ts\n  /post/\n    /PostService.ts\n    /PostController.ts\n  /comment/\n    /CommentService.ts\n    /CommentController.ts\n```\n\nServices can use other services:\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static postService = PostService;\n\n  static doSomething() {\n    this.postService.doSomething();\n  }\n}\n```\n\nIn case service A is dependent on service B, and service B is dependent on service A you can turn the other service property into a getter:\n\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  private static get postService() { return PostService; };\n\n  static doSomething1() {\n    this.postService.doSomething2();\n  }\n}\n```\n\n\n```ts\n// /controllers/user/PostService.ts\nimport UserService from '../post/UserService';\n\nexport default class PostService {\n  private static get userService() { return UserService; };\n\n  static doSomething2() {\n    this.userService.doSomething1();\n  }\n}\n```\n\nOr you can avoid setting up service as a property at all:\n\n```ts\n// /controllers/user/UserController.ts\nimport UserService from './UserService';\n\n// ...\n@prefix('users')\nexport default class UserController {\n  @get()\n  @authGuard()\n  static getAllUsers() {\n    return UserService.findAllUsers();\n  }\n}\n```\n\n```ts\n// /controllers/user/UserService.ts\nimport PostService from '../post/PostService';\n\nexport default class UserService {\n  static doSomething1() {\n    PostService.doSomething2();\n  }\n}\n```\n\nBut it is still recommended to declare services as class properties to keep the classes self-documented.\n\n#### Return type\n\nController method can return an instance of `Response` or custom data. Custom data is serialised to JSON and returned with status 200.\n\n```ts\n@get()\nstatic getSomething() {\n  // same as NextResponse.json({ hello: 'world' }, { status: 200 })\n  return { hello: 'world' };\n}\n```\n\n- If `Response` instance (that also extends `NextResponse`) or `undefined` is returned, passes it to the route handler as is.\n- If something else is returned, the library asumes that the value is an variable that needs to be serialised into JSON and sent to the client.\n\nTake a look at this example:\n\n```ts\nimport { redirect } from 'next/navigation';\n\nclass ExampleService {\n  @get('a')\n  static getA() {\n    return NextResponse.json({ hello: 'world' }, { status: 200 });\n  }\n\n  @get('b')\n  static getB() {\n    return new Response(JSON.stringify({ hello: 'world' }), {\n      status: 200,\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  @get('c')\n  static getC() {\n    // return nothing (undefined)\n  }\n\n  @get('d')\n  static getD() {\n    return { hello: 'world' };\n  }\n}\n```\n\n- The routes A and B respond with result as is because they both return an instance of `Response`.\n- Route C returns `undefined` as is and causes an error \"No response is returned from route handler\".\n- Route D serialises the returned custom data and sends it to the client. The following snippet of code will probably make it clearer:\n\n```ts\nexport default function GET() {\n  // ...\n\n  // A, B, C\n  if(result instanceof Response || typeof result === 'undefined') {\n    return result;\n  }\n\n  // D\n  return NextResponse.json(result);\n}\n```\n\n#### Error handling\n\nYou can throw errors directly from the controller method. The library catches thrown exception and returns an object of type `ErrorResponseBody`.\n\n```ts\n// some client-side code\nimport { type ErrorResponseBody } from 'next-smoothie';\n\nconst dataOrError: MyData | ErrorResponseBody = await (await fetch('...')).json();\n```\n\nThe shape of this type is the following:\n\n```ts\ntype ErrorResponseBody = {\n  statusCode: HttpStatus;\n  message: string;\n  isError: true;\n}\n```\n\nTo throw an error you can use `HttpException` class together with `HttpStatus` enum. You can also throw the errors from the service methods.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie'\n\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new HttpException(HttpStatus.I_AM_A_TEAPOT, \"I'm a teapot\");\n  }\n  // ...\n}\n// ...\n```\n\nAll other exceptions are considered as 500 errors and handled similarly.\n\n```ts\n// ...\n@get()\nstatic getSomething() {\n  if(somethingWrong) {\n    throw new Error('Something is wrong');\n  }\n  // ...\n}\n// ...\n```\n\n## API\n\n```ts\nimport { \n  // main API\n  type ErrorResponseBody, \n  HttpException, \n  HttpStatus, \n  createSegment,\n  createDecorator,\n\n  // global controller members created with createSegment\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n### `createSegment` function, global decorators and handlers\n\nThe function `createSegment` initialises route handlers for one particular router segment. Using the function directly allows you to isolate some particular route path from other route handlers and provides a chance to refactor your code partially. Let's say you want to override only `/users` route handlers by using the library but keep `/comments` and `/posts` as is. \n\n\n```\n/api/posts/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/comments/\n  /route.ts\n  /[id]/\n    /route.ts\n/api/users/[[...]]/\n  /route.ts\n```\n\nIn this example, only the `users` dynamic route will utilize the library. With `createSegment` you can define local variables that are going to be used for one particular segment.\n\n```ts\nimport { createSegment } from 'next-smoothie';\n\nconst { get, post, activateControllers } = createSegment();\n\nclass UserController {\n  @get()\n  static getAll() {\n    // ...\n  }\n\n  @post()\n  static create() {\n    // ...\n  }\n}\n\nexport const { GET, POST } = activateControllers([UserController]);\n```\n\nThis is what `createSegment` returns:\n\n```ts\nconst {  \n  get, post, put, patch, del, head, options, // HTTP methods\n  prefix, \n  activateControllers, \n} = createSegment();\n```\n\n(notice that DELETE method decorator is shortned to `@del`).\n\n`activateControllers` returns all route handlers for all supported HTTP methods and also accepts options with `onError` handler that allows to listen to all errors for logging. It is important to remember that it is also called on [NEXT_REDIRECT](https://nextjs.org/docs/app/api-reference/functions/redirect). \n\n```ts\nexport const { GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD } = activateControllers(controllers, { \n  onError(error) {\n    console.log(error);\n  }\n});\n```\n\nAs you may already guess, some of the the variables imported from the library are created by `createSegment` to keep the code cleaner for the \"global\" segment instance.\n\n```ts\n// these vars are initialised within the library by createSegment\nimport {\n  get, post, put, patch, del, head, options, \n  prefix, \n  activateControllers,\n} from 'next-smoothie';\n```\n\n\n### `HttpException` class and `HttpStatus` enum\n\n\n`HttpException` accepts 2 arguments. The first one is an HTTP code that can be retrieved from `HttpStatus`, the other one is error text.\n\n```ts\nimport { HttpException, HttpStatus } from 'next-smoothie';\n\n// ...\nthrow new HttpException(HttpStatus.BAD_REQUEST, 'Something went wrong');\n```\n\n### `HttpMethod` enum\n\n`HttpMethod` enum has no specific purpose. It is used internally and I thought it might be useful to export it. You can use it with your fetching library for example:\n\n```ts\nfetch('...', {\n  method: HttpMethod.POST,\n})\n```\n\n### `createDecorator` function\n\n`createDecorator` is a higher-order function that produces a decorator factory (a function that returns a decorator). It accepts a middleware function with the following parameters:\n\n\n- `request`, which extends `NextRequest`.\n- `next`, a function that should be invoked and its result returned to call subsequent decorators or the route handler.\n- Additional arguments are passed through to the decorator factory.\n\n```ts\nimport { createDecorator, get } from 'next-smoothie';\n\nconst myDecorator = createDecorator((req, next, a: string, b: number) => {\n  console.log(a, b); // Outputs: \"foo\", 1\n\n  if(isSomething) { \n    // override route method behavior and return { hello: 'world' } from the endpoint\n    return { hello: 'world' };\n  }\n\n  return next();\n});\n\nclass MyController {\n  @get()\n  @myDecorator('foo', 1) // Passes 'foo' as 'a', and 1 as 'b'\n  static get() {\n    // ...\n  }\n}\n```\n\n#### `authGuard` example\n\nThere is the example code that defines `authGuard` decorator that does two things:\n\n- Checks if a user is authorised and returns an Unauthorised status if not.\n- Adds `currentUser` to the request object.\n\nTo extend `req` object you can define your custom interface that extends `NextRequest`.\n\n```ts\n// types.ts\nimport { type NextRequest } from 'next/server'\nimport { type User } from '@prisma/client';\n\nexport default interface GuardedRequest extends NextRequest {\n  currentUser: User;\n}\n```\n\nThen define the `authGuard` decorator itself.\n\n```ts\n// authGuard.ts\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\nimport { NextRequest } from 'next/server';\nimport checkAuth from './checkAuth';\n\nconst authGuard = createDecorator(async (req: GuardedRequest, next) => {\n  // ... define userId and isAuthorised\n  // parse access token for example\n\n  if (!isAuthorised) {\n    throw new HttpException(HttpStatus.UNAUTHORIZED, 'Unauthorized');\n  }\n\n  // let's imagine you use Prisma and you want to find a user by userId\n  const currentUser = await prisma.user.findUnique({ where: { id: userId } });\n\n  req.currentUser = currentUser;\n\n  return next();\n});\n\nexport default authGuard;\n```\n\nAnd finally use the decorator as we did above:\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @get('me')\n  @authGuard()\n  static async getMe(req: GuardedRequest) {\n    return req.currentUser;\n  }\n\n  // ...\n}\n```\n\n#### `handleZodErrors` example\n\nYou can catch any error in your custom decorator and provide relevant response to the client. At this exmple we're checking if `ZodError` is thrown. \n\n```ts\nimport { ZodError } from 'zod';\nimport { HttpException, HttpStatus, createDecorator } from 'next-smoothie';\n\nconst handleZodErrors = createDecorator(async (req, next) => {\n  try {\n    return await next();\n  } catch (e) {\n    if (e instanceof ZodError) {\n      throw new HttpException(\n        HttpStatus.BAD_REQUEST,\n        e.errors?.map((error) => `${error.code}: ${error.message}`).join('; ') ?? 'Validation error'\n      );\n    }\n\n    throw e;\n  }\n});\n\nexport default handleZodErrors;\n```\n\nIf `ZodModel.parse` encounters an error and throws a `ZodError` the decorator is going to catch it and return corresponding response.\n\n```ts\n// ...\nexport default class UserController {\n  // ...\n  @post()\n  @handleZodErrors()\n  static async create(req: NextRequest) {\n    const data = ZodModel.parse(await req.json());\n  }\n\n  // ...\n}\n```\n\nEnjoy!\n","readmeFilename":"README.md"}