Claude
Skills
Sign in
Back

bruno-collection-generator

Included with Lifetime
$97 forever

Generates Bruno collection files (.bru) from Express, Next.js, Fastify, or other API routes. Creates organized collections with environments, authentication, and folder structure for the open-source Bruno API client. Use when users request "generate bruno collection", "bruno api testing", "create bru files", or "bruno import".

Web Dev

What this skill does


# Bruno Collection Generator

Generate Bruno collection files for the open-source, Git-friendly API client.

## Core Workflow

1. **Scan routes**: Find all API route definitions
2. **Extract metadata**: Methods, paths, params, bodies
3. **Create collection**: Initialize bruno.json manifest
4. **Generate .bru files**: One file per request
5. **Organize folders**: Group by resource
6. **Add environments**: Dev, staging, production

## Bruno Collection Structure

```
collection/
├── bruno.json              # Collection manifest
├── environments/
│   ├── Development.bru
│   ├── Staging.bru
│   └── Production.bru
├── users/
│   ├── folder.bru
│   ├── get-users.bru
│   ├── get-user.bru
│   ├── create-user.bru
│   ├── update-user.bru
│   └── delete-user.bru
├── auth/
│   ├── folder.bru
│   ├── login.bru
│   ├── register.bru
│   └── logout.bru
└── products/
    ├── folder.bru
    └── ...
```

## bruno.json Manifest

```json
{
  "version": "1",
  "name": "My API",
  "type": "collection",
  "ignore": ["node_modules", ".git"]
}
```

## .bru File Syntax

```bru
meta {
  name: Get Users
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/users
  body: none
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

query {
  page: 1
  limit: 10
}

headers {
  Accept: application/json
}

docs {
  Retrieve a paginated list of users.
}
```

## Generator Script

