├── packages ├── burger-api │ ├── examples │ │ ├── file-base-static-page-routing-app │ │ │ ├── assets │ │ │ │ ├── js │ │ │ │ │ └── app.js │ │ │ │ └── css │ │ │ │ │ └── app.css │ │ │ ├── api │ │ │ │ └── products │ │ │ │ │ ├── detail │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ ├── pages │ │ │ │ ├── my-static │ │ │ │ │ ├── app.js │ │ │ │ │ ├── index.html │ │ │ │ │ └── app.css │ │ │ │ ├── index.html │ │ │ │ ├── user │ │ │ │ │ └── [id] │ │ │ │ │ │ ├── post │ │ │ │ │ │ └── index.html │ │ │ │ │ │ └── index.html │ │ │ │ └── my-static-2 │ │ │ │ │ ├── product.html │ │ │ │ │ └── index.html │ │ │ ├── middleware │ │ │ │ └── logger.ts │ │ │ ├── index.ts │ │ │ ├── api.test.ts │ │ │ └── README.md │ │ ├── nested-dynamic-routes │ │ │ ├── api │ │ │ │ └── users │ │ │ │ │ ├── route.ts │ │ │ │ │ └── [userId] │ │ │ │ │ ├── route.ts │ │ │ │ │ └── posts │ │ │ │ │ └── [postId] │ │ │ │ │ └── route.ts │ │ │ ├── index.ts │ │ │ └── README.md │ │ ├── my-burger-api-app │ │ │ ├── api │ │ │ │ └── route.ts │ │ │ ├── middleware │ │ │ │ ├── logger.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── error-handling │ │ │ ├── api │ │ │ │ └── products │ │ │ │ │ ├── detail │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ ├── middleware │ │ │ │ └── logger.ts │ │ │ ├── index.ts │ │ │ └── README.md │ │ ├── file-base-api-routing │ │ │ ├── api │ │ │ │ └── (group) │ │ │ │ │ ├── products │ │ │ │ │ ├── detail │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ └── profile │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ ├── middleware │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── README.md │ │ │ └── api.test.ts │ │ ├── openapi-and-swagger-ui │ │ │ ├── middleware │ │ │ │ └── logger.ts │ │ │ ├── index.ts │ │ │ ├── api │ │ │ │ └── products │ │ │ │ │ ├── route.ts │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── README.md │ │ ├── route-specific-middleware │ │ │ ├── middleware │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── api │ │ │ │ └── (group) │ │ │ │ │ ├── products │ │ │ │ │ ├── detail │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ └── profile │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── README.md │ │ ├── native-server │ │ │ └── index.ts │ │ ├── zod-based-schema-validation │ │ │ ├── middleware │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── api │ │ │ │ └── products │ │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ └── README.md │ │ ├── wildcard-routes │ │ │ ├── index.ts │ │ │ └── api │ │ │ │ ├── admin │ │ │ │ ├── route.ts │ │ │ │ └── [...] │ │ │ │ │ └── route.ts │ │ │ │ ├── users │ │ │ │ ├── route.ts │ │ │ │ └── [userId] │ │ │ │ │ ├── [...] │ │ │ │ │ └── route.ts │ │ │ │ │ ├── posts │ │ │ │ │ └── [postId] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── auth │ │ │ │ └── [...] │ │ │ │ └── route.ts │ │ ├── cors-app │ │ │ ├── index.ts │ │ │ └── api │ │ │ │ └── test │ │ │ │ └── route.ts │ │ └── README.md │ ├── tsconfig.build.json │ ├── .npmignore │ ├── src │ │ ├── utils │ │ │ ├── response.ts │ │ │ ├── index.ts │ │ │ ├── wildcard.ts │ │ │ ├── routing.ts │ │ │ └── error.ts │ │ ├── core │ │ │ ├── swagger-ui.ts │ │ │ └── server.ts │ │ └── middleware │ │ │ └── validator.ts │ ├── LICENSE │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md └── cli │ ├── .gitignore │ ├── tsconfig.json │ ├── src │ ├── types │ │ └── index.ts │ ├── index.ts │ └── commands │ │ ├── list.ts │ │ └── serve.ts │ ├── package.json │ ├── CHANGELOG.md │ └── scripts │ ├── README.md │ └── uninstall │ └── uninstall.ps1 ├── package.json ├── .prettierrc ├── ecosystem ├── middlewares │ ├── timeout │ │ └── timeout.ts │ ├── body-size-limiter │ │ └── body-size-limiter.ts │ └── cache │ │ └── cache.ts └── README.md ├── bun.lock ├── .gitignore ├── README.md └── .github └── workflows └── release.yml /packages/burger-api/examples/file-base-static-page-routing-app/assets/js/app.js: -------------------------------------------------------------------------------- 1 | alert("Hello World"); -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/assets/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #333; 3 | color: #fff; 4 | } 5 | 6 | h1 { 7 | font-size: 2rem; 8 | font-weight: bold; 9 | } 10 | -------------------------------------------------------------------------------- /packages/burger-api/examples/nested-dynamic-routes/api/users/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../src/index'; 2 | 3 | export async function GET(req: BurgerRequest) { 4 | return Response.json({ 5 | message: 'Users list', 6 | level: 'users', 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/burger-api/examples/my-burger-api-app/api/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../src/index'; 2 | 3 | export async function GET() { 4 | // console.log('Hello world'); 5 | return Response.json({ message: 'Hello world' }); 6 | // return new Response('Hello world'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | .build/ 7 | 8 | # OS files 9 | .DS_Store 10 | Thumbs.db 11 | 12 | # Editor 13 | .vscode/ 14 | .idea/ 15 | *.swp 16 | *.swo 17 | 18 | # Logs 19 | *.log 20 | 21 | # Environment 22 | .env 23 | .env.local 24 | -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/api/products/detail/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../src/index'; 2 | 3 | export async function GET(req: BurgerRequest) { 4 | console.log('[GET] Product Detail route invoked'); 5 | 6 | return Response.json({ 7 | name: 'Sample Product', 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/api/(group)/products/detail/route.ts: -------------------------------------------------------------------------------- 1 | // This is data is outside of the route handler 2 | // but you can access it in the route handler 3 | const productId = '123'; 4 | 5 | export function GET() { 6 | return Response.json({ 7 | message: 'Product Detail', 8 | productId, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/api/products/detail/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../src/index'; 2 | 3 | export async function GET(req: BurgerRequest) { 4 | console.log('[GET] Product Detail route invoked'); 5 | 6 | return Response.json({ 7 | name: 'Sample Product', 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/burger-api/examples/openapi-and-swagger-ui/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerRequest, BurgerNext } from '../../../src/index'; 2 | 3 | // Global middleware example: a simple logger. 4 | export const globalLogger: Middleware = (req: BurgerRequest): BurgerNext => { 5 | console.log(`[Global Logger] ${req.method} ${req.url}`); 6 | return undefined; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/my-static/app.js: -------------------------------------------------------------------------------- 1 | 2 | const button = document.querySelector("button"); 3 | button.addEventListener("click", () => { 4 | alert("Button clicked"); 5 | }); 6 | 7 | const input = document.querySelector("input"); 8 | input.addEventListener("input", (e) => { 9 | console.log(e.target.value); 10 | }); 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerRequest, BurgerNext } from '../../../src/index'; 2 | 3 | // Global middleware example: a simple logger. 4 | export const globalLogger: Middleware = (req: BurgerRequest): BurgerNext => { 5 | console.log(`[Global Logger] ${req.method} ${req.url}`); 6 | return undefined; // Continue to the next middleware 7 | }; 8 | -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerNext, BurgerRequest } from '../../../src/index'; 2 | 3 | export const globalMiddleware1: Middleware = ( 4 | req: BurgerRequest 5 | ): BurgerNext => { 6 | console.log('Global middleware executed for request:', req.url); 7 | 8 | // Call the next middleware 9 | return undefined; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerNext, BurgerRequest } from '../../../src/index'; 2 | 3 | export const globalMiddleware1: Middleware = ( 4 | request: BurgerRequest 5 | ): BurgerNext => { 6 | console.log('Global middleware executed for request:', request.url); 7 | 8 | // Call the next middleware 9 | return undefined; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest, Middleware, BurgerNext } from '../../../src/index'; 2 | 3 | // Global middleware example: a simple logger. 4 | export const globalLogger: Middleware = ( 5 | req: BurgerRequest 6 | ): BurgerNext => { 7 | console.log(`[Global Logger] ${req.method} ${req.url}`); 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/burger-api/examples/native-server/index.ts: -------------------------------------------------------------------------------- 1 | // Import burger 2 | import { Burger } from '../../src/index'; 3 | 4 | // Create a new burger instance 5 | const burger = new Burger({ 6 | title: 'Burger API', 7 | description: 'A simple API for serving your data', 8 | }); 9 | 10 | // Start the server 11 | burger.serve(4000, () => { 12 | console.log(`✨ Server is running on port: 4000`); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/burger-api/examples/zod-based-schema-validation/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerNext, BurgerRequest } from '../../../src/index'; 2 | 3 | export const globalMiddleware1: Middleware = ( 4 | req: BurgerRequest 5 | ): BurgerNext => { 6 | console.log('Global middleware executed for request:', req.url); 7 | 8 | // Call the next middleware 9 | return undefined; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/burger-api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "allowImportingTsExtensions": false, 6 | "declaration": true, 7 | "declarationDir": "./dist", 8 | "outDir": "./dist", 9 | "rootDir": "./" 10 | // "removeComments": true 11 | }, 12 | "include": ["src/**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/api/(group)/profile/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../../src/index'; 2 | 3 | export function GET(req: BurgerRequest) { 4 | const query = new URL(req.url).searchParams; 5 | 6 | return Response.json({ 7 | id: req?.params?.id, 8 | query: Object.fromEntries(query), 9 | name: 'John Doe', 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main Page 7 | 8 | 9 |

Main Page

10 |

This is the main page.

11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/user/[id]/post/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Post Page 7 | 8 | 9 |

Post Page

10 |

This is the post page.

11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/my-static-2/product.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Product Page 7 | 8 | 9 |

Product Page

10 |

This is a product page.

11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/burger-api/examples/nested-dynamic-routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Burger, setDir } from '../../src/index'; 2 | 3 | // Simple test example for nested dynamic routes with Zod validation 4 | const burger = new Burger({ 5 | title: 'Nested Dynamic Routes Test', 6 | description: 'Testing nested dynamic routes functionality with Zod schemas', 7 | apiDir: setDir(__dirname, 'api'), 8 | apiPrefix: 'api', 9 | debug: true, 10 | }); 11 | 12 | burger.serve(4000); 13 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/user/[id]/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dynamic Page 7 | 8 | 9 |

Dynamic Page

10 |

This is a file base static page routing app.

11 |

users/[id]

12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/index.ts: -------------------------------------------------------------------------------- 1 | // Import burger 2 | import { Burger, setDir } from '../../src/index'; 3 | import { globalMiddleware1 } from './middleware'; 4 | 5 | // Create a new burger instance 6 | const burger = new Burger({ 7 | title: 'Burger API', 8 | description: 'A simple API for serving your data', 9 | apiDir: setDir(__dirname, 'api'), 10 | globalMiddleware: [globalMiddleware1], 11 | }); 12 | 13 | // Start the server 14 | burger.serve(4000); 15 | -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/index.ts: -------------------------------------------------------------------------------- 1 | // Import burger 2 | import { Burger, setDir } from '../../src/index'; 3 | import { globalMiddleware1 } from './middleware'; 4 | 5 | // Create a new burger instance 6 | const burger = new Burger({ 7 | title: 'Burger API', 8 | description: 'A simple API for serving your data', 9 | apiDir: setDir(__dirname, 'api'), 10 | globalMiddleware: [globalMiddleware1], 11 | }); 12 | 13 | // Start the server 14 | burger.serve(4000); 15 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/api/(group)/products/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../src/index'; 2 | 3 | export function GET(req: BurgerRequest) { 4 | const query = new URL(req.url).searchParams; 5 | return Response.json({ 6 | query: Object.fromEntries(query), 7 | name: 'John Doe', 8 | }); 9 | } 10 | 11 | export async function POST(req: BurgerRequest) { 12 | const body = await req.json(); 13 | return Response.json(body); 14 | } 15 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/index.ts: -------------------------------------------------------------------------------- 1 | // Wildcard Routes Example 2 | import { Burger, setDir } from '../../src/index'; 3 | 4 | // Create a new burger instance with wildcard routing 5 | const burger = new Burger({ 6 | title: 'BurgerAPI with Wildcard Routes', 7 | description: 'Demonstrating wildcard routes using [...] syntax', 8 | apiDir: setDir(__dirname, 'api'), 9 | debug: true, // Enable debug for better error messages 10 | }); 11 | 12 | // Start the server 13 | burger.serve(4000); 14 | -------------------------------------------------------------------------------- /packages/burger-api/.npmignore: -------------------------------------------------------------------------------- 1 | # Source files 2 | src/ 3 | examples/ 4 | 5 | # Development files 6 | .git/ 7 | .gitignore 8 | .npmignore 9 | .editorconfig 10 | .eslintrc 11 | .prettierrc 12 | tsconfig.json 13 | bun.lockb 14 | bun-experiment/* 15 | # Test files 16 | __tests__/ 17 | *.test.ts 18 | *.spec.ts 19 | 20 | # Documentation 21 | docs/ 22 | *.md 23 | !README.md 24 | 25 | # Build files 26 | dist/ 27 | build/ 28 | 29 | # IDE files 30 | .idea/ 31 | .vscode/ 32 | *.swp 33 | *.swo 34 | 35 | # OS files 36 | .DS_Store 37 | Thumbs.db -------------------------------------------------------------------------------- /packages/burger-api/examples/my-burger-api-app/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerNext, BurgerRequest } from '../../../src/index'; 2 | 3 | // Global middleware example: a simple logger. 4 | export const globalLogger: Middleware = ( 5 | request: BurgerRequest 6 | ): BurgerNext => { 7 | // console.log(`[Global Logger] ${request.method} ${request.url}`); 8 | let a = 2 + Math.random(); 9 | // return new Response(a.toString()); 10 | console.log(a); 11 | // console.log('Time:', Date.now()); 12 | return undefined; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/my-static-2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My Static 2 7 | 8 | 9 | 10 |

My Static 2

11 |

This is a static page.

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/admin/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../src/index'; 2 | 3 | /** 4 | * Static admin route example 5 | * Route: /api/admin 6 | * 7 | * Note: This route will handle the base path (/api/admin) since a static route exists as a sibling 8 | */ 9 | export async function GET(req: BurgerRequest) { 10 | return Response.json({ 11 | message: 'Static admin route working', 12 | note: 'This route handles base path (/api/admin) since a static route exists as a sibling', 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/burger-api/examples/zod-based-schema-validation/index.ts: -------------------------------------------------------------------------------- 1 | // Import burger 2 | import { Burger, setDir } from '../../src/index'; 3 | import { globalMiddleware1 } from './middleware'; 4 | 5 | // Create a new burger instance 6 | const burger = new Burger({ 7 | title: 'Burger API', 8 | description: 'A simple API for serving your data', 9 | apiDir: setDir(__dirname, 'api'), 10 | globalMiddleware: [globalMiddleware1], 11 | }); 12 | 13 | // Start the server 14 | burger.serve(4000, () => { 15 | console.log(`✨ Server is running on port: 4000`); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/my-static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My Static Page 5 | 6 | 7 | 8 |

My Static Page

9 |
10 |

This is a static page.

