├── 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 - BurgerAPIRequest: ${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 |
3 |
4 |
5 |
6 | A modern, high-performance API framework built on Bun.js
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
--------------------------------------------------------------------------------