```typescript
// scripts/generate-bruno.ts
import * as fs from "fs";
import * as path from "path";

interface RouteInfo {
  method: string;
  path: string;
  name: string;
  description?: string;
  body?: object;
  queryParams?: { name: string; value: string }[];
  auth?: boolean;
}

interface BrunoOptions {
  collectionName: string;
  outputDir: string;
  baseUrl: string;
  authType?: "bearer" | "basic" | "apikey";
}

function generateBrunoCollection(
  routes: RouteInfo[],
  options: BrunoOptions
): void {
  const { outputDir, collectionName } = options;

  // Create output directory
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  // Create bruno.json
  const manifest = {
    version: "1",
    name: collectionName,
    type: "collection",
    ignore: ["node_modules", ".git"],
  };
  fs.writeFileSync(
    path.join(outputDir, "bruno.json"),
    JSON.stringify(manifest, null, 2)
  );

  // Create environments
  generateEnvironments(outputDir, options);

  // Group routes by resource
  const groupedRoutes = groupRoutesByResource(routes);

  for (const [resource, resourceRoutes] of Object.entries(groupedRoutes)) {
    const folderPath = path.join(outputDir, resource);

    if (!fs.existsSync(folderPath)) {
      fs.mkdirSync(folderPath, { recursive: true });
    }

    // Create folder.bru
    const folderBru = `meta {\n  name: ${capitalize(resource)}\n}\n`;
    fs.writeFileSync(path.join(folderPath, "folder.bru"), folderBru);

    // Create request files
    let seq = 1;
    for (const route of resourceRoutes) {
      const fileName = generateFileName(route);
      const content = generateBruFile(route, seq++, options);
      fs.writeFileSync(path.join(folderPath, `${fileName}.bru`), content);
    }
  }
}

function generateBruFile(
  route: RouteInfo,
  seq: number,
  options: BrunoOptions
): string {
  const lines: string[] = [];

  // Meta section
  lines.push("meta {");
  lines.push(`  name: ${route.name}`);
  lines.push("  type: http");
  lines.push(`  seq: ${seq}`);
  lines.push("}");
  lines.push("");

  // Request section
  const method = route.method.toLowerCase();
  const urlPath = route.path.replace(/:(\w+)/g, "{{$1}}");

  lines.push(`${method} {`);
  lines.push(`  url: {{baseUrl}}${urlPath}`);

  if (["post", "put", "patch"].includes(method) && route.body) {
    lines.push("  body: json");
  } else {
    lines.push("  body: none");
  }

  if (route.auth && options.authType) {
    lines.push(`  auth: ${options.authType}`);
  } else {
    lines.push("  auth: none");
  }

  lines.push("}");
  lines.push("");

  // Auth section
  if (route.auth && options.authType === "bearer") {
    lines.push("auth:bearer {");
    lines.push("  token: {{authToken}}");
    lines.push("}");
    lines.push("");
  } else if (route.auth && options.authType === "basic") {
    lines.push("auth:basic {");
    lines.push("  username: {{username}}");
    lines.push("  password: {{password}}");
    lines.push("}");
    lines.push("");
  }

  // Query params
  if (route.queryParams?.length) {
    lines.push("query {");
    for (const param of route.queryParams) {
      lines.push(`  ${param.name}: ${param.value}`);
    }
    lines.push("}");
    lines.push("");
  }

  // Headers
  lines.push("headers {");
  lines.push("  Accept: application/json");
  if (["post", "put", "patch"].includes(method)) {
    lines.push("  Content-Type: application/json");
  }
  lines.push("}");
  lines.push("");

  // Body
  if (["post", "put", "patch"].includes(method) && route.body) {
    lines.push("body:json {");
    lines.push(JSON.stringify(route.body, null, 2));
    lines.push("}");
    lines.push("");
  }

  // Docs
  if (route.description) {
    lines.push("docs {");
    lines.push(`  ${route.description}`);
    lines.push("}");
  }

  return lines.join("\n");
}

function generateEnvironments(outputDir: string, options: BrunoOptions): void {
  const envsDir = path.join(outputDir, "environments");

  if (!fs.existsSync(envsDir)) {
    fs.mkdirSync(envsDir, { recursive: true });
  }

  const environments = [
    { name: "Development", baseUrl: "http://localhost:3000/api" },
    { name: "Staging", baseUrl: "https://staging-api.example.com" },
    { name: "Production", baseUrl: "https://api.example.com" },
  ];

  for (const env of environments) {
    const content = `vars {
  baseUrl: ${env.baseUrl}
  authToken:
}

vars:secret [
  authToken
]
`;
    fs.writeFileSync(path.join(envsDir, `${env.name}.bru`), content);
  }
}

function generateFileName(route: RouteInfo): string {
  return route.name.toLowerCase().replace(/\s+/g, "-");
}

function groupRoutesByResource(
  routes: RouteInfo[]
): Record<string, RouteInfo[]> {
  const groups: Record<string, RouteInfo[]> = {};

  for (const route of routes) {
    const parts = route.path.split("/").filter(Boolean);
    const resource = parts[0] || "api";

    if (!groups[resource]) {
      groups[resource] = [];
    }
    groups[resource].push(route);
  }

  return groups;
}

function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
```

## Complete Example Files

### bruno.json

```json
{
  "version": "1",
  "name": "My API",
  "type": "collection",
  "ignore": ["node_modules", ".git"]
}
```

### environments/Development.bru

```bru
vars {
  baseUrl: http://localhost:3000/api
  authToken:
  userId: 1
}

vars:secret [
  authToken
]
```

### environments/Production.bru

```bru
vars {
  baseUrl: https://api.example.com
  authToken:
  userId:
}

vars:secret [
  authToken
]
```

### users/folder.bru

```bru
meta {
  name: Users
}
```

### users/get-users.bru

```bru
meta {
  name: Get Users
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/users
  body: none
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

query {
  page: 1
  limit: 10
}

headers {
  Accept: application/json
}

docs {
  Retrieve a paginated list of users.

  ## Query Parameters
  - page: Page number (default: 1)
  - limit: Items per page (default: 10, max: 100)

  ## Response
  Returns paginated user list with metadata.
}
```

### users/get-user.bru

```bru
meta {
  name: Get User by ID
  type: http
  seq: 2
}

get {
  url: {{baseUrl}}/users/{{userId}}
  body: none
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

headers {
  Accept: application/json
}

docs {
  Retrieve a single user by their ID.
}
```

### users/create-user.bru

```bru
meta {
  name: Create User
  type: http
  seq: 3
}

post {
  url: {{baseUrl}}/users
  body: json
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

headers {
  Accept: application/json
  Content-Type: app

Related in Web Dev