11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/burger-api/examples/nested-dynamic-routes/api/users/[userId]/route.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import type { BurgerRequest } from '../../../../../src/index'; 3 | 4 | export const schema = { 5 | get: { 6 | params: z.object({ 7 | userId: z.string().min(1, 'User ID is required'), 8 | }), 9 | }, 10 | }; 11 | 12 | export async function GET( 13 | req: BurgerRequest<{ params: z.infer }> 14 | ) { 15 | const { userId } = req.validated.params; 16 | return Response.json({ message: 'User details', userId, level: 'user' }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/burger-api/src/utils/response.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The method not allowed response 3 | */ 4 | export const METHOD_NOT_ALLOWED = new Response('Method Not Allowed', { 5 | status: 405, 6 | }); 7 | 8 | /** 9 | * The not found response 10 | */ 11 | export const NOT_FOUND = new Response('Not Found', { status: 404 }); 12 | 13 | /** 14 | * The OpenAPI error response 15 | */ 16 | export const OPENAPI_ERROR = Response.json({ 17 | error: 'API Router not configured', 18 | message: 19 | 'Please provide an apiDir option when initializing the Burger instance to enable OpenAPI documentation.', 20 | }); 21 | -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/api/(group)/products/detail/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerNext, BurgerRequest, Middleware } from '../../../../../../src/index'; 2 | 3 | // Route-specific middleware 4 | export const middleware: Middleware[] = [ 5 | (req: BurgerRequest): BurgerNext => { 6 | console.log( 7 | 'Product Detail Route-specific middleware executed for request:', 8 | req.url 9 | ); 10 | return undefined; 11 | }, 12 | ]; 13 | 14 | export function GET(req: BurgerRequest) { 15 | return Response.json({ 16 | message: 'Product Detail', 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/pages/my-static/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: tomato; 3 | font-family: Arial, sans-serif; 4 | } 5 | 6 | h1 { 7 | color: burlywood; 8 | text-align: center; 9 | } 10 | 11 | p { 12 | color: blanchedalmond; 13 | } 14 | 15 | button { 16 | background-color: #fff; 17 | color: #333; 18 | padding: 10px 20px; 19 | border-radius: 5px; 20 | cursor: pointer; 21 | } 22 | 23 | button:hover { 24 | background-color: #333; 25 | color: #fff; 26 | } 27 | 28 | .container { 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "burger-api", 3 | "author": "Isfhan Ahmed", 4 | "version": "0.6.4", 5 | "private": true, 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "type": "module", 10 | "scripts": { 11 | "typecheck": "bun run --filter burger-api typecheck", 12 | "build": "bun run --filter burger-api build", 13 | "test": "bun run --filter burger-api test", 14 | "dev": "bun run --filter burger-api dev" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/isfhan/burger-api.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/isfhan/burger-api/issues" 22 | }, 23 | "homepage": "https://burger-api.com", 24 | "license": "MIT" 25 | } -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/admin/[...]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../src/index'; 2 | 3 | /** 4 | * Admin wildcard route example 5 | * Route: /api/admin/[...] 6 | * 7 | * Note: This route will match any path that starts with /api/admin/ 8 | */ 9 | 10 | export async function GET(req: BurgerRequest) { 11 | const wildcardParams = req.wildcardParams || []; 12 | return Response.json({ 13 | message: 'Admin wildcard route working', 14 | wildcardParams: wildcardParams, 15 | adminPath: wildcardParams.join('/'), 16 | segments: wildcardParams.length, 17 | note: 'This demonstrates admin path handling with wildcard routes', 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/api/(group)/profile/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerNext, BurgerRequest, Middleware } from '../../../../../../src/index'; 2 | 3 | // Route-specific middleware 4 | export const middleware: Middleware[] = [ 5 | (req: BurgerRequest): BurgerNext => { 6 | console.log( 7 | 'Profile Route-specific middleware executed for request:', 8 | req.url 9 | ); 10 | return undefined; 11 | }, 12 | ]; 13 | 14 | export function GET(req: BurgerRequest) { 15 | const query = new URL(req.url).searchParams; 16 | 17 | return Response.json({ 18 | id: req.params?.id, 19 | query: Object.fromEntries(query), 20 | name: 'John Doe', 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/burger-api/examples/nested-dynamic-routes/api/users/[userId]/posts/[postId]/route.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import type { BurgerRequest } from '../../../../../../../src/index'; 3 | 4 | export const schema = { 5 | get: { 6 | params: z.object({ 7 | userId: z.string().min(1, 'User ID is required'), 8 | postId: z.string().min(1, 'Post ID is required'), 9 | }), 10 | }, 11 | }; 12 | 13 | export async function GET( 14 | req: BurgerRequest<{ params: z.infer }> 15 | ) { 16 | const { userId, postId } = req.validated.params; 17 | return Response.json({ 18 | message: 'Post details', 19 | userId, 20 | postId, 21 | level: 'post', 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/index.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from burger-api 2 | import { Burger, setDir } from '../../src/index'; 3 | 4 | // Import middleware 5 | import { globalLogger } from './middleware/logger'; 6 | 7 | // Create a new Burger instance with OpenAPI metadata and global middleware. 8 | const burger = new Burger({ 9 | title: 'Demo API', 10 | description: 11 | 'This is a demo API demonstrating all available options in burger-api.', 12 | apiDir: setDir(__dirname, 'api'), 13 | globalMiddleware: [globalLogger], 14 | version: '1.0.0', 15 | debug: true, 16 | }); 17 | 18 | // Start the server on port 4000, with a callback to log the startup. 19 | burger.serve(4000, () => { 20 | console.log(`🚀 Server is running on port 4000`); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/burger-api/examples/openapi-and-swagger-ui/index.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from burger-api 2 | import { Burger, setDir } from '../../src/index'; 3 | 4 | // Import middleware 5 | import { globalLogger } from './middleware/logger'; 6 | 7 | // Create a new Burger instance with OpenAPI metadata and global middleware. 8 | const burger = new Burger({ 9 | title: 'Demo API', 10 | description: 11 | 'This is a demo API demonstrating all available options in burger-api.', 12 | apiDir: setDir(__dirname, 'api'), 13 | globalMiddleware: [globalLogger], 14 | version: '1.0.0', 15 | debug: true, 16 | }); 17 | 18 | // Start the server on port 4000, with a callback to log the startup. 19 | burger.serve(4000, () => { 20 | console.log(`🚀 Server is running on port 4000`); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/index.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from burger-api 2 | import { Burger, setDir } from '../../src/index'; 3 | 4 | // Import middleware 5 | import { globalLogger } from './middleware/logger'; 6 | 7 | // Create a new Burger instance with OpenAPI metadata and global middleware. 8 | const burger = new Burger({ 9 | title: 'Demo API', 10 | description: 11 | 'This is a demo API demonstrating all available options in burger-api.', 12 | apiDir: setDir(__dirname, 'api'), 13 | pageDir: setDir(__dirname, 'pages'), 14 | globalMiddleware: [globalLogger], 15 | version: '1.0.0', 16 | debug: true, 17 | }); 18 | 19 | // Start the server on port 4000, with a callback to log the startup. 20 | burger.serve(4000, () => { 21 | console.log(`🚀 Server is running on port 4000`); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext"], 5 | "target": "ESNext", 6 | "module": "Preserve", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | "noImplicitOverride": true, 23 | 24 | // Some stricter flags (disabled by default) 25 | "noUnusedLocals": false, 26 | "noUnusedParameters": false, 27 | "noPropertyAccessFromIndexSignature": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/burger-api/examples/cors-app/index.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from burger-api 2 | import { Burger, setDir } from '../../src/index'; 3 | 4 | // Import middleware 5 | import { cors } from '../../../../ecosystem/middlewares/cors/cors'; 6 | 7 | // Create Burger instance 8 | const burger = new Burger({ 9 | title: 'CORS App', 10 | description: 'CORS middleware in the Burger API framework.', 11 | apiDir: setDir(__dirname, 'api'), 12 | globalMiddleware: [ 13 | cors({ 14 | origin: ['http://localhost:3000','https://hoppscotch.io'], // Allow only requests from http://localhost:3000 15 | debug: true, 16 | }), 17 | ], 18 | version: '1.0.0', 19 | debug: true, 20 | }); 21 | 22 | // Start the server on port 4000 23 | burger.serve(4000, () => { 24 | console.log(`🚀 Server is running on port 4000`); 25 | }); 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "es5", 10 | "bracketSpacing": true, 11 | "jsxBracketSameLine": false, 12 | "arrowParens": "always", 13 | "proseWrap": "preserve", 14 | "htmlWhitespaceSensitivity": "css", 15 | "endOfLine": "lf", 16 | "embeddedLanguageFormatting": "auto", 17 | "vueIndentScriptAndStyle": false, 18 | "overrides": [ 19 | { 20 | "files": "*.md", 21 | "options": { 22 | "proseWrap": "always", 23 | "printWidth": 80 24 | } 25 | }, 26 | { 27 | "files": "*.json", 28 | "options": { 29 | "tabWidth": 4 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/api/(group)/products/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerNext, BurgerRequest, Middleware } from '../../../../../src/index'; 2 | 3 | // Route-specific middleware 4 | export const middleware: Middleware[] = [ 5 | (req: BurgerRequest): BurgerNext => { 6 | console.log( 7 | 'Product Route-specific middleware executed for request:', 8 | req.url 9 | ); 10 | return undefined; 11 | }, 12 | ]; 13 | 14 | export async function GET(req: BurgerRequest) { 15 | console.log('Product GET request'); 16 | const query = new URL(req.url).searchParams; 17 | return Response.json({ 18 | query: Object.fromEntries(query), 19 | name: 'John Doe', 20 | }); 21 | } 22 | 23 | export async function POST(req: BurgerRequest) { 24 | const body = await req.json(); 25 | return Response.json(body); 26 | } 27 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/users/route.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | BurgerNext, 3 | BurgerRequest, 4 | Middleware, 5 | } from '../../../../src/index'; 6 | 7 | export const middleware: Middleware[] = [ 8 | (req: BurgerRequest): BurgerNext => { 9 | console.log('Route middleware executed'); 10 | return undefined; 11 | }, 12 | ]; 13 | 14 | /** 15 | * Static route example 16 | * Route: /api/users 17 | * 18 | * Note: This route will handle the base path (/api/users) 19 | */ 20 | export async function GET(req: BurgerRequest) { 21 | return Response.json({ 22 | message: 'Users list route working', 23 | note: 'This route handles the base path (/api/users)', 24 | users: [ 25 | { id: 1, name: 'John Doe' }, 26 | { id: 2, name: 'Jane Doe' }, 27 | { id: 3, name: 'John Smith' }, 28 | { id: 4, name: 'Jane Smith' }, 29 | ], 30 | level: 'static route example', 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/burger-api/src/core/swagger-ui.ts: -------------------------------------------------------------------------------- 1 | export const swaggerHtml = ` 2 | 3 | 4 | 5 | API Documentation 6 | 7 | 13 | 14 | 15 |
16 | 17 | 32 | 33 | `; 34 | -------------------------------------------------------------------------------- /packages/burger-api/examples/zod-based-schema-validation/api/products/[id]/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerNext, BurgerRequest, Middleware } from '../../../../../src/index'; 6 | 7 | // Export a schema for GET requests. 8 | export const schema = { 9 | get: { 10 | params: z.object({ 11 | id: z.string().min(1, 'ID is required'), 12 | }), 13 | }, 14 | }; 15 | 16 | // Route-specific middleware 17 | export const middleware: Middleware[] = [ 18 | (req: BurgerRequest): BurgerNext => { 19 | console.log( 20 | 'Product Detail Route-specific middleware executed for request:', 21 | req.url 22 | ); 23 | return undefined; 24 | }, 25 | ]; 26 | 27 | export async function GET( 28 | req: BurgerRequest<{ params: z.infer }> 29 | ) { 30 | return Response.json({ 31 | id: req.validated?.params.id, 32 | name: 'John Doe', 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/burger-api/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Isfhan Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/auth/[...]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../src/index'; 2 | 3 | /** 4 | * Documentation wildcard route (no static sibling) 5 | * Route: /api/auth/[...] 6 | * 7 | * This demonstrates wildcard handling base path when no static route exists. 8 | * Unlike /api/admin which has a static sibling, this route handles both: 9 | * - /api/auth (base path with empty params) 10 | * - /api/auth/login (with segments) 11 | * - /api/auth/logout (with segments) 12 | * - /api/auth/register (with segments) 13 | * - /api/auth/forgot-password (with segments) 14 | * - /api/auth/reset-password (with segments) 15 | * - /api/auth/verify-email (with segments) 16 | */ 17 | 18 | export async function GET(req: BurgerRequest) { 19 | const wildcardParams = req.wildcardParams || []; 20 | return Response.json({ 21 | message: 'Auth wildcard route', 22 | wildcardParams: wildcardParams, 23 | authPath: wildcardParams.join('/'), 24 | segments: wildcardParams.length, 25 | note: 'This route handles base path (/api/auth) with wildcard parameters if provided since no static route exists as a sibling', 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/burger-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false, 26 | 27 | // Project settings 28 | "rootDir": "../../", 29 | "outDir": "./dist", 30 | "baseUrl": ".", 31 | "paths": { 32 | // This is the path to the burger-api package in the root directory 33 | "burger-api": ["src/index.ts"] 34 | } 35 | }, 36 | "include": ["src/**/*", "tests/**/*", "examples/**/*"], 37 | "exclude": ["node_modules", "dist"] 38 | } 39 | -------------------------------------------------------------------------------- /packages/burger-api/examples/README.md: -------------------------------------------------------------------------------- 1 | # 🧪 Examples – Framework Testing 2 | 3 | ## 🎯 Purpose 4 | 5 | This folder is for **framework devs only**. It’s here to test BurgerAPI while building it—not for tutorials, learning, or production use. 6 | 7 | ## 🔧 Why 8 | 9 | * Try out new features 10 | * Reproduce and fix bugs 11 | * Check changes don’t break stuff 12 | * Test before publishing to npm 13 | 14 | ## 🚀 How to Use 15 | 16 | Run an example: 17 | 18 | ```bash 19 | cd examples/[example-name] 20 | bun run index.ts 21 | ``` 22 | 23 | Things to try: 24 | 25 | * Valid + invalid params 26 | * Edge cases 27 | * Error handling 28 | 29 | Debugging tips: 30 | 31 | * Drop in `console.log`s 32 | * Play with route patterns 33 | * Check param extraction + validation 34 | 35 | ## ⚠️ Notes 36 | 37 | * Not for learning the framework 38 | * Dev/testing only 39 | * Keep examples minimal 40 | 41 | ## 🔍 What to Test 42 | 43 | * Routing (dynamic segments) 44 | * Validation (Zod schemas) 45 | * Type inference (TS) 46 | * Param extraction 47 | * Error handling 48 | 49 | ## 📝 Adding New Examples 50 | 51 | * One feature per example 52 | * Clear folder name 53 | * Minimal code + short note on what it tests 54 | 55 | --- 56 | 57 | 👉 Keep it simple. This folder is just your sandbox for testing the framework while building it. 58 | 59 | 60 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/users/[userId]/[...]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../../src/index'; 2 | import { z } from 'zod'; 3 | 4 | /** 5 | * Zod schema for the wildcard route inside dynamic route folder 6 | * Route: /api/users/[userId]/[...] 7 | * 8 | * Note: This schema will validate the request params 9 | */ 10 | export const schema = { 11 | get: { 12 | params: z.object({ 13 | userId: z.string().min(1, 'User ID is required'), 14 | }), 15 | }, 16 | }; 17 | 18 | /** 19 | * Wildcard route inside dynamic route folder example 20 | * 21 | * Note: This route will handle the path (/api/users/[userId]/[...]) with the dynamic user id and the wildcard parameters 22 | */ 23 | export async function GET( 24 | req: BurgerRequest<{ params: z.infer }> 25 | ) { 26 | const { userId } = req.validated.params; 27 | const wildcardParams = req.wildcardParams || []; 28 | return Response.json({ 29 | message: 'Wildcard route example working', 30 | level: 'wildcard route inside dynamic route folder example', 31 | note: 'This route handles the path (/api/users/[userId]/[...]) with the dynamic user id and the wildcard parameters', 32 | userId: userId, 33 | wildcardParams: wildcardParams, 34 | userPath: `${userId}/${wildcardParams.join('/')}`, 35 | segments: wildcardParams.length, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/api/products/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | post: { 11 | summary: 'Create a Product', 12 | description: 13 | 'Creates a new product. Requires name and price in the request body.', 14 | tags: ['Product'], 15 | operationId: 'createProduct', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | post: { 22 | // Validate the JSON body. 23 | body: z.object({ 24 | name: z.string().min(1, 'Name is required'), 25 | price: z.number().positive('Price must be positive'), 26 | }), 27 | }, 28 | }; 29 | 30 | // Route-Specific Middleware 31 | export const middleware: Middleware[] = [ 32 | (req: BurgerRequest): BurgerNext => { 33 | console.log('Products Middleware'); 34 | return undefined; 35 | }, 36 | ]; 37 | 38 | // POST handler: creates a new product. 39 | export async function POST( 40 | req: BurgerRequest<{ body: z.infer }> 41 | ) { 42 | console.log('[POST] Products route invoked'); 43 | // Use validated body 44 | const body = req.validated.body; 45 | return Response.json(body); 46 | } 47 | -------------------------------------------------------------------------------- /packages/burger-api/examples/openapi-and-swagger-ui/api/products/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | post: { 11 | summary: 'Create a Product', 12 | description: 13 | 'Creates a new product. Requires name and price in the request body.', 14 | tags: ['Product'], 15 | operationId: 'createProduct', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | post: { 22 | // Validate the JSON body. 23 | body: z.object({ 24 | name: z.string().min(1, 'Name is required'), 25 | price: z.number().positive('Price must be positive'), 26 | }), 27 | }, 28 | }; 29 | 30 | type ReqBody = z.infer; 31 | 32 | // Route-Specific Middleware 33 | export const middleware: Middleware[] = [ 34 | (req: BurgerRequest): BurgerNext => { 35 | console.log('Products Middleware'); 36 | return undefined; 37 | }, 38 | ]; 39 | 40 | // POST handler: creates a new product. 41 | export async function POST(req: BurgerRequest<{ body: ReqBody }>) { 42 | console.log('[POST] Products route invoked'); 43 | // Use validated body 44 | const body = req.validated.body; 45 | return Response.json(body); 46 | } 47 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/users/[userId]/posts/[postId]/route.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../../../../../../../src/index'; 2 | import { z } from 'zod'; 3 | 4 | /** 5 | * Zod schema for the nested dynamic route and wildcard route sibling example 6 | * Route: /api/users/[userId]/posts/[postId] 7 | * 8 | * Note: This schema will validate the request params 9 | */ 10 | export const schema = { 11 | get: { 12 | params: z.object({ 13 | userId: z.string().min(1, 'User ID is required'), 14 | postId: z.string().min(1, 'Post ID is required'), 15 | }), 16 | }, 17 | }; 18 | 19 | /** 20 | * Nested dynamic route and wildcard route sibling example 21 | * This route handles the path (/api/users/[userId]/posts/[postId]) with the dynamic user id and the dynamic post id 22 | * Note: This route will return the post details for the given user id and post id 23 | */ 24 | export async function GET( 25 | req: BurgerRequest<{ params: z.infer }> 26 | ) { 27 | const { userId, postId } = req.validated.params; 28 | return Response.json({ 29 | message: 30 | 'Nested dynamic route and wildcard route sibling example working', 31 | note: 'This route handles the path (/api/users/[userId]/posts/[postId]) with the dynamic user id and the dynamic post id', 32 | userId: userId, 33 | postId: postId, 34 | level: 'nested dynamic route and wildcard route sibling example', 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli/src/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared TypeScript types for the CLI 3 | * 4 | * These types are used across different parts of the CLI 5 | * to ensure type safety and good developer experience. 6 | */ 7 | 8 | /** 9 | * Options for creating a new Burger API project 10 | */ 11 | export interface CreateOptions { 12 | /** Name of the project */ 13 | name: string; 14 | /** Whether to include API routes */ 15 | useApi: boolean; 16 | /** Directory for API routes (e.g., 'api') */ 17 | apiDir?: string; 18 | /** Prefix for API routes (e.g., '/api') */ 19 | apiPrefix?: string; 20 | /** Enable debug mode */ 21 | debug?: boolean; 22 | /** Whether to include Page routes */ 23 | usePages: boolean; 24 | /** Directory for Page routes (e.g., 'pages') */ 25 | pageDir?: string; 26 | /** Prefix for Page routes (e.g., '/') */ 27 | pagePrefix?: string; 28 | } 29 | 30 | /** 31 | * Information about a middleware/feature from GitHub 32 | */ 33 | export interface MiddlewareInfo { 34 | /** Name of the middleware (e.g., 'cors') */ 35 | name: string; 36 | /** Short description of what it does */ 37 | description: string; 38 | /** Path in the GitHub repo */ 39 | path: string; 40 | /** Files that are part of this middleware */ 41 | files: string[]; 42 | } 43 | 44 | /** 45 | * GitHub API response for directory contents 46 | */ 47 | export interface GitHubFile { 48 | name: string; 49 | path: string; 50 | type: 'file' | 'dir'; 51 | download_url?: string; 52 | size: number; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@burger-api/cli", 3 | "version": "0.6.4", 4 | "description": "Simple command-line tool for Burger API projects", 5 | "module": "src/index.ts", 6 | "type": "module", 7 | "bin": { 8 | "burger-api": "./src/index.ts" 9 | }, 10 | "scripts": { 11 | "dev": "bun run src/index.ts", 12 | "build:win": "bun build ./src/index.ts --compile --target bun-windows-x64 --outfile dist/burger-api.exe --minify", 13 | "build:linux": "bun build ./src/index.ts --compile --target bun-linux-x64 --outfile dist/burger-api-linux --minify", 14 | "build:mac": "bun build ./src/index.ts --compile --target bun-darwin-arm64 --outfile dist/burger-api-mac --minify", 15 | "build:mac-intel": "bun build ./src/index.ts --compile --target bun-darwin-x64 --outfile dist/burger-api-mac-intel --minify", 16 | "build:all": "bun run build:win && bun run build:linux && bun run build:mac && bun run build:mac-intel" 17 | }, 18 | "dependencies": { 19 | "commander": "^11.1.0", 20 | "@clack/prompts": "^0.7.0" 21 | }, 22 | "devDependencies": { 23 | "@types/bun": "latest", 24 | "@types/commander": "^2.12.5" 25 | }, 26 | "peerDependencies": { 27 | "typescript": "^5" 28 | }, 29 | "keywords": [ 30 | "burger-api", 31 | "cli", 32 | "framework", 33 | "bun" 34 | ], 35 | "author": "Isfhan Ahmed", 36 | "license": "MIT", 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/isfhan/burger-api.git", 40 | "directory": "packages/cli" 41 | } 42 | } -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/api/products/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | post: { 11 | summary: 'Create a Product', 12 | description: 13 | 'Creates a new product. Requires name and price in the request body.', 14 | tags: ['Product'], 15 | operationId: 'createProduct', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | post: { 22 | // Validate the JSON body. 23 | body: z.object({ 24 | name: z.string().min(1, 'Name is required'), 25 | price: z.number().positive('Price must be positive'), 26 | }), 27 | }, 28 | }; 29 | 30 | // Route-Specific Middleware 31 | export const middleware: Middleware[] = [ 32 | (req: BurgerRequest): BurgerNext => { 33 | console.log('Products Middleware'); 34 | return undefined; 35 | }, 36 | ]; 37 | 38 | // POST handler: creates a new product. 39 | export async function POST( 40 | req: BurgerRequest<{ body: z.infer }> 41 | ) { 42 | console.log('[POST] Products route invoked'); 43 | // Use validated body if available; otherwise, parse the JSON body. 44 | const body = req.validated?.body || (await req.json()); 45 | return Response.json(body); 46 | } 47 | -------------------------------------------------------------------------------- /packages/burger-api/examples/cors-app/api/test/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest } from '../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | export const openapi = { 9 | get: { 10 | summary: 'Get a Test', 11 | description: 12 | 'Gets a test message.', 13 | tags: ['Test'], 14 | operationId: 'getTest', 15 | }, 16 | post: { 17 | summary: 'Create a Test', 18 | description: 19 | 'Creates a new test message. Requires name and price in the request body.', 20 | tags: ['Test'], 21 | operationId: 'createTest', 22 | }, 23 | }; 24 | 25 | // Validation Schemas 26 | export const schema = { 27 | post: { 28 | // Validate the JSON body. 29 | body: z.object({ 30 | name: z.string().min(1, 'Name is required'), 31 | price: z.number().positive('Price must be positive'), 32 | }), 33 | }, 34 | }; 35 | 36 | // Create a type for the request body 37 | type ReqBody = z.infer; 38 | 39 | export async function GET() { 40 | return Response.json({ 41 | message: 'Hello World from GET route', 42 | }); 43 | } 44 | 45 | // POST handler: creates a new product. 46 | export async function POST(req: BurgerRequest<{ body: ReqBody }>) { 47 | // Use validated body 48 | const body = req.validated.body; 49 | 50 | // Return response with the validated body. 51 | return Response.json({ 52 | message: 'Hello World from POST route', 53 | data: body, 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /packages/burger-api/examples/my-burger-api-app/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerNext, BurgerRequest } from '../../../src/index'; 2 | 3 | // Export logger 4 | export { globalLogger } from './logger'; 5 | 6 | // Auth guard middleware 7 | export const authGuard: Middleware = ( 8 | request: BurgerRequest 9 | ): BurgerNext => { 10 | // Simple demo auth check 11 | const authHeader = request.headers.get('authorization'); 12 | if (!authHeader) { 13 | console.log('[Auth Guard] No auth header - allowing request for demo'); 14 | } 15 | return undefined; // Continue to next middleware 16 | }; 17 | 18 | // Rate limiter middleware 19 | export const rateLimiter: Middleware = ( 20 | request: BurgerRequest 21 | ): BurgerNext => { 22 | // Demo rate limiting 23 | console.log('[Rate Limiter] Checking rate limits for:', request.url); 24 | return undefined; // Continue to next middleware 25 | }; 26 | 27 | // CORS middleware 28 | export const corsMiddleware: Middleware = ( 29 | request: BurgerRequest 30 | ): BurgerNext => { 31 | // Return a function to add CORS headers to the response 32 | return async (response: Response) => { 33 | const headers = new Headers(response.headers); 34 | headers.set('Access-Control-Allow-Origin', '*'); 35 | headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 36 | headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 37 | 38 | return new Response(response.body, { 39 | status: response.status, 40 | statusText: response.statusText, 41 | headers, 42 | }); 43 | }; 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /packages/burger-api/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | // Export constants 4 | export { 5 | ROUTE_CONSTANTS, 6 | HTTP_METHODS, 7 | compareRoutes, 8 | getRouteSpecificity, 9 | collectRoutes, 10 | } from './routing'; 11 | 12 | /** 13 | * Resolves the given path to the specified directory. 14 | * @param directory the directory to resolve to 15 | * @param path the path to resolve 16 | * @returns the resolved path 17 | */ 18 | export function setDir(directory: string, path: string): string { 19 | return resolve(directory, path); 20 | } 21 | 22 | /** 23 | * Removes leading and trailing slashes from the given prefix. 24 | * @param prefix the prefix to clean 25 | * @returns the cleaned prefix 26 | */ 27 | export function cleanPrefix(prefix: string): string { 28 | // Remove all slashes from the beginning 29 | while (prefix.startsWith('/')) { 30 | prefix = prefix.slice(1); 31 | } 32 | // Remove all slashes from the end 33 | while (prefix.endsWith('/')) { 34 | prefix = prefix.slice(0, -1); 35 | } 36 | return prefix; 37 | } 38 | 39 | /** 40 | * Normalizes a file path by replacing multiple slashes with a single slash 41 | * and removing any trailing slash, unless the path is the root. 42 | * @param path The file path to normalize. 43 | * @returns The normalized file path. 44 | */ 45 | export function normalizePath(path: string): string { 46 | // Replace multiple slashes with a single slash 47 | let normalized = path.replace(/\/+/g, '/'); 48 | 49 | // Remove trailing slash if it's not the root 50 | if (normalized.length > 1 && normalized.endsWith('/')) { 51 | normalized = normalized.slice(0, -1); 52 | } 53 | 54 | return normalized; 55 | } 56 | -------------------------------------------------------------------------------- /packages/burger-api/examples/wildcard-routes/api/users/[userId]/route.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import type { BurgerRequest } from '../../../../../src/index'; 3 | 4 | // Dummy users data 5 | const users = [ 6 | { id: 1, name: 'John Doe' }, 7 | { id: 2, name: 'Jane Doe' }, 8 | { id: 3, name: 'John Smith' }, 9 | { id: 4, name: 'Jane Smith' }, 10 | ]; 11 | 12 | /** 13 | * Zod schema for the dynamic route 14 | * Route: /api/users/[userId] 15 | * 16 | * Note: This schema will validate the request params 17 | */ 18 | export const schema = { 19 | get: { 20 | params: z.object({ 21 | userId: z.string().min(1, 'User ID is required'), 22 | }), 23 | }, 24 | }; 25 | 26 | /** 27 | * Dynamic user details route example 28 | * Route: /api/users/[userId] 29 | * 30 | * Note: This route will handle the path (/api/users/[userId]) with the dynamic user id 31 | */ 32 | export async function GET( 33 | req: BurgerRequest<{ params: z.infer }> 34 | ) { 35 | const { userId } = req.validated.params; 36 | const user = users.find((user) => user.id === parseInt(userId)) || null; 37 | if (!user) { 38 | return Response.json( 39 | { 40 | message: 'User not found', 41 | user: null, 42 | note: 'This route handles the path (/api/users/[userId]) with the dynamic user id', 43 | level: 'dynamic route example', 44 | }, 45 | { status: 404 } 46 | ); 47 | } 48 | return Response.json({ 49 | message: 'User found', 50 | user: user, 51 | note: 'This route handles the path (/api/users/[userId]) with the dynamic user id and returns the user details', 52 | level: 'dynamic route example', 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 📣 Release Notes - Burger API CLI 2 | 3 | All notable changes to the Burger API CLI will be documented in this file. 4 | 5 | ## Version 0.6.3 - (December 17, 2025) 6 | 7 | ### Added 8 | - GitHub Actions release workflow 9 | - Updated README.md 10 | 11 | ## Version 0.1.0 - (December 14, 2025) 12 | 13 | ### Added 14 | - Initial release of Burger API CLI 15 | - `create` command to generate new projects with interactive prompts 16 | - `list` command to show available middleware from ecosystem 17 | - `add` command to download and install middleware 18 | - `build` command to bundle projects to single JS file 19 | - `build:executable` command to compile to standalone executable 20 | - `serve` command for development server with hot reload 21 | - Beautiful console output with colors and symbols 22 | - Zero external dependencies for file operations (uses Bun's native APIs) 23 | - Comprehensive documentation and examples 24 | - Support for Windows, macOS, and Linux 25 | 26 | ### Technical Details 27 | - Built with TypeScript and Bun.js 28 | - Uses only 2 dependencies: `commander` and `@clack/prompts` 29 | - All file downloads use Bun's native `fetch()` API 30 | - All file operations use Bun's fast file system APIs 31 | - All process spawning uses `Bun.spawn()` 32 | - Comprehensive JSDoc comments throughout codebase 33 | 34 | ## Release Process 35 | 36 | See [RELEASING.md](./RELEASING.md) for the release process. 37 | 38 | --- 39 | 40 | ## Change Categories 41 | 42 | We use these categories to organize changes: 43 | 44 | - **Added** - New features or commands 45 | - **Changed** - Changes to existing functionality 46 | - **Deprecated** - Features that will be removed in future 47 | - **Removed** - Features that have been removed 48 | - **Fixed** - Bug fixes 49 | - **Security** - Security improvements 50 | 51 | -------------------------------------------------------------------------------- /packages/burger-api/examples/zod-based-schema-validation/api/products/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerNext, BurgerRequest, Middleware } from '../../../../src/index'; 6 | 7 | // Export a schema for both GET and POST requests. 8 | // For GET, we validate the query parameters. 9 | // For POST, we validate the body. 10 | export const schema = { 11 | get: { 12 | query: z.object({ 13 | search: z.string(), 14 | }), 15 | }, 16 | post: { 17 | body: z.object({ 18 | // "name" is required and must be at least 1 character. 19 | name: z.string().min(1, 'Name is required'), 20 | // "price" must be a positive number. 21 | price: z.number().positive('Price must be positive'), 22 | }), 23 | }, 24 | }; 25 | 26 | // Route-specific middleware 27 | export const middleware: Middleware[] = [ 28 | (req: BurgerRequest): BurgerNext => { 29 | console.log( 30 | 'Product Route-specific middleware executed for request:', 31 | req.url 32 | ); 33 | return undefined; 34 | }, 35 | ]; 36 | 37 | // GET handler: uses validated query if available. 38 | export async function GET( 39 | req: BurgerRequest<{ query: z.infer }> 40 | ) { 41 | // Get the validated query by zod schema. 42 | // const query = new URL(req.url).searchParams; 43 | 44 | // Return response with the validated query. 45 | return Response.json({ 46 | query: req.validated?.query, 47 | name: 'John Doe', 48 | }); 49 | } 50 | 51 | // POST handler: uses validated body if available. 52 | export async function POST( 53 | req: BurgerRequest<{ body: z.infer }> 54 | ) { 55 | // Get the validated body by zod schema. 56 | const body = req.validated?.body; 57 | 58 | // Return response with the validated body. 59 | return Response.json(body); 60 | } 61 | -------------------------------------------------------------------------------- /packages/cli/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Installation Scripts 2 | 3 | This directory contains all installation and uninstallation scripts for the Burger API CLI. 4 | 5 | ## Structure 6 | 7 | ``` 8 | scripts/ 9 | ├── install/ # Installation scripts 10 | │ ├── install.sh # Linux/macOS installer 11 | │ └── install.ps1 # Windows installer 12 | └── uninstall/ # Uninstallation scripts 13 | ├── uninstall.sh # Linux/macOS uninstaller 14 | └── uninstall.ps1 # Windows uninstaller 15 | ``` 16 | 17 | ## Quick Reference 18 | 19 | ### Installation 20 | 21 | **Linux/macOS:** 22 | ```bash 23 | curl -fsSL https://burger-api.com/install.sh | bash 24 | ``` 25 | 26 | **Windows:** 27 | ```powershell 28 | irm https://burger-api.com/install.ps1 | iex 29 | ``` 30 | 31 | ### Uninstallation 32 | 33 | **Linux/macOS:** 34 | ```bash 35 | curl -fsSL https://burger-api.com/uninstall.sh | bash 36 | ``` 37 | 38 | **Windows:** 39 | ```powershell 40 | irm https://burger-api.com/uninstall.ps1 | iex 41 | ``` 42 | 43 | ## Website Deployment 44 | 45 | When deploying to the website repository: 46 | 47 | 1. Copy all files from `install/` to website public directory 48 | 2. Copy all files from `uninstall/` to website public directory 49 | 3. Ensure files are accessible at the root URLs: 50 | - `https://burger-api.com/install.sh` 51 | - `https://burger-api.com/install.ps1` 52 | - `https://burger-api.com/uninstall.sh` 53 | - `https://burger-api.com/uninstall.ps1` 54 | 55 | ## Features 56 | 57 | All scripts include: 58 | - ✅ Progress bars during downloads 59 | - ✅ Automatic PATH configuration 60 | - ✅ Multi-shell support (bash, zsh, fish) 61 | - ✅ macOS M1/zsh compatibility 62 | - ✅ Comprehensive error handling 63 | - ✅ User-friendly error messages 64 | - ✅ Confirmation prompts (uninstall only) 65 | - ✅ Config file backups (uninstall only) 66 | 67 | ## Documentation 68 | 69 | - See [`install/README.md`](./install/README.md) for installation script details 70 | - See [`uninstall/README.md`](./uninstall/README.md) for uninstallation script details 71 | 72 | -------------------------------------------------------------------------------- /packages/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | 3 | /** 4 | * BurgerAPI CLI Tool 5 | * 6 | * This is the main entry point for the CLI. 7 | * It sets up all the commands users can run. 8 | * 9 | * We use commander to handle command parsing and routing. 10 | * This makes it easy to add new commands and provide helpful error messages. 11 | */ 12 | 13 | import { Command } from 'commander'; 14 | import { createCommand } from './commands/create'; 15 | import { addCommand } from './commands/add'; 16 | import { listCommand } from './commands/list'; 17 | import { buildCommand, buildExecutableCommand } from './commands/build'; 18 | import { serveCommand } from './commands/serve'; 19 | import { showBanner } from './utils/logger'; 20 | 21 | /** 22 | * Create the main CLI program 23 | * This is what runs when someone types 'burger-api' in their terminal 24 | */ 25 | const program = new Command(); 26 | 27 | // Set up basic information about our CLI 28 | program 29 | .name('burger-api') 30 | .description('Simple tool to work with BurgerAPI projects') 31 | .version('0.6.3'); 32 | 33 | // Add all our commands to the CLI 34 | // Each command is defined in its own file for better organization 35 | program.addCommand(createCommand); // Create new projects 36 | program.addCommand(addCommand); // Add middleware to projects 37 | program.addCommand(listCommand); // List available middleware 38 | program.addCommand(buildCommand); // Bundle to JS file 39 | program.addCommand(buildExecutableCommand); // Compile to executable 40 | program.addCommand(serveCommand); // Run development server 41 | 42 | // Show banner + help when no command is provided 43 | program.action(() => { 44 | showBanner(); 45 | program.help(); 46 | }); 47 | 48 | // Override the help display to include banner 49 | program.configureOutput({ 50 | writeOut: (str) => { 51 | process.stdout.write(str); 52 | }, 53 | writeErr: (str) => { 54 | process.stderr.write(str); 55 | }, 56 | }); 57 | 58 | // Run the CLI - this parses the arguments the user typed 59 | program.parse(); 60 | -------------------------------------------------------------------------------- /packages/burger-api/examples/openapi-and-swagger-ui/api/products/[id]/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | get: { 11 | summary: 'Get Product Details', 12 | description: 13 | 'Retrieves the details of a product by its ID. Optionally accepts a search parameter.', 14 | tags: ['Product'], 15 | operationId: 'getProductDetails', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | get: { 22 | // Validate URL parameters: convert "id" from string to number. 23 | params: z.object({ 24 | id: z.preprocess((val) => { 25 | if (typeof val === 'string') { 26 | const parsed = parseInt(val, 10); 27 | return isNaN(parsed) ? 'string' : parsed; 28 | } 29 | return val; 30 | }, z.number().min(1, 'ID is required')), 31 | }), 32 | // Validate query parameters. 33 | query: z.object({ 34 | search: z.string().optional(), 35 | }), 36 | }, 37 | }; 38 | 39 | // Route-Specific Middleware 40 | export const middleware: Middleware[] = [ 41 | (req: BurgerRequest): BurgerNext => { 42 | console.log('Product Detail Middleware'); 43 | return undefined; 44 | }, 45 | ]; 46 | 47 | export async function GET( 48 | req: BurgerRequest<{ 49 | params: z.infer; 50 | query: z.infer; 51 | }> 52 | ) { 53 | console.log('[GET] Product Detail route invoked'); 54 | 55 | const validatedParams = req.validated.params; 56 | const query = req.validated.query; 57 | 58 | return Response.json({ 59 | id: validatedParams.id, 60 | query: query, 61 | name: 'Sample Product', 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/api/products/[id]/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | get: { 11 | summary: 'Get Product Details', 12 | description: 13 | 'Retrieves the details of a product by its ID. Optionally accepts a search parameter.', 14 | tags: ['Product'], 15 | operationId: 'getProductDetails', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | get: { 22 | // Validate URL parameters: convert "id" from string to number. 23 | params: z.object({ 24 | id: z.preprocess((val) => { 25 | if (typeof val === 'string') { 26 | const parsed = parseInt(val, 10); 27 | return isNaN(parsed) ? 'string' : parsed; 28 | } 29 | return val; 30 | }, z.number().min(1, 'ID is required')), 31 | }), 32 | // Validate query parameters. 33 | query: z.object({ 34 | search: z.string().optional(), 35 | }), 36 | }, 37 | }; 38 | 39 | // Route-Specific Middleware 40 | export const middleware: Middleware[] = [ 41 | (req: BurgerRequest): BurgerNext => { 42 | console.log('Product Detail Middleware'); 43 | return undefined; 44 | }, 45 | ]; 46 | 47 | export async function GET( 48 | req: BurgerRequest<{ 49 | params: z.infer; 50 | query: z.infer; 51 | }> 52 | ) { 53 | console.log('[GET] Dynamic Product route invoked'); 54 | 55 | // Use validated parameters 56 | const validatedParams = req.validated.params; 57 | const query = req.validated.query; 58 | 59 | return Response.json({ 60 | id: validatedParams.id, 61 | query: query, 62 | name: 'Sample Product', 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/api/products/[id]/route.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from zod 2 | import { z } from 'zod'; 3 | 4 | // Import types 5 | import type { BurgerRequest, BurgerNext, Middleware } from '../../../../../src/index'; 6 | 7 | // OpenAPI Metadata 8 | // Developers can provide custom metadata to enrich the docs. 9 | export const openapi = { 10 | get: { 11 | summary: 'Get Product Details', 12 | description: 13 | 'Retrieves the details of a product by its ID. Optionally accepts a search parameter.', 14 | tags: ['Product'], 15 | operationId: 'getProductDetails', 16 | }, 17 | }; 18 | 19 | // Validation Schemas 20 | export const schema = { 21 | get: { 22 | // Validate URL parameters: convert "id" from string to number. 23 | params: z.object({ 24 | id: z.preprocess((val) => { 25 | if (typeof val === 'string') { 26 | const parsed = parseInt(val, 10); 27 | return isNaN(parsed) ? 'string' : parsed; 28 | } 29 | return val; 30 | }, z.number().min(1, 'ID is required')), 31 | }), 32 | // Validate query parameters. 33 | query: z.object({ 34 | search: z.string().optional(), 35 | }), 36 | }, 37 | }; 38 | 39 | // Route-Specific Middleware 40 | export const middleware: Middleware[] = [ 41 | (req: BurgerRequest): BurgerNext => { 42 | console.log('Product Detail Middleware'); 43 | return undefined; 44 | }, 45 | ]; 46 | 47 | export async function GET( 48 | req: BurgerRequest<{ 49 | params: z.infer; 50 | query: z.infer; 51 | }> 52 | ) { 53 | console.log('[GET] Dynamic Product route invoked'); 54 | 55 | // Use validated parameters if available; otherwise, fallback to resolved params. 56 | const validatedParams = req.validated.params; 57 | const query = req.validated.query; 58 | return Response.json({ 59 | id: validatedParams?.id, 60 | query: query, 61 | name: 'Sample Product', 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /packages/burger-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "burger-api", 3 | "version": "0.6.4", 4 | "author": "Isfhan Ahmed", 5 | "type": "module", 6 | "module": "dist/src/index.js", 7 | "types": "dist/src/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "typecheck": "tsc --noEmit", 13 | "build": "tsc --project tsconfig.build.json", 14 | "prepare": "bun run build", 15 | "test": "bun test", 16 | "dev": "bun run examples/file-base-static-page-routing-app/index.ts" 17 | }, 18 | "keywords": [ 19 | "burger-api", 20 | "burger-api-framework", 21 | "bun", 22 | "bunjs", 23 | "bun-framework", 24 | "fast-backend", 25 | "javascript-backend", 26 | "high-performance", 27 | "esm", 28 | "typescript", 29 | "openapi", 30 | "swagger-ui", 31 | "declarative-routing", 32 | "api-framework", 33 | "modern-backend", 34 | "backend-framework", 35 | "server", 36 | "bun-native", 37 | "js-backend", 38 | "fast-api", 39 | "scalable-backend", 40 | "backend-development", 41 | "modern-api", 42 | "api-development", 43 | "backend-engine", 44 | "efficient", 45 | "minimalist-backend", 46 | "bun-powered", 47 | "open-source", 48 | "typescript-backend", 49 | "performance", 50 | "rapid-development", 51 | "web-framework", 52 | "api-server", 53 | "dynamic-routing", 54 | "backend-routing", 55 | "extensible", 56 | "modular", 57 | "declarative-api", 58 | "lightweight", 59 | "contemporary-backend", 60 | "fast-javascript", 61 | "bun-js-framework", 62 | "bun-backend", 63 | "bun-api-framework", 64 | "pakistan-backend", 65 | "pakistan-api-framework", 66 | "pakistan-api-server", 67 | "pakistan-backend-framework", 68 | "pakistan-bunjs-framework", 69 | "pakistan-javascript-framework" 70 | ], 71 | "license": "MIT", 72 | "repository": { 73 | "type": "git", 74 | "url": "git+https://github.com/isfhan/burger-api.git" 75 | }, 76 | "bugs": { 77 | "url": "https://github.com/isfhan/burger-api/issues" 78 | }, 79 | "homepage": "https://burger-api.com", 80 | "dependencies": { 81 | "zod": "^4.0.17" 82 | }, 83 | "devDependencies": { 84 | "@types/bun": "latest" 85 | }, 86 | "peerDependencies": { 87 | "typescript": "^5.7.3" 88 | } 89 | } -------------------------------------------------------------------------------- /packages/burger-api/src/core/server.ts: -------------------------------------------------------------------------------- 1 | // Import stuff from Bun 2 | import { serve } from 'bun'; 3 | 4 | // Import stuff from utils 5 | import { errorResponse } from '../utils/error'; 6 | 7 | import type { ServerOptions, FetchHandler } from '../types/index'; 8 | 9 | export class Server { 10 | private options: ServerOptions; 11 | private server: ReturnType | null = null; 12 | 13 | /** 14 | * Initializes a new instance of the Server class with the given options. 15 | * @param options - Configuration options for the server. 16 | */ 17 | constructor(options: ServerOptions) { 18 | this.options = options; 19 | } 20 | 21 | public start( 22 | routes: { [key: string]: any } | undefined, 23 | handler: FetchHandler, 24 | port: number, 25 | cb?: () => void 26 | ): void { 27 | // Start Bun's native server using Bun.serve 28 | this.server = serve({ 29 | routes, 30 | // Bun's fetch handler 31 | fetch: async (request: Request) => { 32 | try { 33 | return await handler(request); 34 | } catch (error) { 35 | // Return a custom error response 36 | return errorResponse( 37 | error, 38 | request, 39 | this.options.debug ?? false 40 | ); 41 | } 42 | }, 43 | // Global error handler 44 | error(error) { 45 | console.error(error); 46 | return new Response(`Internal Server Error: ${error.message}`, { 47 | status: 500, 48 | headers: { 49 | 'Content-Type': 'text/plain', 50 | }, 51 | }); 52 | }, 53 | port, 54 | }); 55 | if (cb) { 56 | cb(); 57 | } else { 58 | console.log( 59 | `🍔 BurgerAPI is running at: http://${ 60 | this.options.hostname || 'localhost' 61 | }:${port}` 62 | ); 63 | } 64 | } 65 | 66 | /** 67 | * Stops the server. 68 | * If the server is currently running, this method will stop the server and 69 | * log a message to the console indicating that the server has been stopped. 70 | * If the server is not running, this method does nothing. 71 | */ 72 | public stop(): void { 73 | if (this.server) { 74 | this.server.stop(); 75 | console.log('Server stopped.'); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * List Command 3 | * 4 | * Shows users all available middleware they can add to their project. 5 | * Fetches the list from GitHub and displays it in a nice table format. 6 | * 7 | * Example: burger-api list 8 | */ 9 | 10 | import { Command } from 'commander'; 11 | import { getMiddlewareList, getMiddlewareInfo } from '../utils/github'; 12 | import { 13 | header, 14 | spinner, 15 | error as logError, 16 | table, 17 | newline, 18 | info, 19 | dim, 20 | command, 21 | } from '../utils/logger'; 22 | 23 | /** 24 | * Create the "list" command 25 | * This shows all available middleware from the ecosystem 26 | */ 27 | export const listCommand = new Command('list') 28 | .description('Show available middleware from the ecosystem') 29 | .alias('ls') // Allow users to type "burger-api ls" too 30 | .action(async () => { 31 | // Show a spinner while fetching from GitHub 32 | const spin = spinner('Fetching middleware list from GitHub...'); 33 | 34 | try { 35 | // Get the list of middleware names 36 | const middlewareNames = await getMiddlewareList(); 37 | 38 | // Fetch details for each middleware (in parallel for speed!) 39 | const middlewareDetails = await Promise.all( 40 | middlewareNames.map((name) => 41 | getMiddlewareInfo(name).catch(() => ({ 42 | name, 43 | description: 'No description available', 44 | path: '', 45 | files: [], 46 | })) 47 | ) 48 | ); 49 | 50 | spin.stop('Found available middleware!'); 51 | newline(); 52 | 53 | // Display header 54 | header('Available Middleware'); 55 | 56 | // Create table data 57 | const tableData: string[][] = [ 58 | ['Name', 'Description'], 59 | ...middlewareDetails.map((m) => [ 60 | m.name, 61 | m.description.length > 60 62 | ? m.description.substring(0, 57) + '...' 63 | : m.description, 64 | ]), 65 | ]; 66 | 67 | // Show the table 68 | table(tableData); 69 | newline(); 70 | 71 | // Show usage instructions 72 | info('To add middleware to your project, run:'); 73 | command('burger-api add '); 74 | newline(); 75 | dim('Example: burger-api add cors logger rate-limiter'); 76 | newline(); 77 | } catch (err) { 78 | spin.stop('Failed to fetch middleware list', true); 79 | logError( 80 | err instanceof Error 81 | ? err.message 82 | : 'Could not connect to GitHub' 83 | ); 84 | newline(); 85 | info('Please check your internet connection and try again.'); 86 | process.exit(1); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /ecosystem/middlewares/timeout/timeout.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerRequest, BurgerNext } from 'burger-api'; 2 | 3 | /** 4 | * Configuration options for the timeout middleware. 5 | */ 6 | export interface TimeoutOptions { 7 | /** 8 | * Timeout duration in milliseconds. 9 | * @default 30000 (30 seconds) 10 | */ 11 | ms?: number; 12 | 13 | /** 14 | * Custom error handler for timeout. 15 | * If not provided, returns a default 408 response. 16 | * 17 | * @returns Response to send when timeout occurs 18 | */ 19 | onTimeout?: () => Response; 20 | 21 | /** 22 | * Custom message for timeout error. 23 | * @default 'Request timeout' 24 | */ 25 | message?: string; 26 | } 27 | 28 | /** 29 | * Creates a timeout middleware that detects slow requests. 30 | * 31 | * This middleware measures how long the handler takes to complete. 32 | * If it takes longer than the timeout, it returns a 408 response. 33 | * 34 | * Note: This detects timeouts AFTER the handler completes. 35 | * The handler will still run to completion even if it exceeds the timeout. 36 | * For true timeout enforcement that stops handlers mid-execution, 37 | * implement timeout logic inside your handlers using AbortSignal. 38 | * 39 | * @param options - Configuration options for timeout behavior 40 | * @returns A middleware function that detects slow requests 41 | * 42 | * @example 43 | * ```typescript 44 | * // Basic usage: detect requests taking longer than 30 seconds 45 | * const timeout = requestTimeout(); 46 | * 47 | * // Custom timeout duration 48 | * const timeout = requestTimeout({ ms: 5000 }); // 5 seconds 49 | * 50 | * // Custom error response 51 | * const timeout = requestTimeout({ 52 | * ms: 10000, 53 | * onTimeout: () => Response.json( 54 | * { error: 'Request took too long' }, 55 | * { status: 408 } 56 | * ) 57 | * }); 58 | * ``` 59 | */ 60 | export function requestTimeout(options: TimeoutOptions = {}): Middleware { 61 | const { 62 | ms = 30000, // 30 seconds 63 | onTimeout, 64 | message = 'Request timeout', 65 | } = options; 66 | 67 | return (req: BurgerRequest): BurgerNext => { 68 | // Start timer when middleware runs 69 | const startTime = Date.now(); 70 | 71 | // Return function to check timeout after handler completes 72 | return async (response: Response): Promise => { 73 | const duration = Date.now() - startTime; 74 | 75 | // If handler took too long, return timeout response 76 | if (duration > ms) { 77 | if (onTimeout) { 78 | return onTimeout(); 79 | } 80 | 81 | return Response.json( 82 | { 83 | error: 'Request Timeout', 84 | message, 85 | }, 86 | { 87 | status: 408, 88 | statusText: 'Request Timeout', 89 | } 90 | ); 91 | } 92 | 93 | // Handler completed in time, return normal response 94 | return response; 95 | }; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /packages/burger-api/examples/my-burger-api-app/index.ts: -------------------------------------------------------------------------------- 1 | import { Burger, setDir } from '../../src/index'; 2 | import { globalLogger, authGuard, rateLimiter, corsMiddleware } from './middleware'; 3 | 4 | // Create a new burger instance demonstrating a complete production application 5 | const burger = new Burger({ 6 | title: '🍔 My Burger API App - Production Ready Demo', 7 | description: ` 8 | A complete demonstration of a production-ready BurgerAPI application. 9 | 10 | ## ✨ What This Example Shows: 11 | - **Full Application Structure**: Complete API with all features 12 | - **Production Middleware**: Auth, rate limiting, CORS, logging 13 | - **Real-World Patterns**: How to structure a complete application 14 | - **Best Practices**: Production-ready configuration and setup 15 | - **Scalable Architecture**: Foundation for growing applications 16 | 17 | ## 🚀 Key Concepts: 18 | 1. **Complete Setup**: All middleware and features configured 19 | 2. **Production Ready**: Security, monitoring, and performance features 20 | 3. **Real-World Usage**: How to build actual applications 21 | 4. **Best Practices**: Industry-standard patterns and configurations 22 | 5. **Scalability**: Architecture that grows with your needs 23 | 24 | Perfect for developers building real-world applications! 25 | `, 26 | version: '2.0.0', 27 | apiDir: setDir(__dirname, 'api'), 28 | apiPrefix: 'api', 29 | globalMiddleware: [globalLogger, corsMiddleware, rateLimiter, authGuard], 30 | debug: true, 31 | }); 32 | 33 | const port = 5000; 34 | 35 | // Start the server with comprehensive production information 36 | burger.serve(port, () => { 37 | console.log('🍔 My Burger API App - Production Ready Demo is running!'); 38 | console.log('========================================================='); 39 | console.log(''); 40 | console.log('📖 API Documentation: http://localhost:5000/docs'); 41 | console.log('🔗 OpenAPI Spec: http://localhost:5000/openapi.json'); 42 | console.log(''); 43 | console.log('🏗️ Production Features Enabled:'); 44 | console.log(''); 45 | console.log('🔐 Security & Authentication:'); 46 | console.log(' • JWT token validation'); 47 | console.log(' • Role-based access control'); 48 | console.log(' • Secure headers and CORS'); 49 | console.log(''); 50 | console.log('⚡ Performance & Monitoring:'); 51 | console.log(' • Rate limiting (100 req/min)'); 52 | console.log(' • Request logging and tracking'); 53 | console.log(' • Performance monitoring'); 54 | console.log(''); 55 | console.log('🌐 Cross-Origin Support:'); 56 | console.log(' • CORS configuration'); 57 | console.log(' • Preflight request handling'); 58 | console.log(' • Credentials support'); 59 | console.log(''); 60 | console.log('📊 Application Structure:'); 61 | console.log(' • Organized API endpoints'); 62 | console.log(' • Middleware pipeline'); 63 | console.log(' • Error handling'); 64 | console.log(' • Validation schemas'); 65 | console.log(''); 66 | console.log('💡 Production Best Practices:'); 67 | console.log(' • Environment-based configuration'); 68 | console.log(' • Structured logging'); 69 | console.log(' • Error tracking'); 70 | console.log(' • Security headers'); 71 | console.log(''); 72 | console.log('🚀 Ready for production deployment!'); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/cli/src/commands/serve.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Serve Command 3 | * 4 | * Runs a development server with hot reload (auto-restart on file changes). 5 | * This is perfect for development - just edit your code and see changes instantly! 6 | * 7 | * Example: burger-api serve 8 | * Example: burger-api serve --port 4000 9 | */ 10 | 11 | import { Command } from 'commander'; 12 | import { existsSync } from 'fs'; 13 | import { 14 | success, 15 | error as logError, 16 | info, 17 | newline, 18 | highlight, 19 | dim, 20 | } from '../utils/logger'; 21 | 22 | /** 23 | * Serve command options 24 | */ 25 | interface ServeCommandOptions { 26 | port: string; 27 | file: string; 28 | } 29 | 30 | /** 31 | * Create the "serve" command 32 | * Starts a development server with hot reload 33 | */ 34 | export const serveCommand = new Command('serve') 35 | .description('Start development server with hot reload') 36 | .option('-p, --port ', 'Port to run the server on', '4000') 37 | .option('-f, --file ', 'Entry file to run', 'src/index.ts') 38 | .action(async (options: ServeCommandOptions) => { 39 | const file = options.file; 40 | const port = options.port; 41 | 42 | // Check if the entry file exists 43 | if (!existsSync(file)) { 44 | logError(`Entry file not found: ${file}`); 45 | info('Make sure you are in the project directory.'); 46 | process.exit(1); 47 | } 48 | 49 | // Show startup message 50 | newline(); 51 | info('Starting development server...'); 52 | newline(); 53 | success(`Server running on ${highlight(`http://localhost:${port}`)}`); 54 | info('Press Ctrl+C to stop'); 55 | dim('File changes will automatically restart the server'); 56 | newline(); 57 | 58 | try { 59 | // Run bun with --watch flag for hot reload 60 | // We use --watch to automatically restart when files change 61 | const proc = Bun.spawn(['bun', '--watch', file], { 62 | stdout: 'inherit', // Show output in the terminal 63 | stderr: 'inherit', // Show errors in the terminal 64 | stdin: 'inherit', // Allow user input 65 | env: { 66 | ...process.env, 67 | PORT: port, // Pass port as environment variable 68 | }, 69 | }); 70 | 71 | // Handle Ctrl+C gracefully 72 | process.on('SIGINT', () => { 73 | newline(); 74 | info('Shutting down server...'); 75 | proc.kill(); 76 | process.exit(0); 77 | }); 78 | 79 | // Handle Ctrl+Break on Windows 80 | process.on('SIGBREAK', () => { 81 | newline(); 82 | info('Shutting down server...'); 83 | proc.kill(); 84 | process.exit(0); 85 | }); 86 | 87 | // Wait for the process to exit 88 | const exitCode = await proc.exited; 89 | 90 | if (exitCode !== 0) { 91 | logError('Server stopped unexpectedly'); 92 | process.exit(exitCode); 93 | } 94 | } catch (err) { 95 | logError( 96 | err instanceof Error ? err.message : 'Failed to start server' 97 | ); 98 | process.exit(1); 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "configVersion": 0, 4 | "workspaces": { 5 | "": { 6 | "name": "burger-api", 7 | }, 8 | "packages/burger-api": { 9 | "name": "burger-api", 10 | "version": "0.6.2", 11 | "dependencies": { 12 | "zod": "^4.0.17", 13 | }, 14 | "devDependencies": { 15 | "@types/bun": "latest", 16 | }, 17 | "peerDependencies": { 18 | "typescript": "^5.7.3", 19 | }, 20 | }, 21 | "packages/cli": { 22 | "name": "@burger-api/cli", 23 | "version": "0.1.0", 24 | "bin": { 25 | "burger-api": "./src/index.ts", 26 | }, 27 | "dependencies": { 28 | "@clack/prompts": "^0.7.0", 29 | "commander": "^11.1.0", 30 | }, 31 | "devDependencies": { 32 | "@types/bun": "latest", 33 | "@types/commander": "^2.12.5", 34 | }, 35 | "peerDependencies": { 36 | "typescript": "^5", 37 | }, 38 | }, 39 | }, 40 | "packages": { 41 | "@burger-api/cli": ["@burger-api/cli@workspace:packages/cli"], 42 | 43 | "@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="], 44 | 45 | "@clack/prompts": ["@clack/prompts@0.7.0", "", { "dependencies": { "@clack/core": "^0.3.3", "is-unicode-supported": "*", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA=="], 46 | 47 | "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], 48 | 49 | "@types/commander": ["@types/commander@2.12.5", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="], 50 | 51 | "@types/node": ["@types/node@22.13.4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="], 52 | 53 | "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], 54 | 55 | "burger-api": ["burger-api@workspace:packages/burger-api"], 56 | 57 | "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], 58 | 59 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 60 | 61 | "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 62 | 63 | "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], 64 | 65 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 66 | 67 | "zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], 68 | 69 | "@clack/prompts/is-unicode-supported": ["is-unicode-supported@2.1.0", "", { "bundled": true }, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/burger-api/src/utils/wildcard.ts: -------------------------------------------------------------------------------- 1 | import type { BurgerRequest } from '../types/index'; 2 | 3 | /** 4 | * Extracts the pathname from a full URL string, removing query parameters. 5 | * @param url - The full URL string (e.g., "http://localhost:4000/api/users/123/profile?id=1") 6 | * @returns The extracted pathname (e.g., "/api/users/123/profile") 7 | */ 8 | export function extractPathnameFromUrl(url: string): string { 9 | // Find where the path starts (after protocol and domain) 10 | // Example: "http://localhost:4000/api/..." → protocolEnd = 4 (after "http") 11 | const protocolEnd = url.indexOf('://'); 12 | 13 | // Find first "/" after the domain 14 | // Example: "http://localhost:4000/api/..." → pathStart = 21 (the "/" before "api") 15 | const pathStart = url.indexOf('/', protocolEnd + 3); 16 | 17 | // Find where query parameters start (if any) 18 | // Example: "/api/users/123?id=1" → pathEnd = 15 (at the "?") 19 | const pathEnd = url.indexOf('?', pathStart); 20 | 21 | // Extract just the pathname without query parameters 22 | // Example: "/api/users/123/profile" 23 | return pathEnd === -1 24 | ? url.substring(pathStart) // No query params 25 | : url.substring(pathStart, pathEnd); // Has query params, stop at "?" 26 | } 27 | 28 | /** 29 | * Extracts wildcard parameters from a request pathname and sets them on the request object. 30 | * @param request - The request object to modify 31 | * @param pathname - The pathname extracted from the URL (e.g., "/api/users/123/profile") 32 | * @param baseSegmentCount - Number of segments before the wildcard (e.g., 3 for "/api/users/:userId/*") 33 | */ 34 | export function extractWildcardParams( 35 | request: BurgerRequest, 36 | pathname: string, 37 | baseSegmentCount: number 38 | ): void { 39 | if (baseSegmentCount === 0) { 40 | // Fast path: The entire path is wildcard 41 | // Example: "/api/docs/*" where all segments after domain are wildcards 42 | // Just split everything and we're done! 43 | request.wildcardParams = pathname.split('/').filter(Boolean); 44 | } else { 45 | // Optimized path: Skip base segments and only collect wildcard segments 46 | // Example: "/api/users/123/settings/privacy" 47 | // - We need to skip 3 segments (api, users, 123) 48 | // - Only collect: ["settings", "privacy"] 49 | 50 | const wildcardParams: string[] = []; 51 | let segmentCount = 0; // Counts how many segments we've seen 52 | let start = pathname[0] === '/' ? 1 : 0; // Start after leading "/" if present 53 | 54 | // Loop through each character in the pathname 55 | // When we hit "/" or end of string, we know we found a segment 56 | for (let i = start; i <= pathname.length; i++) { 57 | // Found end of a segment (either "/" or end of string) 58 | if (i === pathname.length || pathname[i] === '/') { 59 | // Make sure it's not an empty segment (from double slashes) 60 | if (i > start) { 61 | // Only save segments AFTER we've skipped the base segments 62 | // Example: If baseSegmentCount=3, save segment 3, 4, 5, etc. 63 | if (segmentCount >= baseSegmentCount) { 64 | // Extract this segment from the pathname 65 | // Example: pathname.substring(5, 13) might give "settings" 66 | wildcardParams.push(pathname.substring(start, i)); 67 | } 68 | segmentCount++; // Increment count for next segment 69 | } 70 | start = i + 1; // Move start position to character after "/" 71 | } 72 | } 73 | 74 | // Done! wildcardParams now contains only the wildcard segments 75 | // Example: ["settings", "privacy"] instead of ["api", "users", "123", "settings", "privacy"] 76 | request.wildcardParams = wildcardParams; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | .vscode/settings.json 177 | 178 | # Dependencies (includes workspace packages) 179 | node_modules/ 180 | .pnp/ 181 | .pnp.js 182 | 183 | # Build outputs 184 | dist/ 185 | build/ 186 | *.tsbuildinfo 187 | 188 | # Environment files 189 | .env 190 | .env.local 191 | .env.development.local 192 | .env.test.local 193 | .env.production.local 194 | 195 | # Logs 196 | logs/ 197 | *.log 198 | npm-debug.log* 199 | yarn-debug.log* 200 | yarn-error.log* 201 | 202 | # Editor directories and files 203 | .idea/ 204 | .vscode/ 205 | *.suo 206 | *.ntvs* 207 | *.njsproj 208 | *.sln 209 | *.sw? 210 | 211 | # OS generated files 212 | .DS_Store 213 | .DS_Store? 214 | ._* 215 | .Spotlight-V100 216 | .Trashes 217 | ehthumbs.db 218 | Thumbs.db 219 | 220 | # Bun 221 | bun.lockb 222 | bun-experiment/* 223 | 224 | examples/my-burger-api-app/* 225 | -------------------------------------------------------------------------------- /packages/burger-api/src/utils/routing.ts: -------------------------------------------------------------------------------- 1 | import type { PageDefinition, RouteDefinition, TrieNode } from '../types/index'; 2 | 3 | /** 4 | * Constants for route handling. 5 | */ 6 | export const ROUTE_CONSTANTS = { 7 | // Supported page extensions 8 | SUPPORTED_PAGE_EXTENSIONS: ['.tsx', '.html'], 9 | // Page index files 10 | PAGE_INDEX_FILES: ['index.tsx', 'index.html'], 11 | // Dynamic route constants 12 | DYNAMIC_SEGMENT_PREFIX: ':', 13 | DYNAMIC_FOLDER_START: '[', 14 | DYNAMIC_FOLDER_END: ']', 15 | // Grouping folder constants 16 | GROUPING_FOLDER_START: '(', 17 | GROUPING_FOLDER_END: ')', 18 | // Wildcard route constants 19 | WILDCARD_SEGMENT_PREFIX: '*', 20 | WILDCARD_SIMPLE: '[...]', 21 | WILDCARD_START: '[...', 22 | }; 23 | 24 | /** 25 | * Supported HTTP methods 26 | */ 27 | export const HTTP_METHODS = [ 28 | 'GET', 29 | 'POST', 30 | 'PUT', 31 | 'DELETE', 32 | 'PATCH', 33 | 'HEAD', 34 | 'OPTIONS', 35 | ]; 36 | 37 | /** 38 | * Calculates the specificity of a route path based on the number of static segments. 39 | * Static segments increase the score, while dynamic segments (:param) do not. 40 | * Wildcard segments (*) have the lowest specificity (highest penalty). 41 | * @param path The route path to evaluate. 42 | * @returns The specificity score (higher means more static segments, lower priority for wildcard). 43 | */ 44 | export const getRouteSpecificity = (path: string): number => { 45 | const segments = path.split('/').filter(Boolean); 46 | return segments.reduce((score, segment) => { 47 | if (segment.startsWith(ROUTE_CONSTANTS.WILDCARD_SEGMENT_PREFIX)) { 48 | return score + 1000; // Wildcard routes get highest penalty (lowest priority) 49 | } 50 | return segment.startsWith(ROUTE_CONSTANTS.DYNAMIC_SEGMENT_PREFIX) 51 | ? score + 100 // Dynamic routes get medium penalty 52 | : score + 1; // Static routes get minimal penalty 53 | }, 0); 54 | }; 55 | 56 | /** 57 | * Compares two routes for sorting, prioritizing those with higher specificity (more static segments). 58 | * Route prioritization: Static > Dynamic > Wildcard. 59 | * If specificity is equal, sorts alphabetically by path. 60 | * @param a The first route to compare. 61 | * @param b The second route to compare. 62 | * @returns Negative if a comes before b, positive if b comes before a, zero if equal. 63 | */ 64 | export const compareRoutes = ( 65 | a: PageDefinition | RouteDefinition, 66 | b: PageDefinition | RouteDefinition 67 | ): number => { 68 | const aSpecificity = getRouteSpecificity(a.path); 69 | const bSpecificity = getRouteSpecificity(b.path); 70 | 71 | if (aSpecificity > bSpecificity) return -1; 72 | if (aSpecificity < bSpecificity) return 1; 73 | return a.path.localeCompare(b.path); 74 | }; 75 | 76 | /** 77 | * Collects all routes from the trie and returns them as an array of RouteDefinition objects. 78 | * @param node The current node in the trie. 79 | * @param currentPath The current path being traversed. 80 | * @param routes The array of collected routes. 81 | * @returns An array of RouteDefinition objects. 82 | */ 83 | export function collectRoutes( 84 | node: TrieNode, 85 | currentPath: string = '', 86 | routes: RouteDefinition[] = [] 87 | ): RouteDefinition[] { 88 | // If this node has a route definition, add it 89 | if (node.route) { 90 | routes.push({ 91 | ...node.route, 92 | path: currentPath, 93 | }); 94 | } 95 | 96 | // Traverse static children 97 | node.children.forEach((child: TrieNode, segment: string) => { 98 | collectRoutes(child, `${currentPath}/${segment}`, routes); 99 | }); 100 | 101 | // Traverse dynamic child if exists 102 | if (node.paramChild) { 103 | const paramPath = `${currentPath}/:${node.paramChild.paramName}`; 104 | collectRoutes(node.paramChild, paramPath, routes); 105 | } 106 | 107 | // Traverse wildcard child if exists (lowest priority) 108 | if (node.wildcardChild) { 109 | // const wildcardPath = `${currentPath}/${node.wildcardChild.wildcardParamName}`; 110 | const wildcardPath = `${currentPath}/${ROUTE_CONSTANTS.WILDCARD_SEGMENT_PREFIX}`; 111 | collectRoutes(node.wildcardChild, wildcardPath, routes); 112 | } 113 | 114 | return routes; 115 | } 116 | -------------------------------------------------------------------------------- /ecosystem/README.md: -------------------------------------------------------------------------------- 1 | # Burger API Ecosystem 2 | 3 | This folder contains official extensions and tools for the Burger API framework. 4 | 5 | ## 📁 Standardized Project Structure 6 | 7 | To maintain consistency and organization across all Burger API projects, we 8 | recommend the following folder structure: 9 | 10 | ``` 11 | my-burger-app/ 12 | ├── ecosystem/ # Official middleware collection (copy from burger-api) 13 | │ └── middlewares/ 14 | │ ├── cors/ 15 | │ ├── logger/ 16 | │ ├── rate-limiter/ 17 | │ └── ... 18 | ├── middleware/ # Your middleware configuration 19 | │ ├── global/ 20 | │ │ └── index.ts # Export array of global middleware 21 | │ ├── route-specific/ # Route-specific middleware 22 | │ │ └── auth.ts 23 | │ └── custom/ # Your custom middleware 24 | │ └── my-middleware.ts 25 | └── index.ts # Main app entry 26 | ``` 27 | 28 | ## 🚀 Quick Setup 29 | 30 | ### Step 1: Copy Ecosystem 31 | 32 | ```bash 33 | # Copy the entire ecosystem folder to your project 34 | cp -r burger-api/ecosystem ./ 35 | ``` 36 | 37 | ### Step 2: Create Middleware Structure 38 | 39 | ```bash 40 | # Create the recommended middleware folder structure 41 | mkdir -p middleware/{global,route-specific,custom} 42 | ``` 43 | 44 | ### Step 3: Configure Global Middleware 45 | 46 | Create `middleware/global/index.ts`: 47 | 48 | ```typescript 49 | import { cors } from '../../ecosystem/middlewares/cors/cors'; 50 | import { logger } from '../../ecosystem/middlewares/logger/logger'; 51 | import { rateLimit } from '../../ecosystem/middlewares/rate-limiter/rate-limiter'; 52 | 53 | export const globalMiddleware = [ 54 | logger({ 55 | level: 'info', 56 | format: 'combined', 57 | }), 58 | cors({ 59 | origin: 60 | process.env.NODE_ENV === 'production' 61 | ? ['https://example.com'] 62 | : '*', 63 | credentials: true, 64 | debug: process.env.NODE_ENV !== 'production', 65 | }), 66 | rateLimit({ 67 | windowMs: 60000, 68 | maxRequests: 100, 69 | }), 70 | ]; 71 | ``` 72 | 73 | ### Step 4: Use in Your App 74 | 75 | ```typescript 76 | // index.ts 77 | import { Burger } from 'burger-api'; 78 | import { globalMiddleware } from './middleware/global'; 79 | 80 | const app = new Burger({ 81 | apiDir: './api', 82 | globalMiddleware, // Clean and organized! 83 | }); 84 | 85 | app.serve(4000); 86 | ``` 87 | 88 | ## 📦 Available Packages 89 | 90 | ### Middleware Collection 91 | 92 | Located in `./middlewares/` - A comprehensive collection of production-ready 93 | middleware for Burger API applications. 94 | 95 | **Features:** 96 | 97 | - ✅ Production-ready middleware (CORS, Rate Limiter, Logger, etc.) 98 | - ✅ Optimized for Bun.js v1.3.1+ with automatic fallbacks 99 | - ✅ Comprehensive documentation and testing guides 100 | - ✅ Copy & paste approach for easy customization 101 | 102 | ## 🎯 Benefits of This Structure 103 | 104 | ### **Organization** 105 | 106 | - ✅ **Clean separation** of ecosystem vs custom middleware 107 | - ✅ **Easy management** of global middleware in one place 108 | - ✅ **Scalable** for large applications 109 | - ✅ **Consistent** across all Burger API projects 110 | 111 | ### **Development Experience** 112 | 113 | - ✅ **Easy to enable/disable** middleware by editing one file 114 | - ✅ **Clear separation** of concerns 115 | - ✅ **Future CLI-ready** structure 116 | - ✅ **Easy to maintain** and update 117 | 118 | ### **Performance** 119 | 120 | - ✅ **Pre-computed optimizations** in all middleware 121 | - ✅ **Minimal overhead** with efficient implementations 122 | - ✅ **Production-ready** configurations 123 | 124 | ## 📚 Documentation 125 | 126 | - [Middleware Collection](./middlewares/README.md) - Complete middleware 127 | documentation 128 | - [Testing Guide](./middlewares/TESTING.md) - Manual testing instructions 129 | - [Burger API Framework](https://burger-api.com) - Main framework 130 | documentation 131 | 132 | ## 🤝 Contributing 133 | 134 | Want to contribute to the ecosystem? Follow these guidelines: 135 | 136 | 1. Each package in its own folder within `./middlewares/` 137 | 2. Include implementation files and comprehensive README.md 138 | 3. Follow the established patterns and conventions 139 | 4. Include tests and examples 140 | 5. Use the standardized folder structure 141 | 142 | ## 📄 License 143 | 144 | MIT License - feel free to use these packages in your projects! 145 | 146 | --- 147 | 148 | **Built with ❤️ for the Burger API community** 149 | -------------------------------------------------------------------------------- /packages/burger-api/src/utils/error.ts: -------------------------------------------------------------------------------- 1 | import { normalizePath } from './index'; 2 | 3 | /** 4 | * Generates an HTTP error response based on the request's "Accept" header. 5 | * 6 | * If the "Accept" header includes "text/html" and debugging is enabled, it returns 7 | * an HTML response with the error message and stack trace. 8 | * 9 | * If the "Accept" header includes "application/json" or debugging is enabled, it 10 | * returns a JSON response with the error message and optionally the stack trace. 11 | * 12 | * Otherwise, it returns a plain text response indicating an internal server error. 13 | * 14 | * @param error - The error object or message to include in the response. 15 | * @param req - The incoming HTTP request object. 16 | * @param debug - A boolean indicating whether debugging information should be included. 17 | * @returns A Response object with the appropriate error details and status code. 18 | */ 19 | export function errorResponse( 20 | error: any, 21 | req: Request, 22 | debug: boolean 23 | ): Response { 24 | // Extract request details 25 | const method = req.method; 26 | const url = req.url; 27 | const logPrefix = `[${method} ${url}]`; 28 | 29 | // Log detailed error context 30 | console.error(`${logPrefix} Error:`, error); 31 | 32 | const acceptHeader = req.headers.get('accept') || ''; 33 | if (acceptHeader.includes('text/html') && debug) { 34 | const body = `Error Occurred - BurgerAPI
Whoops! Something went wrong
Debug Mode Enabled
Request: ${method} ${url}
${ 35 | error.stack?.split('\n')[0].split(':')[0] 36 | }

${ 37 | error.message 38 | }

Stack Trace

${
39 |             error.stack || 'No stack trace available'
40 |         }
41 | `; 42 | 43 | // Return the html response 44 | return new Response(body, { 45 | status: 500, 46 | headers: { 'Content-Type': 'text/html' }, 47 | }); 48 | } else if (debug) { 49 | // Log detailed error context 50 | console.error(`${logPrefix} Error:`, error); 51 | 52 | // Create a JSON response 53 | const body = JSON.stringify({ 54 | request: `${method} ${url}`, 55 | error: error instanceof Error ? error.message : 'Unknown error', 56 | stack: 57 | error instanceof Error ? normalizePath(error.stack || '') : '', 58 | }); 59 | 60 | // Return the JSON response 61 | return new Response(body, { 62 | status: 500, 63 | headers: { 'Content-Type': 'application/json' }, 64 | }); 65 | } else { 66 | // Log detailed error context 67 | console.error(`${logPrefix} Error:`, error); 68 | 69 | // Return a plain text response 70 | return new Response('Internal Server Error', { status: 500 }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/burger-api/examples/nested-dynamic-routes/README.md: -------------------------------------------------------------------------------- 1 | # Nested Dynamic Routes Example 2 | 3 | This example demonstrates nested dynamic routes in burger-api, showing how to 4 | create routes with multiple dynamic segments and parameter validation. 5 | 6 | ## Overview 7 | 8 | This example includes: 9 | 10 | - **Static routes** - `/api/users` 11 | - **Dynamic routes** - `/api/users/:userId` 12 | - **Nested dynamic routes** - `/api/users/:userId/posts/:postId` 13 | - **Parameter validation** - Using Zod schemas to validate route parameters 14 | 15 | ## Features Demonstrated 16 | 17 | ### 1. Static Routes 18 | 19 | - `/api/users` - Static route for users list 20 | 21 | ### 2. Dynamic Routes 22 | 23 | - `/api/users/:userId` - Dynamic route with single parameter 24 | - Validates `userId` parameter (string, min 1 character) 25 | 26 | ### 3. Nested Dynamic Routes 27 | 28 | - `/api/users/:userId/posts/:postId` - Nested dynamic route with multiple 29 | parameters 30 | - Validates both `userId` and `postId` parameters 31 | - Demonstrates route priority (nested routes take precedence) 32 | 33 | ## Running the Example 34 | 35 | ### Step 1: Start the Server 36 | 37 | In your terminal, navigate to the project root and run: 38 | 39 | ```bash 40 | bun run examples/nested-dynamic-routes/index.ts 41 | ``` 42 | 43 | You should see: 44 | 45 | ``` 46 | Loading route: /api/users 47 | Loading route: /api/users/:userId 48 | Loading route: /api/users/:userId/posts/:postId 49 | 🍔 BurgerAPI is running at: http://localhost:4000 50 | ``` 51 | 52 | ### Step 2: Test the API 53 | 54 | Open another terminal and test the endpoints: 55 | 56 | ```bash 57 | # Get users list 58 | curl http://localhost:4000/api/users 59 | 60 | # Get user by ID 61 | curl http://localhost:4000/api/users/1 62 | 63 | # Get post by user and post ID 64 | curl http://localhost:4000/api/users/1/posts/100 65 | ``` 66 | 67 | ## Running Tests 68 | 69 | This example includes a comprehensive test suite using Bun's built-in test 70 | runner. 71 | 72 | ### Prerequisites 73 | 74 | 1. **Start the server first** (in one terminal): 75 | 76 | ```bash 77 | bun run examples/nested-dynamic-routes/index.ts 78 | ``` 79 | 80 | 2. **Run tests** (in another terminal): 81 | 82 | ```bash 83 | bun test examples/nested-dynamic-routes/api.test.ts 84 | ``` 85 | 86 | ### Test Commands 87 | 88 | #### Run All Tests 89 | 90 | ```bash 91 | bun test examples/nested-dynamic-routes/api.test.ts 92 | ``` 93 | 94 | #### Run Tests in Watch Mode 95 | 96 | ```bash 97 | bun test --watch examples/nested-dynamic-routes/api.test.ts 98 | ``` 99 | 100 | #### Run Specific Test Group 101 | 102 | ```bash 103 | # Run only Users tests 104 | bun test --test-name-pattern "Users" examples/nested-dynamic-routes/api.test.ts 105 | 106 | # Run only Posts tests 107 | bun test --test-name-pattern "posts" examples/nested-dynamic-routes/api.test.ts 108 | ``` 109 | 110 | ## Test Coverage 111 | 112 | The test suite includes **13 tests** covering: 113 | 114 | ### ✅ Users API (7 tests) 115 | 116 | - GET users list 117 | - GET user by ID (valid) 118 | - Different user IDs 119 | - Empty user ID validation 120 | 121 | ### ✅ Nested Posts API (5 tests) 122 | 123 | - GET post with valid IDs 124 | - Different user/post ID combinations 125 | - Empty user ID validation 126 | - Empty post ID validation 127 | - Special characters in IDs 128 | 129 | ### ✅ Route Priority (2 tests) 130 | 131 | - Static route priority 132 | - Nested route priority 133 | 134 | ### ✅ Error Handling (2 tests) 135 | 136 | - 404 for non-existent routes 137 | - 404 for invalid nested routes 138 | 139 | ## API Endpoints 140 | 141 | | Method | Endpoint | Description | Validation | 142 | | ------ | --------------------------------- | ------------------------------ | ----------------------------- | 143 | | GET | `/api/users` | Get users list | None | 144 | | GET | `/api/users/:userId` | Get user by ID | `userId` (string, min 1) | 145 | | GET | `/api/users/:userId/posts/:postId` | Get post by user and post ID | `userId` (string, min 1), `postId` (string, min 1) | 146 | 147 | ## Route Priority 148 | 149 | Routes are matched in the following priority order: 150 | 151 | 1. **Static routes** (highest priority) 152 | - `/api/users` matches before `/api/users/:userId` 153 | 154 | 2. **Nested dynamic routes** 155 | - `/api/users/:userId/posts/:postId` matches before `/api/users/:userId` 156 | 157 | 3. **Dynamic routes** (lowest priority) 158 | - `/api/users/:userId` matches when no exact route exists 159 | 160 | ## File Structure 161 | 162 | ``` 163 | nested-dynamic-routes/ 164 | ├── README.md # This file 165 | ├── index.ts # Server entry point 166 | ├── api.test.ts # Test suite 167 | └── api/ # API routes 168 | └── users/ 169 | ├── route.ts # GET /api/users 170 | ├── [userId]/ 171 | │ ├── route.ts # GET /api/users/:userId 172 | │ └── posts/ 173 | │ └── [postId]/ 174 | │ └── route.ts # GET /api/users/:userId/posts/:postId 175 | ``` 176 | 177 | ## Key Concepts 178 | 179 | 1. **Nested Dynamic Routes**: Multiple dynamic segments in a single route 180 | 2. **Parameter Validation**: Using Zod schemas to validate route parameters 181 | 3. **Route Priority**: Understanding how routes are matched 182 | 4. **Type Safety**: TypeScript types inferred from Zod schemas 183 | 5. **File Structure**: How folder structure maps to route paths 184 | 185 | ## Troubleshooting 186 | 187 | ### Server Not Running Error 188 | 189 | If you see: 190 | 191 | ``` 192 | ❌ Server is not running! 193 | ``` 194 | 195 | **Solution**: Start the server first: 196 | 197 | ```bash 198 | bun run examples/nested-dynamic-routes/index.ts 199 | ``` 200 | 201 | ### Route Not Matching 202 | 203 | If routes are not matching correctly: 204 | 205 | 1. **Check route priority**: Static routes match before dynamic routes 206 | 2. **Check nested routes**: Nested routes match before parent routes 207 | 3. **Check parameter validation**: Ensure parameters meet validation requirements 208 | 4. **Check file structure**: Ensure files are in correct locations 209 | 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | BurgerAPI logo 3 |

4 | 5 |

6 | A modern, high-performance API framework built on Bun.js 7 |

8 | 9 |

10 | 11 | Under Development 12 | 13 | 14 | License 15 | 16 | 17 | Bun 18 | 19 | 20 | Documentation 21 | 22 |

23 | 24 | ## 📖 About 25 | 26 | This monorepo contains the **burger-api** ecosystem - a modern, open-source API 27 | framework built on [Bun.js](https://bun.sh). The framework combines the 28 | simplicity of file-based routing with powerful features like built-in 29 | middleware, Zod-based schema validation, and automatic OpenAPI generation. 30 | 31 | **This project is under active development and should not be used in production 32 | yet.** 33 | 34 | ## 📦 Packages 35 | 36 | This monorepo is organized into the following packages: 37 | 38 | ### 🚀 [`packages/burger-api`](./packages/burger-api) 39 | 40 | The core **burger-api** framework package. This is the main framework that gets 41 | published to npm. 42 | 43 | #### ✨ Key Features 44 | 45 | - ⚡ **Bun-Native Performance** - Leverages Bun's high-performance HTTP server 46 | - 📁 **File-Based Routing** - Automatically registers API routes from your 47 | file structure 48 | - 🚀 **Optimized Middleware** - Specialized fast paths for 0, 1, 2, and 3+ 49 | middlewares 50 | - ✅ **Type-Safe Validation** - Utilizes Zod for request validation with full 51 | type safety 52 | - 📚 **Automatic OpenAPI Generation** - Generates complete OpenAPI 3.0 53 | specifications 54 | - 🔍 **Swagger UI Integration** - Out-of-the-box Swagger UI endpoint for 55 | interactive API docs 56 | - 🎯 **Developer Friendly** - Simple, clear middleware patterns that are easy 57 | to understand 58 | 59 | ### 🛠️ [`packages/cli`](./packages/cli) 60 | 61 | The **Burger API CLI** tool for scaffolding new burger-api projects and managing 62 | your development workflow. 63 | 64 | #### ✨ Key Features 65 | 66 | - 🚀 **Project Scaffolding** - Create new burger-api projects with interactive 67 | prompts 68 | - 📦 **Middleware Management** - Browse and add middleware from the ecosystem 69 | - 🔨 **Build Tools** - Bundle projects or compile to standalone executables 70 | - 🔥 **Development Server** - Hot reload development server with auto-restart 71 | - 🎯 **Zero Dependencies** - Uses Bun's native APIs for file operations and 72 | downloads 73 | - 💻 **Cross-Platform** - Works on Windows, macOS, and Linux 74 | 75 | #### 📥 Installation 76 | 77 | Install the CLI tool from [burger-api.com](https://burger-api.com): 78 | 79 | **macOS, Linux, WSL:** 80 | ```bash 81 | curl -fsSL https://burger-api.com/install.sh | bash 82 | ``` 83 | 84 | **Windows PowerShell:** 85 | ```powershell 86 | irm https://burger-api.com/install.ps1 | iex 87 | ``` 88 | 89 | For detailed documentation, see 90 | [`packages/cli/README.md`](./packages/cli/README.md). 91 | 92 | ## 🚀 Quick Start 93 | 94 | ### Prerequisites 95 | 96 | - [Bun](https://bun.sh) installed (version 1.2.20 or later) 97 | 98 | ### Installation 99 | 100 | Install dependencies for all packages: 101 | 102 | ```bash 103 | bun install 104 | ``` 105 | 106 | ### Development 107 | 108 | #### Work on Individual Packages 109 | 110 | ```bash 111 | # Work on burger-api framework 112 | cd packages/burger-api 113 | 114 | # Work on CLI 115 | cd packages/cli 116 | ``` 117 | 118 | #### Use Workspace Commands from Root 119 | 120 | ```bash 121 | # Typecheck burger-api 122 | bun run typecheck 123 | 124 | # Build burger-api 125 | bun run build 126 | 127 | # Test burger-api 128 | bun run test 129 | 130 | # Run burger-api dev server 131 | bun run dev 132 | ``` 133 | 134 | ## 📚 Documentation 135 | 136 | - **Framework Documentation:** 137 | [`packages/burger-api/README.md`](./packages/burger-api/README.md) 138 | - **Official Website:** [burger-api.com](https://burger-api.com/) 139 | - **Publishing Guide:** 140 | [`packages/burger-api/PUBLISHING.md`](./packages/burger-api/PUBLISHING.md) 141 | 142 | ## 🏗️ Project Structure 143 | 144 | ``` 145 | burger-api/ 146 | ├── packages/ 147 | │ ├── burger-api/ # Core framework (published to npm) 148 | │ │ ├── src/ # Source code 149 | │ │ ├── examples/ # Example projects 150 | │ │ └── dist/ # Build output 151 | │ └── cli/ # CLI tool (under development) 152 | │ └── src/ # CLI source code 153 | ├── ecosystem/ # Middleware templates (ready-to-use) 154 | ├── package.json # Workspace root configuration 155 | └── README.md # This file 156 | ``` 157 | 158 | ## 🤝 Contributing 159 | 160 | We welcome contributions from the community! Whether it's: 161 | 162 | - 🐛 Reporting bugs 163 | - 💡 Suggesting features 164 | - 📝 Improving documentation 165 | - 🔧 Submitting pull requests 166 | 167 | Please feel free to open an issue or submit a pull request. Let's build 168 | something amazing together! 169 | 170 | **Contributing Guidelines:** 171 | 172 | - Check existing issues before creating new ones 173 | - Follow the existing code style 174 | - Add tests for new features 175 | - Update documentation as needed 176 | 177 | ## 📄 License 178 | 179 | This project is licensed under the **MIT License** - see the 180 | [LICENSE](./packages/burger-api/LICENSE) file for details. 181 | 182 | The MIT License is a permissive license that allows people to do anything with 183 | your code as long as they provide attribution back to you and don't hold you 184 | liable. 185 | 186 | ## 🔗 Links 187 | 188 | - **Website:** [burger-api.com](https://burger-api.com/) 189 | - **GitHub:** 190 | [github.com/isfhan/burger-api](https://github.com/isfhan/burger-api) 191 | - **Issues:** 192 | [github.com/isfhan/burger-api/issues](https://github.com/isfhan/burger-api/issues) 193 | - **Bun.js:** [bun.sh](https://bun.sh) 194 | 195 | --- 196 | 197 |

198 | Made with ❤️ for the Bun.js community by Isfhan Ahmed 199 |

200 | -------------------------------------------------------------------------------- /packages/burger-api/examples/error-handling/README.md: -------------------------------------------------------------------------------- 1 | # Error Handling Example 2 | 3 | This example demonstrates error handling and validation in burger-api, showing 4 | how to handle validation errors, invalid requests, and edge cases. 5 | 6 | ## Overview 7 | 8 | This example includes: 9 | 10 | - **Product API** with validation 11 | - **Error handling** for invalid requests 12 | - **Zod validation** for request body and parameters 13 | - **Route-specific middleware** for logging 14 | 15 | ## Features Demonstrated 16 | 17 | ### 1. Product Creation (POST /api/products) 18 | 19 | - Validates request body with Zod 20 | - Requires `name` (string, min 1 character) 21 | - Requires `price` (number, must be positive) 22 | - Returns validation errors for invalid data 23 | 24 | ### 2. Product Detail (GET /api/products/detail) 25 | 26 | - Static route for product details 27 | - Simple GET endpoint 28 | 29 | ### 3. Product by ID (GET /api/products/:id) 30 | 31 | - Dynamic route with parameter validation 32 | - Validates ID as number (min 1) 33 | - Supports optional query parameters 34 | - Returns validation errors for invalid IDs 35 | 36 | ## Running the Example 37 | 38 | ### Step 1: Start the Server 39 | 40 | In your terminal, navigate to the project root and run: 41 | 42 | ```bash 43 | bun run examples/error-handling/index.ts 44 | ``` 45 | 46 | You should see: 47 | 48 | ``` 49 | Loading route: /api/products 50 | Loading route: /api/products/detail 51 | Loading route: /api/products/:id 52 | 🚀 Server is running on port 4000 53 | ``` 54 | 55 | ### Step 2: Test the API 56 | 57 | Open another terminal and test the endpoints: 58 | 59 | ```bash 60 | # Create a product (valid) 61 | curl -X POST http://localhost:4000/api/products \ 62 | -H "Content-Type: application/json" \ 63 | -d '{"name": "Test Product", "price": 99.99}' 64 | 65 | # Create a product (invalid - missing name) 66 | curl -X POST http://localhost:4000/api/products \ 67 | -H "Content-Type: application/json" \ 68 | -d '{"price": 99.99}' 69 | 70 | # Get product detail 71 | curl http://localhost:4000/api/products/detail 72 | 73 | # Get product by ID (valid) 74 | curl http://localhost:4000/api/products/1 75 | 76 | # Get product by ID (invalid) 77 | curl http://localhost:4000/api/products/invalid 78 | ``` 79 | 80 | ## Running Tests 81 | 82 | This example includes a comprehensive test suite using Bun's built-in test 83 | runner. 84 | 85 | ### Prerequisites 86 | 87 | 1. **Start the server first** (in one terminal): 88 | 89 | ```bash 90 | bun run examples/error-handling/index.ts 91 | ``` 92 | 93 | 2. **Run tests** (in another terminal): 94 | 95 | ```bash 96 | bun test examples/error-handling/api.test.ts 97 | ``` 98 | 99 | ### Test Commands 100 | 101 | #### Run All Tests 102 | 103 | ```bash 104 | bun test examples/error-handling/api.test.ts 105 | ``` 106 | 107 | #### Run Tests in Watch Mode 108 | 109 | ```bash 110 | bun test --watch examples/error-handling/api.test.ts 111 | ``` 112 | 113 | #### Run Specific Test Group 114 | 115 | ```bash 116 | # Run only POST tests 117 | bun test --test-name-pattern "POST" examples/error-handling/api.test.ts 118 | 119 | # Run only GET tests 120 | bun test --test-name-pattern "GET" examples/error-handling/api.test.ts 121 | ``` 122 | 123 | ## Test Coverage 124 | 125 | The test suite includes **15 tests** covering: 126 | 127 | ### ✅ Product Creation (6 tests) 128 | 129 | - Valid product creation 130 | - Missing name validation 131 | - Missing price validation 132 | - Invalid price (negative) validation 133 | - Empty name validation 134 | - Invalid data types validation 135 | 136 | ### ✅ Product Detail (1 test) 137 | 138 | - GET product detail 139 | 140 | ### ✅ Product by ID (6 tests) 141 | 142 | - Valid ID with query parameter 143 | - Invalid ID (non-numeric) 144 | - Invalid ID (zero) 145 | - Invalid ID (negative) 146 | - Large valid IDs 147 | 148 | ### ✅ Error Handling (3 tests) 149 | 150 | - 404 for non-existent routes 151 | - 405 for unsupported methods 152 | - Malformed JSON handling 153 | 154 | ## API Endpoints 155 | 156 | | Method | Endpoint | Description | Validation | 157 | | ------ | ----------------------- | ------------------------------ | ----------------------------- | 158 | | POST | `/api/products` | Create a new product | `name` (string, min 1), `price` (number, positive) | 159 | | GET | `/api/products/detail` | Get product detail | None | 160 | | GET | `/api/products/:id` | Get product by ID | `id` (number, min 1), optional `search` query | 161 | 162 | ## Validation Examples 163 | 164 | ### Valid Request 165 | 166 | ```json 167 | { 168 | "name": "Test Product", 169 | "price": 99.99 170 | } 171 | ``` 172 | 173 | ### Invalid Requests 174 | 175 | ```json 176 | // Missing name 177 | { 178 | "price": 99.99 179 | } 180 | 181 | // Invalid price (negative) 182 | { 183 | "name": "Test Product", 184 | "price": -10 185 | } 186 | 187 | // Empty name 188 | { 189 | "name": "", 190 | "price": 99.99 191 | } 192 | ``` 193 | 194 | ## File Structure 195 | 196 | ``` 197 | error-handling/ 198 | ├── README.md # This file 199 | ├── index.ts # Server entry point 200 | ├── api.test.ts # Test suite 201 | ├── api/ # API routes 202 | │ └── products/ 203 | │ ├── route.ts # POST /api/products 204 | │ ├── detail/ 205 | │ │ └── route.ts # GET /api/products/detail 206 | │ └── [id]/ 207 | │ └── route.ts # GET /api/products/:id 208 | └── middleware/ 209 | └── logger.ts # Global logger middleware 210 | ``` 211 | 212 | ## Key Concepts 213 | 214 | 1. **Validation**: Using Zod schemas to validate request data 215 | 2. **Error Handling**: Proper error responses for invalid requests 216 | 3. **Type Safety**: TypeScript types inferred from Zod schemas 217 | 4. **Middleware**: Route-specific middleware for logging 218 | 219 | ## Troubleshooting 220 | 221 | ### Server Not Running Error 222 | 223 | If you see: 224 | 225 | ``` 226 | ❌ Server is not running! 227 | ``` 228 | 229 | **Solution**: Start the server first: 230 | 231 | ```bash 232 | bun run examples/error-handling/index.ts 233 | ``` 234 | 235 | ### Validation Errors 236 | 237 | If you see validation errors, check: 238 | 239 | 1. Request body format (must be valid JSON) 240 | 2. Required fields are present 241 | 3. Data types match schema (name: string, price: number) 242 | 4. Validation rules (name min 1 char, price must be positive) 243 | 244 | -------------------------------------------------------------------------------- /packages/cli/scripts/uninstall/uninstall.ps1: -------------------------------------------------------------------------------- 1 | # Burger API CLI Uninstaller (Windows) 2 | # 3 | # This script uninstalls the Burger API CLI from Windows. 4 | # It removes the executable and cleans up PATH entries. 5 | # 6 | # Usage: irm https://burger-api.com/uninstall.ps1 | iex 7 | # 8 | 9 | # Make sure we stop on errors 10 | $ErrorActionPreference = "Stop" 11 | 12 | # Function to print colored output 13 | function Print-Success { 14 | param([string]$Message) 15 | Write-Host "[OK] $Message" -ForegroundColor Green 16 | } 17 | 18 | function Print-Error { 19 | param([string]$Message) 20 | Write-Host "[X] $Message" -ForegroundColor Red 21 | } 22 | 23 | function Print-Info { 24 | param([string]$Message) 25 | Write-Host "[i] $Message" -ForegroundColor Blue 26 | } 27 | 28 | function Print-Warning { 29 | param([string]$Message) 30 | Write-Host "[!] $Message" -ForegroundColor Yellow 31 | } 32 | 33 | function Print-Header { 34 | param([string]$Message) 35 | Write-Host "" 36 | Write-Host $Message -ForegroundColor Cyan 37 | Write-Host "" 38 | } 39 | 40 | Print-Header " > BurgerAPI CLI Uninstaller" 41 | 42 | # Define installation paths 43 | $installDir = Join-Path $env:USERPROFILE ".burger-api\bin" 44 | $installPath = Join-Path $installDir "burger-api.exe" 45 | 46 | # Check if burger-api is installed 47 | $isInstalled = Test-Path $installPath 48 | 49 | if (-not $isInstalled) { 50 | Print-Info "burger-api is not installed or has already been removed" 51 | Print-Info "Installation path: $installPath" 52 | 53 | # Check if directory exists but executable doesn't 54 | if (Test-Path $installDir) { 55 | Print-Info "Installation directory still exists: $installDir" 56 | Print-Warning "Would you like to remove it? (Y/N)" 57 | $removeDir = Read-Host 58 | 59 | if ($removeDir -eq "Y" -or $removeDir -eq "y") { 60 | try { 61 | Remove-Item -Path $installDir -Recurse -Force 62 | Print-Success "Removed installation directory" 63 | } 64 | catch { 65 | Print-Error "Could not remove directory: $installDir" 66 | Print-Info "You may need to remove it manually" 67 | } 68 | } 69 | } 70 | 71 | Write-Host "" 72 | Print-Info "Nothing else to uninstall" 73 | exit 0 74 | } 75 | 76 | # Confirmation prompt 77 | Print-Warning "This will remove burger-api from your system" 78 | Print-Info "Installation path: $installPath" 79 | Write-Host "" 80 | Print-Info "Do you want to continue? (Y/N)" 81 | $confirmation = Read-Host 82 | 83 | if ($confirmation -ne "Y" -and $confirmation -ne "y") { 84 | Print-Info "Uninstall cancelled. No changes were made." 85 | exit 0 86 | } 87 | 88 | Write-Host "" 89 | 90 | # Remove the executable 91 | Print-Info "Removing executable..." 92 | try { 93 | if (Test-Path $installPath) { 94 | Remove-Item -Path $installPath -Force 95 | Print-Success "Removed burger-api.exe" 96 | } 97 | } 98 | catch { 99 | Print-Error "Could not remove executable" 100 | $errorMessage = $_.Exception.Message 101 | 102 | if ($errorMessage -match "being used|in use") { 103 | Print-Info "The file is currently in use. Close any running burger-api processes and try again." 104 | } 105 | elseif ($errorMessage -match "denied|permission") { 106 | Print-Info "Permission denied. Try running as administrator." 107 | } 108 | else { 109 | Print-Info "Error: $errorMessage" 110 | } 111 | 112 | Print-Info "You may need to remove it manually: $installPath" 113 | } 114 | 115 | # Remove from PATH 116 | Print-Info "Removing from PATH..." 117 | try { 118 | $userPath = [Environment]::GetEnvironmentVariable("Path", "User") 119 | 120 | if ($userPath -and $userPath -like "*$installDir*") { 121 | # Remove the installation directory from PATH 122 | $pathArray = $userPath -split ';' | Where-Object { $_ -and $_ -ne $installDir } 123 | $newPath = $pathArray -join ';' 124 | 125 | [Environment]::SetEnvironmentVariable( 126 | "Path", 127 | $newPath, 128 | "User" 129 | ) 130 | 131 | Print-Success "Removed from PATH" 132 | Print-Info "You may need to restart your terminal for PATH changes to take effect" 133 | } 134 | else { 135 | Print-Info "Not found in PATH (may have been removed already)" 136 | } 137 | } 138 | catch { 139 | Print-Error "Could not remove from PATH automatically" 140 | $errorMessage = $_.Exception.Message 141 | 142 | if ($errorMessage -match "denied|permission") { 143 | Print-Info "Permission denied. You may need to run as administrator." 144 | } 145 | else { 146 | Print-Info "Error: $errorMessage" 147 | } 148 | 149 | Print-Info "Please manually remove this from your PATH:" 150 | Print-Info "$installDir" 151 | } 152 | 153 | # Remove installation directory if empty 154 | Print-Info "Cleaning up installation directory..." 155 | try { 156 | if (Test-Path $installDir) { 157 | $items = Get-ChildItem -Path $installDir -Force 158 | 159 | if ($items.Count -eq 0) { 160 | # Directory is empty, remove it 161 | Remove-Item -Path $installDir -Force 162 | Print-Success "Removed empty installation directory" 163 | 164 | # Try to remove parent directory if also empty 165 | $parentDir = Split-Path $installDir -Parent 166 | if (Test-Path $parentDir) { 167 | $parentItems = Get-ChildItem -Path $parentDir -Force 168 | if ($parentItems.Count -eq 0) { 169 | Remove-Item -Path $parentDir -Force 170 | Print-Success "Removed parent directory" 171 | } 172 | } 173 | } 174 | else { 175 | Print-Info "Installation directory contains other files, not removing" 176 | Print-Info "Directory: $installDir" 177 | } 178 | } 179 | } 180 | catch { 181 | Print-Warning "Could not remove installation directory" 182 | Print-Info "You may want to manually remove: $installDir" 183 | } 184 | 185 | # Complete 186 | Print-Header "Uninstall Complete!" 187 | Print-Success "burger-api has been removed from your system" 188 | Write-Host "" 189 | Print-Info "If you reinstall later, run:" 190 | Write-Host "" 191 | Write-Host " irm https://burger-api.com/install.ps1 | iex" -ForegroundColor Cyan 192 | Write-Host "" 193 | Print-Success "Thank you for using Burger API!" 194 | Write-Host "" 195 | 196 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/README.md: -------------------------------------------------------------------------------- 1 | # File-Based API Routing Example 2 | 3 | This example demonstrates file-based API routing in burger-api, showing how 4 | routes are automatically discovered from the file structure. 5 | 6 | ## Overview 7 | 8 | This example includes: 9 | 10 | - **File-based routing** - Routes automatically discovered from file structure 11 | - **Route groups** - Using `(group)` folders to organize routes 12 | - **Dynamic routes** - Using `[id]` folders for dynamic segments 13 | - **Nested routes** - Routes with multiple path segments 14 | 15 | ## Features Demonstrated 16 | 17 | ### 1. File-Based Routing 18 | 19 | Routes are automatically discovered from the file structure: 20 | 21 | - `api/products/route.ts` → `/api/products` 22 | - `api/products/detail/route.ts` → `/api/products/detail` 23 | - `api/profile/[id]/route.ts` → `/api/profile/:id` 24 | 25 | ### 2. Route Groups 26 | 27 | Folders with parentheses `(group)` are used for organization but don't appear 28 | in the route path: 29 | 30 | - `api/(group)/products/route.ts` → `/api/products` (not `/api/(group)/products`) 31 | 32 | ### 3. Dynamic Routes 33 | 34 | Folders with brackets `[id]` create dynamic route segments: 35 | 36 | - `api/profile/[id]/route.ts` → `/api/profile/:id` 37 | 38 | ## Running the Example 39 | 40 | ### Step 1: Start the Server 41 | 42 | In your terminal, navigate to the project root and run: 43 | 44 | ```bash 45 | bun run examples/file-base-api-routing/index.ts 46 | ``` 47 | 48 | You should see: 49 | 50 | ``` 51 | Loading route: /api/products 52 | Loading route: /api/products/detail 53 | Loading route: /api/profile/:id 54 | ✨ Server is running on port: 4000 55 | ``` 56 | 57 | ### Step 2: Test the API 58 | 59 | Open another terminal and test the endpoints: 60 | 61 | ```bash 62 | # Get products list 63 | curl http://localhost:4000/api/products 64 | 65 | # Get products with query parameters 66 | curl "http://localhost:4000/api/products?search=test" 67 | 68 | # Create a product 69 | curl -X POST http://localhost:4000/api/products \ 70 | -H "Content-Type: application/json" \ 71 | -d '{"name": "Test Product", "price": 99.99}' 72 | 73 | # Get product detail 74 | curl http://localhost:4000/api/products/detail 75 | 76 | # Get profile by ID 77 | curl http://localhost:4000/api/profile/1 78 | ``` 79 | 80 | ## Running Tests 81 | 82 | This example includes a comprehensive test suite using Bun's built-in test 83 | runner. 84 | 85 | ### Prerequisites 86 | 87 | 1. **Start the server first** (in one terminal): 88 | 89 | ```bash 90 | bun run examples/file-base-api-routing/index.ts 91 | ``` 92 | 93 | 2. **Run tests** (in another terminal): 94 | 95 | ```bash 96 | bun test examples/file-base-api-routing/api.test.ts 97 | ``` 98 | 99 | ### Test Commands 100 | 101 | #### Run All Tests 102 | 103 | ```bash 104 | bun test examples/file-base-api-routing/api.test.ts 105 | ``` 106 | 107 | #### Run Tests in Watch Mode 108 | 109 | ```bash 110 | bun test --watch examples/file-base-api-routing/api.test.ts 111 | ``` 112 | 113 | #### Run Specific Test Group 114 | 115 | ```bash 116 | # Run only Products tests 117 | bun test --test-name-pattern "Products" examples/file-base-api-routing/api.test.ts 118 | 119 | # Run only Profile tests 120 | bun test --test-name-pattern "Profile" examples/file-base-api-routing/api.test.ts 121 | ``` 122 | 123 | ## Test Coverage 124 | 125 | The test suite includes **12 tests** covering: 126 | 127 | ### ✅ Products API (4 tests) 128 | 129 | - GET products list 130 | - Query parameters handling 131 | - POST product creation 132 | - GET product detail 133 | 134 | ### ✅ Profile API (3 tests) 135 | 136 | - GET profile by ID 137 | - Different profile IDs 138 | - Special characters in ID 139 | 140 | ### ✅ Route Groups (2 tests) 141 | 142 | - Grouped routes handling 143 | - Group name not in route path 144 | 145 | ### ✅ Error Handling (2 tests) 146 | 147 | - 404 for non-existent routes 148 | - 404 for invalid nested routes 149 | 150 | ## API Endpoints 151 | 152 | | Method | Endpoint | Description | File Path | 153 | | ------ | ----------------------- | ------------------------------ | ---------------------------------- | 154 | | GET | `/api/products` | Get products list | `api/(group)/products/route.ts` | 155 | | POST | `/api/products` | Create a new product | `api/(group)/products/route.ts` | 156 | | GET | `/api/products/detail` | Get product detail | `api/(group)/products/detail/route.ts` | 157 | | GET | `/api/profile/:id` | Get profile by ID | `api/(group)/profile/[id]/route.ts` | 158 | 159 | ## File Structure 160 | 161 | ``` 162 | file-base-api-routing/ 163 | ├── README.md # This file 164 | ├── index.ts # Server entry point 165 | ├── api.test.ts # Test suite 166 | ├── api/ # API routes 167 | │ └── (group)/ # Route group (ignored in path) 168 | │ ├── products/ 169 | │ │ ├── route.ts # GET/POST /api/products 170 | │ │ └── detail/ 171 | │ │ └── route.ts # GET /api/products/detail 172 | │ └── profile/ 173 | │ └── [id]/ 174 | │ └── route.ts # GET /api/profile/:id 175 | └── middleware/ 176 | └── index.ts # Global middleware 177 | ``` 178 | 179 | ## Key Concepts 180 | 181 | 1. **File-Based Routing**: Routes automatically discovered from file structure 182 | 2. **Route Groups**: Use `(group)` folders to organize routes without affecting paths 183 | 3. **Dynamic Routes**: Use `[id]` folders to create dynamic route segments 184 | 4. **Nested Routes**: Create nested paths by organizing files in folders 185 | 5. **Route Discovery**: Framework automatically scans and registers routes 186 | 187 | ## Route Naming Conventions 188 | 189 | - **Static routes**: `route.ts` files in folders 190 | - **Dynamic routes**: `[paramName]` folders 191 | - **Route groups**: `(groupName)` folders (ignored in path) 192 | - **Nested routes**: Multiple folder levels 193 | 194 | ## Troubleshooting 195 | 196 | ### Server Not Running Error 197 | 198 | If you see: 199 | 200 | ``` 201 | ❌ Server is not running! 202 | ``` 203 | 204 | **Solution**: Start the server first: 205 | 206 | ```bash 207 | bun run examples/file-base-api-routing/index.ts 208 | ``` 209 | 210 | ### Routes Not Loading 211 | 212 | If routes are not loading: 213 | 214 | 1. **Check file structure**: Ensure `route.ts` files are in correct locations 215 | 2. **Check file names**: Route files must be named `route.ts` 216 | 3. **Check server logs**: Look for route loading messages 217 | 4. **Check API directory**: Ensure `apiDir` is correctly configured 218 | 219 | -------------------------------------------------------------------------------- /packages/burger-api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 📣 Release Notes - Burger API Framework 2 | 3 | ### Version 0.6.3 (December 17, 2025) 4 | 5 | - 🔧 **CLI & Release Improvements:** 6 | - Added GitHub Actions release workflow for CLI executables 7 | - Updated README.md 8 | 9 | 10 | ### Version 0.6.2 (November 13, 2025) 11 | 12 | - ⚡ **Major Performance Improvements:** 13 | 14 | - middleware execution with specialized fast paths 15 | - AOT compilation with pre-computed middleware arrays 16 | - Zero runtime allocations (pre-allocated arrays) 17 | - Manual loop unrolling for 2-middleware case 18 | - Reduced code from ~110 to ~80 lines 19 | 20 | - 🎯 **Simplified Middleware System:** 21 | 22 | - Clearer return types: Response, Function, or undefined 23 | - Removed complex "around" middleware pattern 24 | - Dedicated fast paths for 0, 1, and 2 middlewares 25 | - Better JIT optimization 26 | 27 | - 📦 **Monorepo Structure:** 28 | 29 | - Converted to Bun workspace monorepo 30 | - Core framework in `packages/burger-api` 31 | - CLI tool in `packages/cli` (under development) 32 | - Ecosystem middleware at root level 33 | 34 | - 🔧 **Developer Experience:** 35 | - 100% backward compatible 36 | - Clearer documentation 37 | - Easier to understand codebase 38 | 39 | ### Version 0.5.2 (November 9, 2025) 40 | 41 | - 🔧 **Internal Improvements:** 42 | - Refactored wildcard parameter extraction logic into reusable utility 43 | functions 44 | - Added test suites and README files for all example projects 45 | 46 | ### Version 0.5.0 (November 1, 2025) 47 | 48 | - 🌟 **Feature:** Auto-injected OPTIONS handler for CORS preflight: 49 | 50 | - Automatically injects an OPTIONS handler for CORS preflight when needed 51 | - Only injects if the route defines any preflight-triggering methods and 52 | lacks an OPTIONS handler 53 | - Injects a minimal OPTIONS handler that returns a 204 No Content response 54 | - Works for all HTTP methods that trigger CORS preflight (POST, PUT, 55 | DELETE, PATCH) 56 | - Does not inject if the route already has an OPTIONS handler 57 | 58 | - 🌟 **Feature:** Improved response handling in middleware (after 59 | middlewares): 60 | 61 | - After middlewares now run even if the current middleware already 62 | returned a response 63 | - After middlewares run in reverse order to make changing the response 64 | easier and to help with CORS 65 | 66 | - 🐛 **Bug Fix:** Fixed TypeScript type resolution for package consumers: 67 | - Users now get full IntelliSense, autocomplete, and type safety out of 68 | the box 69 | - Improved build process by removing `tsc-alias` dependency 70 | - Converted `src/types/index.d.ts` to `src/types/index.ts` for proper 71 | emission 72 | - Updated all 49 files across `src/` and `examples/` folders 73 | - Build is now faster and more reliable 74 | - Universal compatibility across Bun 75 | 76 | ### Version 0.4.0 (October 21, 2025) 77 | 78 | - 🎯 **Wildcard Routes:** 79 | - Added wildcard routes using `[...]` folder name - matches any path after 80 | it 81 | - Create routes that handle multiple path segments automatically 82 | - Access all matched path parts through `wildcardParams` in your request 83 | - Routes are matched in order: exact paths first, then dynamic routes 84 | (like `[id]`), then wildcards last 85 | - Works inside dynamic routes too (example: `/api/users/[userId]/[...]`) 86 | - View wildcard routes in OpenAPI docs and Swagger UI 87 | - Added easy-to-follow examples showing different ways to use wildcard 88 | routes 89 | 90 | ### Version 0.3.0 (August 15, 2025) 91 | 92 | - 🔧 **Updated Zod to version 4:** 93 | - Updated Zod version from 3.x to 4.x 94 | - Updated built-in request validation middleware to use Zod 4 95 | - Updated and better request validation middleware error handling 96 | - Removed Zod-to-json-schema dependency and use Zod 4 directly 97 | 98 | ### Version 0.2.3 (May 2, 2025) 99 | 100 | - ⚡ **Core Improvements:** 101 | 102 | - Removed custom request/response classes for simpler API 103 | - Enhanced type safety and error handling 104 | 105 | ### Version 0.2.0 (April 26, 2025) 106 | 107 | - ⚡ **Performance & Core Improvements:** 108 | - Optimized framework core and improved middleware handling 109 | - Enhanced OpenAPI documentation and route tracking 110 | - Updated ID preprocessing logic in schema validation 111 | - Improved type definitions across the framework 112 | 113 | ### Version 0.1.5 (April 2, 2025) 114 | 115 | - 🔧 **Dependencies & Build:** 116 | 117 | - Updated dependencies to latest versions 118 | - Enhanced build process with tsc-alias 119 | - Improved TypeScript configuration 120 | 121 | - 📦 **Package Updates:** 122 | 123 | - Updated zod to version ^3.24.2 124 | - Updated zod-to-json-schema to version ^3.24.5 125 | - Updated TypeScript peer dependency to ^5.7.3 126 | 127 | - ⚡ **Performance & Core Improvements:** 128 | - Enhanced request handling and middleware execution in Burger class 129 | - Implemented trie structure for optimized route management 130 | - Improved route collection and validation in ApiRouter 131 | - Enhanced OpenAPI integration with better route handling 132 | 133 | ### Version 0.1.4 (March 23, 2025) 134 | 135 | - 🎨 **Code Quality & Standards:** 136 | 137 | - Added Prettier configuration for consistent code style 138 | - Enhanced code formatting and structure across the codebase 139 | - Improved type definitions and safety 140 | - Enhanced error handling and response formatting 141 | 142 | - 🔄 **Refactoring and Improvements:** 143 | - Enhanced page routing and server response handling 144 | - Improved import paths configuration 145 | - Updated request/response handling 146 | - Enhanced server initialization process 147 | 148 | ### Version 0.1.1 (March 15, 2025) 149 | 150 | - 🔧 **Middleware Improvements:** 151 | - Updated middleware to use BurgerNext type for next function 152 | - Enhanced type safety in middleware chain 153 | 154 | ### Version 0.1.0 (March 10, 2025) 155 | 156 | - 🎨 **Static Page Serving:** 157 | - Basic support for serving static `.html` files 158 | - File-based routing for pages 159 | - Support for route grouping with `(group)` syntax 160 | - Support for dynamic route with `[slug]` syntax 161 | 162 | ### Version 0.0.39 (February 25, 2025) 163 | 164 | - 🚀 Initial release with core API features 165 | - ⚡ Bun-native HTTP server implementation 166 | - 📁 File-based API routing 167 | - ✅ Zod schema validation 168 | - 📚 OpenAPI/Swagger integration 169 | - 🔄 Middleware system 170 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-api-routing/api.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test suite for file-base-api-routing example 3 | * 4 | * @file examples/file-base-api-routing/api.test.ts 5 | * @description Tests file-based API routing with groups and dynamic routes 6 | * 7 | * Usage: 8 | * 1. Start the server: bun run examples/file-base-api-routing/index.ts 9 | * 2. In another terminal, run: bun test examples/file-base-api-routing/api.test.ts 10 | */ 11 | 12 | import { describe, it, expect, beforeAll } from 'bun:test'; 13 | 14 | const BASE_URL = 'http://localhost:4000'; 15 | const REQUEST_TIMEOUT = 5000; 16 | 17 | async function fetchAPI( 18 | path: string, 19 | options: RequestInit = {} 20 | ): Promise { 21 | const url = path.startsWith('http') ? path : `${BASE_URL}${path}`; 22 | const controller = new AbortController(); 23 | const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT); 24 | 25 | try { 26 | const response = await fetch(url, { 27 | ...options, 28 | signal: controller.signal, 29 | }); 30 | clearTimeout(timeoutId); 31 | return response; 32 | } catch (error) { 33 | clearTimeout(timeoutId); 34 | throw error; 35 | } 36 | } 37 | 38 | async function fetchJSON(path: string): Promise { 39 | const response = await fetchAPI(path); 40 | if (!response.ok) { 41 | const error = await response.text(); 42 | throw new Error(`HTTP ${response.status}: ${error}`); 43 | } 44 | return response.json(); 45 | } 46 | 47 | async function checkServer(): Promise { 48 | try { 49 | const response = await fetchAPI('/api/products'); 50 | return response.status === 200; 51 | } catch { 52 | return false; 53 | } 54 | } 55 | 56 | beforeAll(async () => { 57 | const isRunning = await checkServer(); 58 | if (!isRunning) { 59 | throw new Error( 60 | '❌ Server is not running!\n\n' + 61 | 'Please start the server first:\n' + 62 | ' bun run examples/file-base-api-routing/index.ts\n\n' + 63 | 'Then run the tests in another terminal:\n' + 64 | ' bun test examples/file-base-api-routing/api.test.ts' 65 | ); 66 | } 67 | console.log('✅ Server is running, starting tests...\n'); 68 | }); 69 | 70 | describe('File-Based API Routing Example', () => { 71 | describe('Products API', () => { 72 | describe('GET /api/products', () => { 73 | it('should return products list', async () => { 74 | const data = await fetchJSON('/api/products'); 75 | expect(data).toHaveProperty('query'); 76 | expect(data).toHaveProperty('name'); 77 | expect(data.name).toBe('John Doe'); 78 | }); 79 | 80 | it('should handle query parameters', async () => { 81 | const data = await fetchJSON('/api/products?search=test'); 82 | expect(data.query).toHaveProperty('search'); 83 | expect(data.query.search).toBe('test'); 84 | }); 85 | }); 86 | 87 | describe('POST /api/products', () => { 88 | it('should create a product', async () => { 89 | const response = await fetchAPI('/api/products', { 90 | method: 'POST', 91 | headers: { 'Content-Type': 'application/json' }, 92 | body: JSON.stringify({ 93 | name: 'Test Product', 94 | price: 99.99, 95 | }), 96 | }); 97 | 98 | expect(response.status).toBe(200); 99 | const data = await response.json(); 100 | expect(data).toHaveProperty('name'); 101 | expect(data).toHaveProperty('price'); 102 | }); 103 | }); 104 | 105 | describe('GET /api/products/detail', () => { 106 | it('should return product detail', async () => { 107 | const data = await fetchJSON('/api/products/detail'); 108 | expect(data).toHaveProperty('message'); 109 | expect(data).toHaveProperty('productId'); 110 | expect(data.message).toBe('Product Detail'); 111 | expect(data.productId).toBe('123'); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('Profile API', () => { 117 | describe('GET /api/profile/:id', () => { 118 | it('should return profile with valid ID', async () => { 119 | const data = await fetchJSON('/api/profile/1'); 120 | expect(data).toHaveProperty('id'); 121 | expect(data).toHaveProperty('name'); 122 | expect(data.id).toBe('1'); 123 | expect(data.name).toBe('John Doe'); 124 | }); 125 | 126 | it('should handle different profile IDs', async () => { 127 | const testIds = ['1', '2', '123', 'abc']; 128 | for (const id of testIds) { 129 | const data = await fetchJSON(`/api/profile/${id}`); 130 | expect(data).toHaveProperty('id'); 131 | expect(data.id).toBe(id); 132 | } 133 | }); 134 | 135 | it('should handle profile ID with special characters', async () => { 136 | const data = await fetchJSON('/api/profile/test-123'); 137 | expect(data).toHaveProperty('id'); 138 | expect(data.id).toBe('test-123'); 139 | }); 140 | }); 141 | }); 142 | 143 | describe('Route Groups', () => { 144 | it('should handle grouped routes correctly', async () => { 145 | // Groups (folders with parentheses) are ignored in the route path 146 | const productsData = await fetchJSON('/api/products'); 147 | expect(productsData).toHaveProperty('name'); 148 | 149 | const profileData = await fetchJSON('/api/profile/1'); 150 | expect(profileData).toHaveProperty('id'); 151 | }); 152 | 153 | it('should not include group name in route path', async () => { 154 | // Route should be /api/products, not /api/(group)/products 155 | const response = await fetchAPI('/api/(group)/products'); 156 | expect(response.status).toBe(404); 157 | }); 158 | }); 159 | 160 | describe('Error Handling', () => { 161 | it('should return 404 for non-existent routes', async () => { 162 | const response = await fetchAPI('/api/nonexistent'); 163 | expect(response.status).toBe(404); 164 | }); 165 | 166 | it('should return 404 for invalid nested routes', async () => { 167 | const response = await fetchAPI('/api/products/invalid/nested'); 168 | expect(response.status).toBe(404); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/api.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test suite for file-base-static-page-routing-app example 3 | * 4 | * @file examples/file-base-static-page-routing-app/api.test.ts 5 | * @description Tests API endpoints (static page routing is not tested here) 6 | * 7 | * Usage: 8 | * 1. Start the server: bun run examples/file-base-static-page-routing-app/index.ts 9 | * 2. In another terminal, run: bun test examples/file-base-static-page-routing-app/api.test.ts 10 | */ 11 | 12 | import { describe, it, expect, beforeAll } from 'bun:test'; 13 | 14 | const BASE_URL = 'http://localhost:4000'; 15 | const REQUEST_TIMEOUT = 5000; 16 | 17 | async function fetchAPI( 18 | path: string, 19 | options: RequestInit = {} 20 | ): Promise { 21 | const url = path.startsWith('http') ? path : `${BASE_URL}${path}`; 22 | const controller = new AbortController(); 23 | const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT); 24 | 25 | try { 26 | const response = await fetch(url, { 27 | ...options, 28 | signal: controller.signal, 29 | }); 30 | clearTimeout(timeoutId); 31 | return response; 32 | } catch (error) { 33 | clearTimeout(timeoutId); 34 | throw error; 35 | } 36 | } 37 | 38 | async function fetchJSON(path: string): Promise { 39 | const response = await fetchAPI(path); 40 | if (!response.ok) { 41 | const error = await response.text(); 42 | throw new Error(`HTTP ${response.status}: ${error}`); 43 | } 44 | return response.json(); 45 | } 46 | 47 | async function checkServer(): Promise { 48 | try { 49 | // Check if server is running by trying to access products detail endpoint 50 | const response = await fetchAPI('/api/products/detail'); 51 | return response.status === 200; 52 | } catch { 53 | return false; 54 | } 55 | } 56 | 57 | beforeAll(async () => { 58 | const isRunning = await checkServer(); 59 | if (!isRunning) { 60 | throw new Error( 61 | '❌ Server is not running!\n\n' + 62 | 'Please start the server first:\n' + 63 | ' bun run examples/file-base-static-page-routing-app/index.ts\n\n' + 64 | 'Then run the tests in another terminal:\n' + 65 | ' bun test examples/file-base-static-page-routing-app/api.test.ts' 66 | ); 67 | } 68 | console.log('✅ Server is running, starting tests...\n'); 69 | }); 70 | 71 | describe('File-Based Static Page Routing App Example', () => { 72 | describe('Products API', () => { 73 | describe('POST /api/products', () => { 74 | it('should create a product', async () => { 75 | const response = await fetchAPI('/api/products', { 76 | method: 'POST', 77 | headers: { 'Content-Type': 'application/json' }, 78 | body: JSON.stringify({ 79 | name: 'Test Product', 80 | price: 99.99, 81 | }), 82 | }); 83 | 84 | expect(response.status).toBe(200); 85 | const data = await response.json(); 86 | expect(data).toHaveProperty('name'); 87 | expect(data).toHaveProperty('price'); 88 | }); 89 | }); 90 | 91 | describe('GET /api/products/detail', () => { 92 | it('should return product detail', async () => { 93 | const data = await fetchJSON('/api/products/detail'); 94 | expect(data).toHaveProperty('name'); 95 | expect(data.name).toBe('Sample Product'); 96 | }); 97 | }); 98 | 99 | describe('GET /api/products/:id', () => { 100 | it('should return product with valid ID', async () => { 101 | const data = await fetchJSON('/api/products/1'); 102 | expect(data).toHaveProperty('id'); 103 | expect(data).toHaveProperty('name'); 104 | expect(data).toHaveProperty('query'); 105 | expect(data.id).toBe(1); 106 | expect(data.name).toBe('Sample Product'); 107 | }); 108 | 109 | it('should handle different numeric product IDs', async () => { 110 | const testIds = ['1', '2', '123']; 111 | for (const id of testIds) { 112 | const data = await fetchJSON(`/api/products/${id}`); 113 | expect(data).toHaveProperty('id'); 114 | expect(data.id).toBe(parseInt(id, 10)); 115 | } 116 | }); 117 | 118 | it('should return validation error for non-numeric ID', async () => { 119 | const response = await fetchAPI('/api/products/abc'); 120 | expect(response.status).toBe(400); 121 | const data = await response.json(); 122 | expect(data).toHaveProperty('errors'); 123 | }); 124 | 125 | it('should handle query parameters with product ID', async () => { 126 | const data = await fetchJSON('/api/products/1?search=test'); 127 | expect(data).toHaveProperty('id'); 128 | expect(data).toHaveProperty('query'); 129 | expect(data.id).toBe(1); 130 | expect(data.query).toHaveProperty('search'); 131 | }); 132 | }); 133 | }); 134 | 135 | describe('API and Static Pages Coexistence', () => { 136 | it('should handle API routes correctly', async () => { 137 | // Test POST endpoint (GET doesn't exist for /api/products) 138 | const response = await fetchAPI('/api/products', { 139 | method: 'POST', 140 | headers: { 'Content-Type': 'application/json' }, 141 | body: JSON.stringify({ 142 | name: 'Test Product', 143 | price: 99.99, 144 | }), 145 | }); 146 | expect(response.status).toBe(200); 147 | const data = await response.json(); 148 | expect(data).toHaveProperty('name'); 149 | }); 150 | 151 | it('should not interfere with static page routes', async () => { 152 | // API routes should work independently 153 | const data = await fetchJSON('/api/products/1'); 154 | expect(data).toHaveProperty('id'); 155 | }); 156 | }); 157 | 158 | describe('Error Handling', () => { 159 | it('should return 404 for non-existent API routes', async () => { 160 | const response = await fetchAPI('/api/nonexistent'); 161 | expect(response.status).toBe(404); 162 | }); 163 | 164 | it('should return 404 for invalid nested API routes', async () => { 165 | const response = await fetchAPI('/api/products/invalid/nested'); 166 | expect(response.status).toBe(404); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release CLI Executables 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: 'Version to release (e.g., v1.0.0)' 11 | required: true 12 | type: string 13 | 14 | env: 15 | BUN_VERSION: 1.3.4 16 | 17 | jobs: 18 | build: 19 | name: Build ${{ matrix.target }} 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | include: 24 | - os: ubuntu-latest 25 | target: linux 26 | script: build:linux 27 | output: burger-api-linux 28 | asset_name: burger-api-linux 29 | 30 | - os: windows-latest 31 | target: windows 32 | script: build:win 33 | output: burger-api.exe 34 | asset_name: burger-api.exe 35 | 36 | - os: macos-latest 37 | target: mac-arm64 38 | script: build:mac 39 | output: burger-api-mac 40 | asset_name: burger-api-mac 41 | 42 | - os: macos-latest 43 | target: mac-intel 44 | script: build:mac-intel 45 | output: burger-api-mac-intel 46 | asset_name: burger-api-mac-intel 47 | 48 | steps: 49 | - name: Checkout code 50 | uses: actions/checkout@v4 51 | 52 | - name: Setup Bun 53 | uses: oven-sh/setup-bun@v1 54 | with: 55 | bun-version: ${{ env.BUN_VERSION }} 56 | 57 | - name: Setup Node.js (for bun compatibility) 58 | uses: actions/setup-node@v4 59 | with: 60 | node-version: '20' 61 | 62 | - name: Install dependencies 63 | run: | 64 | cd packages/cli 65 | bun install 66 | 67 | - name: Get version from tag (Linux/macOS) 68 | if: runner.os != 'Windows' 69 | run: | 70 | if [ "${{ github.event.inputs.version }}" != "" ]; then 71 | VERSION="${{ github.event.inputs.version }}" 72 | else 73 | VERSION="${GITHUB_REF#refs/tags/}" 74 | fi 75 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 76 | shell: bash 77 | 78 | - name: Get version from tag (Windows) 79 | if: runner.os == 'Windows' 80 | run: | 81 | if ("${{ github.event.inputs.version }}") { 82 | $VERSION = "${{ github.event.inputs.version }}" 83 | } else { 84 | $VERSION = "${{ github.ref }}".Replace('refs/tags/', '') 85 | } 86 | echo "VERSION=$VERSION" >> $env:GITHUB_ENV 87 | shell: pwsh 88 | 89 | - name: Build executable 90 | run: | 91 | cd packages/cli 92 | mkdir -p dist 93 | bun run ${{ matrix.script }} 94 | 95 | - name: Display build output (debug) 96 | run: | 97 | ls -la packages/cli/dist/ 98 | shell: bash 99 | if: runner.os != 'Windows' 100 | 101 | - name: Display build output (debug Windows) 102 | run: | 103 | Get-ChildItem -Path packages\cli\dist\ 104 | shell: pwsh 105 | if: runner.os == 'Windows' 106 | 107 | - name: Upload artifact 108 | uses: actions/upload-artifact@v4 109 | with: 110 | name: ${{ matrix.asset_name }} 111 | path: packages/cli/dist/${{ matrix.output }} 112 | if-no-files-found: error 113 | retention-days: 90 114 | 115 | release: 116 | name: Create GitHub Release 117 | runs-on: ubuntu-latest 118 | needs: build 119 | permissions: 120 | contents: write 121 | steps: 122 | - name: Checkout code 123 | uses: actions/checkout@v4 124 | 125 | - name: Download all artifacts 126 | uses: actions/download-artifact@v4 127 | with: 128 | path: ./releases 129 | 130 | - name: Generate checksums 131 | run: | 132 | cd releases 133 | # Generate individual checksum files 134 | for file in */*; do 135 | sha256sum "$file" > "${file}.sha256" 136 | done 137 | 138 | # Combine all checksums into a single file 139 | { 140 | echo "# SHA256 Checksums" 141 | echo "" 142 | } > checksums.txt 143 | 144 | for checksum_file in */*.sha256; do 145 | checksum_line=$(head -n 1 "$checksum_file") 146 | checksum=$(echo "$checksum_line" | awk '{print $1}') 147 | filename=$(basename "$(dirname "$checksum_file")")/$(basename "${checksum_file%.sha256}") 148 | echo "\`${filename}\`: \`${checksum}\`" >> checksums.txt 149 | done 150 | 151 | - name: Prepare release assets 152 | run: | 153 | mkdir -p release_assets 154 | cp releases/*/* release_assets/ 155 | cp releases/checksums.txt release_assets/ 156 | 157 | - name: Get version from tag 158 | id: get_version 159 | run: | 160 | if [ "${{ github.event.inputs.version }}" != "" ]; then 161 | VERSION="${{ github.event.inputs.version }}" 162 | else 163 | VERSION="${GITHUB_REF#refs/tags/}" 164 | fi 165 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 166 | shell: bash 167 | 168 | - name: Create GitHub Release 169 | uses: softprops/action-gh-release@v1 170 | with: 171 | tag_name: ${{ env.VERSION }} 172 | name: Release ${{ env.VERSION }} 173 | body: | 174 | ## Installation 175 | 176 | Download the appropriate executable for your platform: 177 | 178 | - **Linux**: [burger-api-linux](https://github.com/isfhan/burger-api/releases/download/${{ env.VERSION }}/burger-api-linux) 179 | - **Windows**: [burger-api.exe](https://github.com/isfhan/burger-api/releases/download/${{ env.VERSION }}/burger-api.exe) 180 | - **macOS (ARM64)**: [burger-api-mac](https://github.com/isfhan/burger-api/releases/download/${{ env.VERSION }}/burger-api-mac) 181 | - **macOS (Intel)**: [burger-api-mac-intel](https://github.com/isfhan/burger-api/releases/download/${{ env.VERSION }}/burger-api-mac-intel) 182 | 183 | ## Verification 184 | 185 | Check the [checksums.txt](https://github.com/isfhan/burger-api/releases/download/${{ env.VERSION }}/checksums.txt) file to verify the integrity of downloaded executables. 186 | 187 | ## Changelog 188 | 189 | See [CHANGELOG.md](https://github.com/isfhan/burger-api/blob/main/packages/cli/CHANGELOG.md) for details on what's new in this version. 190 | files: | 191 | release_assets/burger-api-linux 192 | release_assets/burger-api.exe 193 | release_assets/burger-api-mac 194 | release_assets/burger-api-mac-intel 195 | release_assets/checksums.txt 196 | draft: false 197 | prerelease: false -------------------------------------------------------------------------------- /packages/burger-api/src/middleware/validator.ts: -------------------------------------------------------------------------------- 1 | // Import types 2 | import type { 3 | RouteSchema, 4 | Middleware, 5 | BurgerRequest, 6 | BurgerNext, 7 | } from '../types/index'; 8 | 9 | /** 10 | * createValidationMiddleware: 11 | * - Precompute a smaller runtime descriptor per method to avoid 12 | * repeatedly touching the full schema object on every request. 13 | * - Uses safeParse (sync) for Zod schemas (no try/catch around safeParse). 14 | * - Parses querystring manually (no URL constructor, no throw). 15 | * - Lazily allocates the errors container only when needed. 16 | * - Avoids unnecessary object allocations for successful paths. 17 | * 18 | * @param schema - The route schema to validate the request against. 19 | * @returns The middleware function that validates the request. 20 | */ 21 | export function createValidationMiddleware(schema: RouteSchema): Middleware { 22 | // Precompute a minimal descriptor per HTTP method (lowercased) 23 | // This work runs once at middleware creation time (fast startup cost, zero cost on each request for those computations). 24 | const methodMap: Record< 25 | string, 26 | { 27 | paramsSchema?: any; 28 | querySchema?: any; 29 | bodySchema?: any; 30 | hasParams: boolean; 31 | hasQuery: boolean; 32 | hasBody: boolean; 33 | } 34 | > = {}; 35 | 36 | // Loop through the schema and create a method map 37 | for (const rawMethod of Object.keys(schema)) { 38 | // Convert the method to lowercase 39 | const key = rawMethod.toLowerCase(); 40 | 41 | // Get the method schema 42 | const m = schema[rawMethod] || {}; 43 | 44 | // Create a method map 45 | methodMap[key] = { 46 | paramsSchema: m.params, // URL parameters schema 47 | querySchema: m.query, // Query parameters schema 48 | bodySchema: m.body, // Request body schema 49 | hasParams: !!m.params, // Whether the method has URL parameters 50 | hasQuery: !!m.query, // Whether the method has query parameters 51 | hasBody: !!m.body, // Whether the method has a request body 52 | }; 53 | } 54 | 55 | /** 56 | * The middleware function that validates the request. 57 | * 58 | * @param req - The request object. 59 | * @returns The next middleware or handler. 60 | */ 61 | return async (req: BurgerRequest): Promise => { 62 | // If the request has been validated, continue. 63 | if (req.validated) { 64 | return undefined; // Proceed to the next middleware or handler 65 | } 66 | 67 | // Determine the HTTP method (in lowercase) to match the schema. 68 | const method = (req.method || 'get').toLowerCase(); 69 | 70 | // Get the schema for the current method. 71 | const methodSchema = methodMap[method]; 72 | 73 | // If there's no schema for this method, continue. 74 | if (!methodSchema) { 75 | return undefined; // Proceed to the next middleware or handler 76 | } 77 | 78 | // Get the schemas for the current method. 79 | const { 80 | paramsSchema, 81 | querySchema, 82 | bodySchema, 83 | hasParams, 84 | hasQuery, 85 | hasBody, 86 | } = methodSchema; 87 | 88 | /** 89 | * Object to store validated request data. This will be attached to the 90 | * request object if validation is successful. The object will contain 91 | * validated data for the following fields: 92 | * 93 | * - `params`: Validated URL parameters (if available and schema provided). 94 | * - `query`: Validated query parameters (if available and schema provided). 95 | * - `body`: Validated request body (if JSON and schema provided). 96 | */ 97 | const validated: BurgerRequest['validated'] = {}; 98 | 99 | // Lazy errors — create only if/when an error occurs 100 | let errors: { 101 | params?: unknown; 102 | query?: unknown; 103 | body?: unknown; 104 | } | null = null; 105 | 106 | // Validate URL parameters (if available and schema provided). 107 | if (hasParams && req.params) { 108 | const result = paramsSchema.safeParse(req.params); 109 | if (result.success) { 110 | validated.params = result.data; 111 | } else { 112 | if (!errors) errors = {}; 113 | errors.params = result.error.issues; 114 | } 115 | } 116 | 117 | // Validate query parameters if available and schema provided. 118 | if (hasQuery) { 119 | const url = new URL(req.url); 120 | const queryParams = Object.fromEntries(url.searchParams.entries()); 121 | 122 | const result = querySchema.safeParse(queryParams); 123 | if (result.success) { 124 | validated.query = result.data; 125 | } else { 126 | if (!errors) errors = {}; 127 | errors.query = result.error.issues; 128 | } 129 | } 130 | 131 | // Get the content type header 132 | const contentType = 133 | req.headers.get('content-type') ?? 134 | req.headers.get('Content-Type') ?? 135 | ''; 136 | 137 | // Validate request body if it's JSON 138 | if (hasBody && contentType?.includes('application/json')) { 139 | try { 140 | // Attempt to parse the JSON body. 141 | const bodyData = await req.json(); 142 | const result = bodySchema.safeParse(bodyData); 143 | if (result.success) { 144 | // Set the validated body. 145 | validated.body = result.data; 146 | 147 | // Set the json method to return the validated body. 148 | req.json = async () => result.data; 149 | } else { 150 | if (!errors) errors = {}; 151 | errors.body = result.error.issues; 152 | } 153 | } catch (error: any) { 154 | // Create a message from the error. 155 | const msg = 156 | error && typeof error.message === 'string' 157 | ? error.message 158 | : String(error); 159 | 160 | if (!errors) errors = {}; 161 | errors.body = [{ message: msg }]; 162 | } 163 | } 164 | 165 | if (errors) { 166 | // If validation fails, return a 400 response with error details. 167 | return Response.json({ errors }, { status: 400 }); 168 | } 169 | 170 | // Attach validated data to the request. 171 | req.validated = validated; 172 | 173 | // Continue to the next middleware or handler. 174 | return undefined; // Proceed 175 | }; 176 | } 177 | -------------------------------------------------------------------------------- /ecosystem/middlewares/body-size-limiter/body-size-limiter.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerRequest, BurgerNext} from 'burger-api'; 2 | 3 | /** 4 | * Configuration options for the body size limiter middleware. 5 | */ 6 | export interface BodySizeLimiterOptions { 7 | /** 8 | * Maximum allowed body size in bytes. 9 | * @default 1048576 (1MB) 10 | */ 11 | maxSize?: number; 12 | 13 | /** 14 | * Whether to check the Content-Length header only (fast) 15 | * or actually read and measure the body (accurate but slower). 16 | * @default 'header' (fast, less accurate) 17 | */ 18 | mode?: 'header' | 'stream'; 19 | 20 | /** 21 | * Custom error handler for oversized requests. 22 | * If not provided, returns a default 413 response. 23 | * 24 | * @param size - The size of the request body 25 | * @param maxSize - The maximum allowed size 26 | * @returns Response to send when body is too large 27 | */ 28 | onError?: (size: number, maxSize: number) => Response; 29 | 30 | /** 31 | * Whether to include the limit in error response. 32 | * @default true 33 | */ 34 | includeLimit?: boolean; 35 | } 36 | 37 | /** 38 | * Creates a body size limiter middleware to prevent large payload attacks. 39 | * 40 | * This middleware checks the size of incoming request bodies and rejects 41 | * requests that exceed the specified limit. It helps prevent DoS attacks 42 | * and protects server resources. 43 | * 44 | * @param options - Configuration options for body size limiting 45 | * @returns A middleware function that enforces body size limits 46 | * 47 | * @example 48 | * ```typescript 49 | * // Default: 1MB limit 50 | * const bodySizeLimit = bodySizeLimiter(); 51 | * 52 | * // Custom limit: 10MB 53 | * const bodySizeLimit = bodySizeLimiter({ maxSize: 10 * 1024 * 1024 }); 54 | * 55 | * // Strict mode: actually measure body size 56 | * const bodySizeLimit = bodySizeLimiter({ 57 | * maxSize: 1024 * 1024, 58 | * mode: 'stream' 59 | * }); 60 | * 61 | * // Custom error message 62 | * const bodySizeLimit = bodySizeLimiter({ 63 | * maxSize: 5 * 1024 * 1024, 64 | * onError: (size, max) => Response.json( 65 | * { 66 | * error: 'Payload too large', 67 | * received: `${(size / 1024 / 1024).toFixed(2)}MB`, 68 | * maximum: `${(max / 1024 / 1024).toFixed(2)}MB` 69 | * }, 70 | * { status: 413 } 71 | * ) 72 | * }); 73 | * ``` 74 | */ 75 | export function bodySizeLimiter(options: BodySizeLimiterOptions = {}): Middleware { 76 | const { 77 | maxSize = 1048576, // 1MB 78 | mode = 'header', 79 | onError = defaultErrorHandler, 80 | includeLimit = true, 81 | } = options; 82 | 83 | return async (req: BurgerRequest): Promise => { 84 | // Skip check for methods that typically don't have bodies 85 | if (['GET', 'HEAD', 'OPTIONS', 'DELETE'].includes(req.method)) { 86 | return undefined; 87 | } 88 | 89 | if (mode === 'header') { 90 | // Fast mode: Check Content-Length header only 91 | const contentLength = req.headers.get('Content-Length'); 92 | 93 | if (contentLength) { 94 | const size = parseInt(contentLength, 10); 95 | 96 | if (isNaN(size)) { 97 | return Response.json( 98 | { error: 'Invalid Content-Length header' }, 99 | { status: 400 } 100 | ); 101 | } 102 | 103 | if (size > maxSize) { 104 | return onError(size, maxSize); 105 | } 106 | } 107 | // If no Content-Length header, we can't check in header mode 108 | // In production, you might want to require Content-Length header 109 | 110 | return undefined; 111 | } else { 112 | // Stream mode: Actually read and measure the body 113 | // This is more accurate but slower and requires reading the body 114 | 115 | if (!req.body) { 116 | return undefined; // No body to check 117 | } 118 | 119 | try { 120 | // Read the body as array buffer 121 | const bodyBuffer = await req.arrayBuffer(); 122 | const size = bodyBuffer.byteLength; 123 | 124 | if (size > maxSize) { 125 | return onError(size, maxSize); 126 | } 127 | 128 | // Recreate request with the consumed body 129 | // Note: This might not work perfectly with all request types 130 | // For production, consider using a streaming approach 131 | (req as any)._bodyBuffer = bodyBuffer; 132 | 133 | return undefined; 134 | } catch (error) { 135 | console.error('Error reading request body:', error); 136 | return Response.json( 137 | { error: 'Error reading request body' }, 138 | { status: 400 } 139 | ); 140 | } 141 | } 142 | }; 143 | } 144 | 145 | /** 146 | * Default error handler for oversized requests. 147 | */ 148 | function defaultErrorHandler(size: number, maxSize: number): Response { 149 | const sizeInMB = (size / 1024 / 1024).toFixed(2); 150 | const maxSizeInMB = (maxSize / 1024 / 1024).toFixed(2); 151 | 152 | return Response.json( 153 | { 154 | error: 'Payload Too Large', 155 | message: `Request body exceeds maximum allowed size`, 156 | received: `${sizeInMB}MB`, 157 | maximum: `${maxSizeInMB}MB`, 158 | }, 159 | { 160 | status: 413, 161 | statusText: 'Payload Too Large', 162 | headers: { 163 | 'Connection': 'close', 164 | }, 165 | } 166 | ); 167 | } 168 | 169 | /** 170 | * Preset: Small payloads (100KB) - for text-based APIs 171 | */ 172 | export function smallPayloadLimit(): Middleware { 173 | return bodySizeLimiter({ maxSize: 102400 }); // 100KB 174 | } 175 | 176 | /** 177 | * Preset: Medium payloads (1MB) - default, good for most APIs 178 | */ 179 | export function mediumPayloadLimit(): Middleware { 180 | return bodySizeLimiter({ maxSize: 1048576 }); // 1MB 181 | } 182 | 183 | /** 184 | * Preset: Large payloads (10MB) - for file uploads 185 | */ 186 | export function largePayloadLimit(): Middleware { 187 | return bodySizeLimiter({ maxSize: 10485760 }); // 10MB 188 | } 189 | 190 | /** 191 | * Preset: Extra large payloads (50MB) - for large file uploads 192 | */ 193 | export function extraLargePayloadLimit(): Middleware { 194 | return bodySizeLimiter({ maxSize: 52428800 }); // 50MB 195 | } 196 | 197 | /** 198 | * Helper: Convert bytes to human-readable format 199 | */ 200 | export function formatBytes(bytes: number, decimals: number = 2): string { 201 | if (bytes === 0) return '0 Bytes'; 202 | 203 | const k = 1024; 204 | const sizes = ['Bytes', 'KB', 'MB', 'GB']; 205 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 206 | 207 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /packages/burger-api/examples/openapi-and-swagger-ui/README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI and Swagger UI Example 2 | 3 | This example demonstrates automatic OpenAPI specification generation and 4 | Swagger UI integration in burger-api, showing how to document your API 5 | automatically. 6 | 7 | ## Overview 8 | 9 | This example includes: 10 | 11 | - **Automatic OpenAPI generation** - OpenAPI 3.0 spec generated from routes 12 | - **Swagger UI integration** - Interactive API documentation 13 | - **Route metadata** - Custom OpenAPI metadata for routes 14 | - **Schema validation** - Zod schemas automatically converted to OpenAPI 15 | 16 | ## Features Demonstrated 17 | 18 | ### 1. Automatic OpenAPI Generation 19 | 20 | - OpenAPI 3.0 specification generated automatically 21 | - Available at `/openapi.json` 22 | - Includes all routes, schemas, and metadata 23 | 24 | ### 2. Swagger UI Integration 25 | 26 | - Interactive API documentation 27 | - Available at `/docs` 28 | - Automatically loads OpenAPI spec 29 | 30 | ### 3. Route Metadata 31 | 32 | - Custom OpenAPI metadata for routes 33 | - Summary, description, tags, operationId 34 | - Request/response schemas 35 | 36 | ### 4. Schema Validation 37 | 38 | - Zod schemas automatically converted to OpenAPI 39 | - Request body validation 40 | - Parameter validation 41 | 42 | ## Running the Example 43 | 44 | ### Step 1: Start the Server 45 | 46 | In your terminal, navigate to the project root and run: 47 | 48 | ```bash 49 | bun run examples/openapi-and-swagger-ui/index.ts 50 | ``` 51 | 52 | You should see: 53 | 54 | ``` 55 | Loading route: /api/products 56 | Loading route: /api/products/:id 57 | 🚀 Server is running on port 4000 58 | ``` 59 | 60 | ### Step 2: Test the API 61 | 62 | Open another terminal and test the endpoints: 63 | 64 | ```bash 65 | # Get products list 66 | curl http://localhost:4000/api/products 67 | 68 | # Create a product 69 | curl -X POST http://localhost:4000/api/products \ 70 | -H "Content-Type: application/json" \ 71 | -d '{"name": "Test Product", "price": 99.99}' 72 | 73 | # Get product by ID 74 | curl http://localhost:4000/api/products/1 75 | ``` 76 | 77 | ### Step 3: View Documentation 78 | 79 | Open your browser and visit: 80 | 81 | - `http://localhost:4000/docs` - Swagger UI (interactive documentation) 82 | - `http://localhost:4000/openapi.json` - OpenAPI specification (JSON) 83 | 84 | ## Running Tests 85 | 86 | This example includes a comprehensive test suite using Bun's built-in test 87 | runner. 88 | 89 | ### Prerequisites 90 | 91 | 1. **Start the server first** (in one terminal): 92 | 93 | ```bash 94 | bun run examples/openapi-and-swagger-ui/index.ts 95 | ``` 96 | 97 | 2. **Run tests** (in another terminal): 98 | 99 | ```bash 100 | bun test examples/openapi-and-swagger-ui/api.test.ts 101 | ``` 102 | 103 | ### Test Commands 104 | 105 | #### Run All Tests 106 | 107 | ```bash 108 | bun test examples/openapi-and-swagger-ui/api.test.ts 109 | ``` 110 | 111 | #### Run Tests in Watch Mode 112 | 113 | ```bash 114 | bun test --watch examples/openapi-and-swagger-ui/api.test.ts 115 | ``` 116 | 117 | #### Run Specific Test Group 118 | 119 | ```bash 120 | # Run only OpenAPI tests 121 | bun test --test-name-pattern "OpenAPI" examples/openapi-and-swagger-ui/api.test.ts 122 | 123 | # Run only Products tests 124 | bun test --test-name-pattern "Products" examples/openapi-and-swagger-ui/api.test.ts 125 | ``` 126 | 127 | ## Test Coverage 128 | 129 | The test suite includes **14 tests** covering: 130 | 131 | ### ✅ Products API (6 tests) 132 | 133 | - GET products list 134 | - Query parameters handling 135 | - POST product creation (valid) 136 | - POST product creation (invalid) 137 | - GET product by ID (valid) 138 | - GET product by ID (invalid) 139 | 140 | ### ✅ OpenAPI Documentation (5 tests) 141 | 142 | - OpenAPI specification structure 143 | - API paths in spec 144 | - Operation metadata 145 | - Request body schema 146 | - Parameter schemas 147 | 148 | ### ✅ Swagger UI (2 tests) 149 | 150 | - Swagger UI HTML response 151 | - Swagger UI configuration 152 | 153 | ### ✅ Error Handling (2 tests) 154 | 155 | - 404 for non-existent routes 156 | - 404 for invalid nested routes 157 | 158 | ## API Endpoints 159 | 160 | | Method | Endpoint | Description | OpenAPI Metadata | 161 | | ------ | ----------------------- | ------------------------------ | ----------------------------- | 162 | | GET | `/api/products` | Get products list | None | 163 | | POST | `/api/products` | Create a new product | Summary, description, tags, operationId | 164 | | GET | `/api/products/:id` | Get product by ID | Summary, description, tags, operationId | 165 | 166 | ## Documentation Endpoints 167 | 168 | | Endpoint | Description | 169 | | ------------------- | ------------------------------ | 170 | | `/openapi.json` | OpenAPI 3.0 specification | 171 | | `/docs` | Swagger UI (interactive docs) | 172 | 173 | ## OpenAPI Metadata Example 174 | 175 | ```typescript 176 | export const openapi = { 177 | post: { 178 | summary: 'Create a Product', 179 | description: 'Creates a new product. Requires name and price in the request body.', 180 | tags: ['Product'], 181 | operationId: 'createProduct', 182 | }, 183 | }; 184 | ``` 185 | 186 | ## File Structure 187 | 188 | ``` 189 | openapi-and-swagger-ui/ 190 | ├── README.md # This file 191 | ├── index.ts # Server entry point 192 | ├── api.test.ts # Test suite 193 | ├── api/ # API routes 194 | │ └── products/ 195 | │ ├── route.ts # GET/POST /api/products 196 | │ └── [id]/ 197 | │ └── route.ts # GET /api/products/:id 198 | └── middleware/ 199 | └── logger.ts # Global logger middleware 200 | ``` 201 | 202 | ## Key Concepts 203 | 204 | 1. **Automatic OpenAPI Generation**: Framework generates OpenAPI spec from routes 205 | 2. **Swagger UI Integration**: Interactive documentation automatically available 206 | 3. **Route Metadata**: Custom OpenAPI metadata for routes 207 | 4. **Schema Conversion**: Zod schemas automatically converted to OpenAPI 208 | 5. **Type Safety**: TypeScript types inferred from Zod schemas 209 | 210 | ## Troubleshooting 211 | 212 | ### Server Not Running Error 213 | 214 | If you see: 215 | 216 | ``` 217 | ❌ Server is not running! 218 | ``` 219 | 220 | **Solution**: Start the server first: 221 | 222 | ```bash 223 | bun run examples/openapi-and-swagger-ui/index.ts 224 | ``` 225 | 226 | ### OpenAPI Spec Not Loading 227 | 228 | If OpenAPI spec is not loading: 229 | 230 | 1. **Check server is running**: Ensure server is running on port 4000 231 | 2. **Check route loading**: Look for route loading messages in server logs 232 | 3. **Check OpenAPI endpoint**: Visit `http://localhost:4000/openapi.json` 233 | 234 | ### Swagger UI Not Loading 235 | 236 | If Swagger UI is not loading: 237 | 238 | 1. **Check server is running**: Ensure server is running on port 4000 239 | 2. **Check OpenAPI spec**: Ensure `/openapi.json` is accessible 240 | 3. **Check browser console**: Look for JavaScript errors 241 | 4. **Check network tab**: Look for failed requests 242 | 243 | -------------------------------------------------------------------------------- /packages/burger-api/examples/zod-based-schema-validation/README.md: -------------------------------------------------------------------------------- 1 | # Zod-Based Schema Validation Example 2 | 3 | This example demonstrates Zod-based schema validation in burger-api, showing 4 | how to validate query parameters and request body using Zod schemas. 5 | 6 | ## Overview 7 | 8 | This example includes: 9 | 10 | - **Product API** with Zod validation 11 | - **Query parameter validation** for GET requests 12 | - **Request body validation** for POST requests 13 | - **Parameter validation** for dynamic routes 14 | - **Route-specific middleware** for logging 15 | 16 | ## Features Demonstrated 17 | 18 | ### 1. Query Parameter Validation (GET /api/products) 19 | 20 | - Validates `search` query parameter (required string) 21 | - Returns validation errors for missing or invalid parameters 22 | 23 | ### 2. Request Body Validation (POST /api/products) 24 | 25 | - Validates request body with Zod 26 | - Requires `name` (string, min 1 character) 27 | - Requires `price` (number, must be positive) 28 | - Returns validation errors for invalid data 29 | 30 | ### 3. Parameter Validation (GET /api/products/:id) 31 | 32 | - Validates URL parameter `id` (number, min 1) 33 | - Returns validation errors for invalid IDs 34 | 35 | ## Running the Example 36 | 37 | ### Step 1: Start the Server 38 | 39 | In your terminal, navigate to the project root and run: 40 | 41 | ```bash 42 | bun run examples/zod-based-schema-validation/index.ts 43 | ``` 44 | 45 | You should see: 46 | 47 | ``` 48 | Loading route: /api/products 49 | Loading route: /api/products/:id 50 | ✨ Server is running on port: 4000 51 | ``` 52 | 53 | ### Step 2: Test the API 54 | 55 | Open another terminal and test the endpoints: 56 | 57 | ```bash 58 | # Get products with search query (valid) 59 | curl "http://localhost:4000/api/products?search=test" 60 | 61 | # Get products without search query (invalid) 62 | curl "http://localhost:4000/api/products" 63 | 64 | # Create a product (valid) 65 | curl -X POST http://localhost:4000/api/products \ 66 | -H "Content-Type: application/json" \ 67 | -d '{"name": "Test Product", "price": 99.99}' 68 | 69 | # Create a product (invalid - missing name) 70 | curl -X POST http://localhost:4000/api/products \ 71 | -H "Content-Type: application/json" \ 72 | -d '{"price": 99.99}' 73 | 74 | # Get product by ID (valid) 75 | curl http://localhost:4000/api/products/1 76 | 77 | # Get product by ID (invalid) 78 | curl http://localhost:4000/api/products/invalid 79 | ``` 80 | 81 | ## Running Tests 82 | 83 | This example includes a comprehensive test suite using Bun's built-in test 84 | runner. 85 | 86 | ### Prerequisites 87 | 88 | 1. **Start the server first** (in one terminal): 89 | 90 | ```bash 91 | bun run examples/zod-based-schema-validation/index.ts 92 | ``` 93 | 94 | 2. **Run tests** (in another terminal): 95 | 96 | ```bash 97 | bun test examples/zod-based-schema-validation/api.test.ts 98 | ``` 99 | 100 | ### Test Commands 101 | 102 | #### Run All Tests 103 | 104 | ```bash 105 | bun test examples/zod-based-schema-validation/api.test.ts 106 | ``` 107 | 108 | #### Run Tests in Watch Mode 109 | 110 | ```bash 111 | bun test --watch examples/zod-based-schema-validation/api.test.ts 112 | ``` 113 | 114 | #### Run Specific Test Group 115 | 116 | ```bash 117 | # Run only GET tests 118 | bun test --test-name-pattern "GET" examples/zod-based-schema-validation/api.test.ts 119 | 120 | # Run only POST tests 121 | bun test --test-name-pattern "POST" examples/zod-based-schema-validation/api.test.ts 122 | ``` 123 | 124 | ## Test Coverage 125 | 126 | The test suite includes **18 tests** covering: 127 | 128 | ### ✅ GET /api/products (5 tests) 129 | 130 | - Valid query parameter 131 | - Missing search parameter 132 | - Empty search parameter 133 | - Special characters in search 134 | - Multiple words in search 135 | 136 | ### ✅ POST /api/products (8 tests) 137 | 138 | - Valid product creation 139 | - Missing name validation 140 | - Missing price validation 141 | - Empty name validation 142 | - Invalid price (negative) 143 | - Invalid price (zero) 144 | - Invalid data types 145 | - Decimal price handling 146 | 147 | ### ✅ GET /api/products/:id (4 tests) 148 | 149 | - Valid ID 150 | - Invalid ID (non-numeric) 151 | - Invalid ID (zero) 152 | - Invalid ID (negative) 153 | 154 | ### ✅ Validation Edge Cases (3 tests) 155 | 156 | - Very long search strings 157 | - Special characters in search 158 | - Unicode characters in product name 159 | 160 | ## API Endpoints 161 | 162 | | Method | Endpoint | Description | Validation | 163 | | ------ | ------------------- | ------------------------------ | ----------------------------- | 164 | | GET | `/api/products` | Get products with search | `search` (string, required) | 165 | | POST | `/api/products` | Create a new product | `name` (string, min 1), `price` (number, positive) | 166 | | GET | `/api/products/:id` | Get product by ID | `id` (number, min 1) | 167 | 168 | ## Validation Examples 169 | 170 | ### Valid Requests 171 | 172 | ```json 173 | // GET /api/products?search=test 174 | { 175 | "query": { "search": "test" }, 176 | "name": "John Doe" 177 | } 178 | 179 | // POST /api/products 180 | { 181 | "name": "Test Product", 182 | "price": 99.99 183 | } 184 | ``` 185 | 186 | ### Invalid Requests 187 | 188 | ```json 189 | // GET /api/products (missing search) 190 | // Returns 400: search is required 191 | 192 | // POST /api/products (missing name) 193 | { 194 | "price": 99.99 195 | } 196 | // Returns 400: name is required 197 | 198 | // POST /api/products (invalid price) 199 | { 200 | "name": "Test Product", 201 | "price": -10 202 | } 203 | // Returns 400: price must be positive 204 | ``` 205 | 206 | ## File Structure 207 | 208 | ``` 209 | zod-based-schema-validation/ 210 | ├── README.md # This file 211 | ├── index.ts # Server entry point 212 | ├── api.test.ts # Test suite 213 | ├── api/ # API routes 214 | │ └── products/ 215 | │ ├── route.ts # GET/POST /api/products 216 | │ └── [id]/ 217 | │ └── route.ts # GET /api/products/:id 218 | └── middleware/ 219 | └── index.ts # Global middleware 220 | ``` 221 | 222 | ## Key Concepts 223 | 224 | 1. **Zod Validation**: Using Zod schemas to validate request data 225 | 2. **Query Parameters**: Validating query string parameters 226 | 3. **Request Body**: Validating JSON request body 227 | 4. **URL Parameters**: Validating dynamic route parameters 228 | 5. **Type Safety**: TypeScript types inferred from Zod schemas 229 | 6. **Error Handling**: Proper error responses for validation failures 230 | 231 | ## Troubleshooting 232 | 233 | ### Server Not Running Error 234 | 235 | If you see: 236 | 237 | ``` 238 | ❌ Server is not running! 239 | ``` 240 | 241 | **Solution**: Start the server first: 242 | 243 | ```bash 244 | bun run examples/zod-based-schema-validation/index.ts 245 | ``` 246 | 247 | ### Validation Errors 248 | 249 | If you see validation errors, check: 250 | 251 | 1. **Query Parameters**: Required parameters are present 252 | 2. **Request Body**: Format is valid JSON 253 | 3. **Required Fields**: All required fields are present 254 | 4. **Data Types**: Types match schema (name: string, price: number) 255 | 5. **Validation Rules**: Values meet schema requirements 256 | 257 | -------------------------------------------------------------------------------- /packages/burger-api/examples/file-base-static-page-routing-app/README.md: -------------------------------------------------------------------------------- 1 | # File-Based Static Page Routing App Example 2 | 3 | This example demonstrates both API routing and static page routing in 4 | burger-api, showing how to serve both API endpoints and static HTML pages from 5 | the same application. 6 | 7 | ## Overview 8 | 9 | This example includes: 10 | 11 | - **API routes** - RESTful API endpoints 12 | - **Static pages** - HTML pages served from file structure 13 | - **Assets** - CSS and JavaScript files 14 | - **Coexistence** - API and static pages working together 15 | 16 | ## Features Demonstrated 17 | 18 | ### 1. API Routes 19 | 20 | - `/api/products` - Products API endpoint 21 | - `/api/products/:id` - Dynamic product route 22 | - `/api/products/detail` - Product detail route 23 | 24 | ### 2. Static Pages 25 | 26 | - `/` - Home page 27 | - `/my-static` - Static page with assets 28 | - `/my-static-2` - Another static page 29 | - `/user/:id` - Dynamic user page 30 | - `/user/:id/post` - Nested dynamic page 31 | 32 | ### 3. Assets 33 | 34 | - CSS files served from `/assets/css/` 35 | - JavaScript files served from `/assets/js/` 36 | 37 | ## Running the Example 38 | 39 | ### Step 1: Start the Server 40 | 41 | In your terminal, navigate to the project root and run: 42 | 43 | ```bash 44 | bun run examples/file-base-static-page-routing-app/index.ts 45 | ``` 46 | 47 | You should see: 48 | 49 | ``` 50 | Loading route: /api/products 51 | Loading route: /api/products/detail 52 | Loading route: /api/products/:id 53 | 🚀 Server is running on port 4000 54 | ``` 55 | 56 | ### Step 2: Test the API 57 | 58 | Open another terminal and test the API endpoints: 59 | 60 | ```bash 61 | # Get products list 62 | curl http://localhost:4000/api/products 63 | 64 | # Get products with query parameters 65 | curl "http://localhost:4000/api/products?search=test" 66 | 67 | # Create a product 68 | curl -X POST http://localhost:4000/api/products \ 69 | -H "Content-Type: application/json" \ 70 | -d '{"name": "Test Product", "price": 99.99}' 71 | 72 | # Get product detail 73 | curl http://localhost:4000/api/products/detail 74 | 75 | # Get product by ID 76 | curl http://localhost:4000/api/products/1 77 | ``` 78 | 79 | ### Step 3: View Static Pages 80 | 81 | Open your browser and visit: 82 | 83 | - `http://localhost:4000/` - Home page 84 | - `http://localhost:4000/my-static` - Static page 85 | - `http://localhost:4000/my-static-2` - Another static page 86 | - `http://localhost:4000/user/1` - Dynamic user page 87 | - `http://localhost:4000/user/1/post` - Nested dynamic page 88 | 89 | ## Running Tests 90 | 91 | This example includes a test suite for API endpoints using Bun's built-in 92 | test runner. Note: Static page routing is not tested here. 93 | 94 | ### Prerequisites 95 | 96 | 1. **Start the server first** (in one terminal): 97 | 98 | ```bash 99 | bun run examples/file-base-static-page-routing-app/index.ts 100 | ``` 101 | 102 | 2. **Run tests** (in another terminal): 103 | 104 | ```bash 105 | bun test examples/file-base-static-page-routing-app/api.test.ts 106 | ``` 107 | 108 | ### Test Commands 109 | 110 | #### Run All Tests 111 | 112 | ```bash 113 | bun test examples/file-base-static-page-routing-app/api.test.ts 114 | ``` 115 | 116 | #### Run Tests in Watch Mode 117 | 118 | ```bash 119 | bun test --watch examples/file-base-static-page-routing-app/api.test.ts 120 | ``` 121 | 122 | ## Test Coverage 123 | 124 | The test suite includes **10 tests** covering: 125 | 126 | ### ✅ Products API (7 tests) 127 | 128 | - GET products list 129 | - Query parameters handling 130 | - POST product creation 131 | - GET product detail 132 | - GET product by ID 133 | - Different product IDs 134 | - Query parameters with product ID 135 | 136 | ### ✅ API and Static Pages Coexistence (2 tests) 137 | 138 | - API routes handling 139 | - No interference with static pages 140 | 141 | ### ✅ Error Handling (2 tests) 142 | 143 | - 404 for non-existent API routes 144 | - 404 for invalid nested API routes 145 | 146 | ## API Endpoints 147 | 148 | | Method | Endpoint | Description | 149 | | ------ | ----------------------- | ------------------------------ | 150 | | GET | `/api/products` | Get products list | 151 | | POST | `/api/products` | Create a new product | 152 | | GET | `/api/products/detail` | Get product detail | 153 | | GET | `/api/products/:id` | Get product by ID | 154 | 155 | ## Static Pages 156 | 157 | | Path | Description | 158 | | --------------------- | ------------------------------ | 159 | | `/` | Home page | 160 | | `/my-static` | Static page with assets | 161 | | `/my-static-2` | Another static page | 162 | | `/my-static-2/product` | Product page | 163 | | `/user/:id` | Dynamic user page | 164 | | `/user/:id/post` | Nested dynamic user post page | 165 | 166 | ## File Structure 167 | 168 | ``` 169 | file-base-static-page-routing-app/ 170 | ├── README.md # This file 171 | ├── index.ts # Server entry point 172 | ├── api.test.ts # Test suite (API only) 173 | ├── api/ # API routes 174 | │ └── products/ 175 | │ ├── route.ts # GET/POST /api/products 176 | │ ├── detail/ 177 | │ │ └── route.ts # GET /api/products/detail 178 | │ └── [id]/ 179 | │ └── route.ts # GET /api/products/:id 180 | ├── pages/ # Static pages 181 | │ ├── index.html # Home page 182 | │ ├── my-static/ 183 | │ │ ├── index.html # Static page 184 | │ │ ├── app.css # CSS file 185 | │ │ └── app.js # JavaScript file 186 | │ ├── my-static-2/ 187 | │ │ ├── index.html # Another static page 188 | │ │ └── product.html # Product page 189 | │ └── user/ 190 | │ └── [id]/ 191 | │ ├── index.html # User page 192 | │ └── post/ 193 | │ └── index.html # User post page 194 | ├── assets/ # Static assets 195 | │ ├── css/ 196 | │ │ └── app.css # Global CSS 197 | │ └── js/ 198 | │ └── app.js # Global JavaScript 199 | └── middleware/ 200 | └── logger.ts # Global logger middleware 201 | ``` 202 | 203 | ## Key Concepts 204 | 205 | 1. **API Routes**: RESTful API endpoints for data operations 206 | 2. **Static Pages**: HTML pages served from file structure 207 | 3. **Assets**: CSS and JavaScript files served statically 208 | 4. **Coexistence**: API and static pages working together 209 | 5. **File Structure**: Separate directories for API and pages 210 | 211 | ## Troubleshooting 212 | 213 | ### Server Not Running Error 214 | 215 | If you see: 216 | 217 | ``` 218 | ❌ Server is not running! 219 | ``` 220 | 221 | **Solution**: Start the server first: 222 | 223 | ```bash 224 | bun run examples/file-base-static-page-routing-app/index.ts 225 | ``` 226 | 227 | ### Static Pages Not Loading 228 | 229 | If static pages are not loading: 230 | 231 | 1. **Check file structure**: Ensure HTML files are in `pages/` directory 232 | 2. **Check file names**: Static pages should be named `index.html` 233 | 3. **Check server logs**: Look for page loading messages 234 | 4. **Check page directory**: Ensure `pageDir` is correctly configured 235 | 236 | ### API Routes Not Working 237 | 238 | If API routes are not working: 239 | 240 | 1. **Check file structure**: Ensure `route.ts` files are in `api/` directory 241 | 2. **Check file names**: Route files must be named `route.ts` 242 | 3. **Check server logs**: Look for route loading messages 243 | 4. **Check API directory**: Ensure `apiDir` is correctly configured 244 | 245 | -------------------------------------------------------------------------------- /packages/burger-api/examples/route-specific-middleware/README.md: -------------------------------------------------------------------------------- 1 | # Route-Specific Middleware Example 2 | 3 | This example demonstrates route-specific middleware in burger-api, showing 4 | how to apply middleware to specific routes in addition to global middleware. 5 | 6 | ## Overview 7 | 8 | This example includes: 9 | 10 | - **Global middleware** - Applied to all routes 11 | - **Route-specific middleware** - Applied only to specific routes 12 | - **Middleware combination** - Global and route-specific middleware working 13 | together 14 | - **Route groups** - Using `(group)` folders to organize routes 15 | 16 | ## Features Demonstrated 17 | 18 | ### 1. Global Middleware 19 | 20 | - Applied to all routes 21 | - Executed before route-specific middleware 22 | - Defined in `globalMiddleware` option 23 | 24 | ### 2. Route-Specific Middleware 25 | 26 | - Applied only to specific routes 27 | - Executed after global middleware 28 | - Defined in route file as `export const middleware` 29 | 30 | ### 3. Middleware Combination 31 | 32 | - Global middleware executes first 33 | - Route-specific middleware executes after 34 | - Both can modify request/response 35 | 36 | ## Running the Example 37 | 38 | ### Step 1: Start the Server 39 | 40 | In your terminal, navigate to the project root and run: 41 | 42 | ```bash 43 | bun run examples/route-specific-middleware/index.ts 44 | ``` 45 | 46 | You should see: 47 | 48 | ``` 49 | Loading route: /api/products 50 | Loading route: /api/products/detail 51 | Loading route: /api/profile/:id 52 | ✨ Server is running on port: 4000 53 | ``` 54 | 55 | ### Step 2: Test the API 56 | 57 | Open another terminal and test the endpoints: 58 | 59 | ```bash 60 | # Get products list (global + route-specific middleware) 61 | curl http://localhost:4000/api/products 62 | 63 | # Get products with query parameters 64 | curl "http://localhost:4000/api/products?search=test" 65 | 66 | # Create a product 67 | curl -X POST http://localhost:4000/api/products \ 68 | -H "Content-Type: application/json" \ 69 | -d '{"name": "Test Product", "price": 99.99}' 70 | 71 | # Get product detail (global + route-specific middleware) 72 | curl http://localhost:4000/api/products/detail 73 | 74 | # Get profile by ID (global + route-specific middleware) 75 | curl http://localhost:4000/api/profile/1 76 | ``` 77 | 78 | ### Step 3: Check Middleware Execution 79 | 80 | Watch the server terminal to see middleware execution logs: 81 | 82 | - Global middleware logs for all requests 83 | - Route-specific middleware logs for specific routes 84 | 85 | ## Running Tests 86 | 87 | This example includes a comprehensive test suite using Bun's built-in test 88 | runner. 89 | 90 | ### Prerequisites 91 | 92 | 1. **Start the server first** (in one terminal): 93 | 94 | ```bash 95 | bun run examples/route-specific-middleware/index.ts 96 | ``` 97 | 98 | 2. **Run tests** (in another terminal): 99 | 100 | ```bash 101 | bun test examples/route-specific-middleware/api.test.ts 102 | ``` 103 | 104 | ### Test Commands 105 | 106 | #### Run All Tests 107 | 108 | ```bash 109 | bun test examples/route-specific-middleware/api.test.ts 110 | ``` 111 | 112 | #### Run Tests in Watch Mode 113 | 114 | ```bash 115 | bun test --watch examples/route-specific-middleware/api.test.ts 116 | ``` 117 | 118 | #### Run Specific Test Group 119 | 120 | ```bash 121 | # Run only Products tests 122 | bun test --test-name-pattern "Products" examples/route-specific-middleware/api.test.ts 123 | 124 | # Run only Profile tests 125 | bun test --test-name-pattern "Profile" examples/route-specific-middleware/api.test.ts 126 | 127 | # Run only Middleware tests 128 | bun test --test-name-pattern "Middleware" examples/route-specific-middleware/api.test.ts 129 | ``` 130 | 131 | ## Test Coverage 132 | 133 | The test suite includes **12 tests** covering: 134 | 135 | ### ✅ Products API (5 tests) 136 | 137 | - GET products list 138 | - Query parameters handling 139 | - POST product creation 140 | - GET product detail 141 | - Route-specific middleware execution 142 | 143 | ### ✅ Profile API (3 tests) 144 | 145 | - GET profile by ID 146 | - Different profile IDs 147 | - Route-specific middleware execution 148 | 149 | ### ✅ Middleware Behavior (3 tests) 150 | 151 | - Global middleware execution 152 | - Route-specific middleware execution 153 | - Middleware combination 154 | 155 | ### ✅ Error Handling (2 tests) 156 | 157 | - 404 for non-existent routes 158 | - 404 for invalid nested routes 159 | 160 | ## API Endpoints 161 | 162 | | Method | Endpoint | Description | Middleware | 163 | | ------ | ----------------------- | ------------------------------ | ----------------------------- | 164 | | GET | `/api/products` | Get products list | Global + Route-specific | 165 | | POST | `/api/products` | Create a new product | Global + Route-specific | 166 | | GET | `/api/products/detail` | Get product detail | Global + Route-specific | 167 | | GET | `/api/profile/:id` | Get profile by ID | Global + Route-specific | 168 | 169 | ## Middleware Execution Order 170 | 171 | 1. **Global middleware** (executes first) 172 | 2. **Route-specific middleware** (executes after global) 173 | 3. **Route handler** (executes last) 174 | 175 | ## File Structure 176 | 177 | ``` 178 | route-specific-middleware/ 179 | ├── README.md # This file 180 | ├── index.ts # Server entry point 181 | ├── api.test.ts # Test suite 182 | ├── api/ # API routes 183 | │ └── (group)/ # Route group (ignored in path) 184 | │ ├── products/ 185 | │ │ ├── route.ts # GET/POST /api/products (with middleware) 186 | │ │ └── detail/ 187 | │ │ └── route.ts # GET /api/products/detail (with middleware) 188 | │ └── profile/ 189 | │ └── [id]/ 190 | │ └── route.ts # GET /api/profile/:id (with middleware) 191 | └── middleware/ 192 | └── index.ts # Global middleware 193 | ``` 194 | 195 | ## Key Concepts 196 | 197 | 1. **Global Middleware**: Applied to all routes 198 | 2. **Route-Specific Middleware**: Applied only to specific routes 199 | 3. **Middleware Order**: Global executes before route-specific 200 | 4. **Middleware Combination**: Both can be used together 201 | 5. **Request/Response Modification**: Middleware can modify request/response 202 | 203 | ## Middleware Example 204 | 205 | ### Global Middleware 206 | 207 | ```typescript 208 | // middleware/index.ts 209 | export const globalMiddleware1: Middleware = (req: BurgerRequest): BurgerNext => { 210 | console.log('Global middleware executed for request:', req.url); 211 | return undefined; 212 | }; 213 | ``` 214 | 215 | ### Route-Specific Middleware 216 | 217 | ```typescript 218 | // api/products/route.ts 219 | export const middleware: Middleware[] = [ 220 | (req: BurgerRequest): BurgerNext => { 221 | console.log('Products Route-specific middleware executed'); 222 | return undefined; 223 | }, 224 | ]; 225 | ``` 226 | 227 | ## Troubleshooting 228 | 229 | ### Server Not Running Error 230 | 231 | If you see: 232 | 233 | ``` 234 | ❌ Server is not running! 235 | ``` 236 | 237 | **Solution**: Start the server first: 238 | 239 | ```bash 240 | bun run examples/route-specific-middleware/index.ts 241 | ``` 242 | 243 | ### Middleware Not Executing 244 | 245 | If middleware is not executing: 246 | 247 | 1. **Check middleware definition**: Ensure middleware is correctly defined 248 | 2. **Check server logs**: Look for middleware execution messages 249 | 3. **Check route files**: Ensure route-specific middleware is exported 250 | 4. **Check global middleware**: Ensure global middleware is configured 251 | 252 | -------------------------------------------------------------------------------- /ecosystem/middlewares/cache/cache.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware, BurgerRequest, BurgerNext } from 'burger-api'; 2 | 3 | /** 4 | * Configuration options for the cache control middleware. 5 | */ 6 | export interface CacheControlOptions { 7 | /** 8 | * Cache control directive. 9 | * @default 'no-cache' 10 | */ 11 | directive?: 12 | | 'public' 13 | | 'private' 14 | | 'no-cache' 15 | | 'no-store' 16 | | 'must-revalidate'; 17 | 18 | /** 19 | * Max age in seconds for cache. 20 | * @default undefined 21 | */ 22 | maxAge?: number; 23 | 24 | /** 25 | * S-maxage in seconds for shared caches (CDNs). 26 | * @default undefined 27 | */ 28 | sMaxAge?: number; 29 | 30 | /** 31 | * Must revalidate after becoming stale. 32 | * @default false 33 | */ 34 | mustRevalidate?: boolean; 35 | 36 | /** 37 | * Proxy revalidate. 38 | * @default false 39 | */ 40 | proxyRevalidate?: boolean; 41 | 42 | /** 43 | * Immutable - content will never change. 44 | * @default false 45 | */ 46 | immutable?: boolean; 47 | 48 | /** 49 | * No transform - intermediaries shouldn't modify the response. 50 | * @default false 51 | */ 52 | noTransform?: boolean; 53 | 54 | /** 55 | * Custom Cache-Control value. If provided, overrides other options. 56 | * @default undefined 57 | */ 58 | custom?: string; 59 | 60 | /** 61 | * Add ETag header for conditional requests. 62 | * @default false 63 | */ 64 | etag?: boolean; 65 | 66 | /** 67 | * Add Vary header to specify cache key factors. 68 | * @default undefined 69 | */ 70 | vary?: string | string[]; 71 | } 72 | 73 | /** 74 | * Creates a cache control middleware for HTTP caching. 75 | * 76 | * This middleware sets Cache-Control and related headers to control how responses 77 | * are cached by browsers, CDNs, and proxy servers. 78 | * 79 | * @param options - Configuration options for cache control 80 | * @returns A middleware function that adds cache headers to responses 81 | * 82 | * @example 83 | * ```typescript 84 | * // No caching (default) 85 | * const noCache = cacheControl(); 86 | * 87 | * // Cache for 1 hour 88 | * const cache1Hour = cacheControl({ 89 | * directive: 'public', 90 | * maxAge: 3600 91 | * }); 92 | * 93 | * // Private cache with revalidation 94 | * const privateCache = cacheControl({ 95 | * directive: 'private', 96 | * maxAge: 300, 97 | * mustRevalidate: true 98 | * }); 99 | * 100 | * // Immutable assets 101 | * const immutableCache = cacheControl({ 102 | * directive: 'public', 103 | * maxAge: 31536000, // 1 year 104 | * immutable: true 105 | * }); 106 | * ``` 107 | */ 108 | export function cacheControl(options: CacheControlOptions = {}): Middleware { 109 | const { 110 | directive = 'no-cache', 111 | maxAge, 112 | sMaxAge, 113 | mustRevalidate = false, 114 | proxyRevalidate = false, 115 | immutable = false, 116 | noTransform = false, 117 | custom, 118 | etag = false, 119 | vary, 120 | } = options; 121 | 122 | return (_req: BurgerRequest): BurgerNext => { 123 | // Transform response to add cache headers 124 | return async (response: Response): Promise => { 125 | const headers = new Headers(response.headers); 126 | 127 | // Build Cache-Control header 128 | let cacheControlValue: string; 129 | 130 | if (custom) { 131 | // Use custom value if provided 132 | cacheControlValue = custom; 133 | } else { 134 | // Build from options 135 | const parts: string[] = [directive]; 136 | 137 | if (maxAge !== undefined) { 138 | parts.push(`max-age=${maxAge}`); 139 | } 140 | 141 | if (sMaxAge !== undefined) { 142 | parts.push(`s-maxage=${sMaxAge}`); 143 | } 144 | 145 | if (mustRevalidate) { 146 | parts.push('must-revalidate'); 147 | } 148 | 149 | if (proxyRevalidate) { 150 | parts.push('proxy-revalidate'); 151 | } 152 | 153 | if (immutable) { 154 | parts.push('immutable'); 155 | } 156 | 157 | if (noTransform) { 158 | parts.push('no-transform'); 159 | } 160 | 161 | cacheControlValue = parts.join(', '); 162 | } 163 | 164 | headers.set('Cache-Control', cacheControlValue); 165 | 166 | // Add ETag if enabled 167 | if (etag) { 168 | const body = await response.text(); 169 | const hash = await generateETag(body); 170 | headers.set('ETag', `"${hash}"`); 171 | 172 | // Recreate response with body 173 | return new Response(body, { 174 | status: response.status, 175 | statusText: response.statusText, 176 | headers, 177 | }); 178 | } 179 | 180 | // Add Vary header if specified 181 | if (vary) { 182 | const varyValue = Array.isArray(vary) ? vary.join(', ') : vary; 183 | headers.set('Vary', varyValue); 184 | } 185 | 186 | return new Response(response.body, { 187 | status: response.status, 188 | statusText: response.statusText, 189 | headers, 190 | }); 191 | }; 192 | }; 193 | } 194 | 195 | /** 196 | * Preset: No caching (default, secure) 197 | */ 198 | export function noCache(): Middleware { 199 | return cacheControl({ 200 | directive: 'no-store', 201 | custom: 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0', 202 | }); 203 | } 204 | 205 | /** 206 | * Preset: Public cache with configurable max-age 207 | */ 208 | export function publicCache(maxAge: number = 3600): Middleware { 209 | return cacheControl({ 210 | directive: 'public', 211 | maxAge, 212 | }); 213 | } 214 | 215 | /** 216 | * Preset: Private cache (for user-specific data) 217 | */ 218 | export function privateCache(maxAge: number = 300): Middleware { 219 | return cacheControl({ 220 | directive: 'private', 221 | maxAge, 222 | mustRevalidate: true, 223 | }); 224 | } 225 | 226 | /** 227 | * Preset: Immutable cache (for static assets with fingerprints) 228 | */ 229 | export function immutableCache(): Middleware { 230 | return cacheControl({ 231 | directive: 'public', 232 | maxAge: 31536000, // 1 year 233 | immutable: true, 234 | }); 235 | } 236 | 237 | /** 238 | * Preset: CDN cache with different browser/CDN durations 239 | */ 240 | export function cdnCache(browserMaxAge: number = 300, cdnMaxAge: number = 3600): Middleware { 241 | return cacheControl({ 242 | directive: 'public', 243 | maxAge: browserMaxAge, 244 | sMaxAge: cdnMaxAge, 245 | mustRevalidate: true, 246 | }); 247 | } 248 | 249 | /** 250 | * Generate ETag hash from content. 251 | */ 252 | async function generateETag(content: string): Promise { 253 | const encoder = new TextEncoder(); 254 | const data = encoder.encode(content); 255 | const hashBuffer = await crypto.subtle.digest('SHA-256', data); 256 | const hashArray = Array.from(new Uint8Array(hashBuffer)); 257 | const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); 258 | return hashHex.substring(0, 16); // Use first 16 characters 259 | } 260 | 261 | --------------------------------------------------------------------------------