Agent skill
bruno-collection-generator
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".
Install this agent skill to your Project
npx add-skill https://github.com/patricio0312rev/skills/tree/main/testing/bruno-collection-generator
SKILL.md
Bruno Collection Generator
Generate Bruno collection files for the open-source, Git-friendly API client.
Core Workflow
- Scan routes: Find all API route definitions
- Extract metadata: Methods, paths, params, bodies
- Create collection: Initialize bruno.json manifest
- Generate .bru files: One file per request
- Organize folders: Group by resource
- 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
{
"version": "1",
"name": "My API",
"type": "collection",
"ignore": ["node_modules", ".git"]
}
.bru File Syntax
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
// 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
{
"version": "1",
"name": "My API",
"type": "collection",
"ignore": ["node_modules", ".git"]
}
environments/Development.bru
vars {
baseUrl: http://localhost:3000/api
authToken:
userId: 1
}
vars:secret [
authToken
]
environments/Production.bru
vars {
baseUrl: https://api.example.com
authToken:
userId:
}
vars:secret [
authToken
]
users/folder.bru
meta {
name: Users
}
users/get-users.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
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
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: application/json
}
body:json {
{
"name": "John Doe",
"email": "john@example.com",
"role": "user"
}
}
docs {
Create a new user account.
## Request Body
- name: User's full name (required)
- email: User's email address (required, unique)
- role: User role (optional, default: "user")
}
users/update-user.bru
meta {
name: Update User
type: http
seq: 4
}
put {
url: {{baseUrl}}/users/{{userId}}
body: json
auth: bearer
}
auth:bearer {
token: {{authToken}}
}
headers {
Accept: application/json
Content-Type: application/json
}
body:json {
{
"name": "John Updated",
"email": "john.updated@example.com"
}
}
docs {
Update an existing user.
}
users/delete-user.bru
meta {
name: Delete User
type: http
seq: 5
}
delete {
url: {{baseUrl}}/users/{{userId}}
body: none
auth: bearer
}
auth:bearer {
token: {{authToken}}
}
headers {
Accept: application/json
}
docs {
Delete a user account.
}
auth/login.bru
meta {
name: Login
type: http
seq: 1
}
post {
url: {{baseUrl}}/auth/login
body: json
auth: none
}
headers {
Accept: application/json
Content-Type: application/json
}
body:json {
{
"email": "user@example.com",
"password": "password123"
}
}
script:post-response {
if (res.body.token) {
bru.setEnvVar("authToken", res.body.token);
}
}
docs {
Authenticate user and receive access token.
On successful login, the token is automatically saved
to the authToken environment variable.
}
Pre/Post Request Scripts
script:pre-request {
// Set dynamic values before request
const timestamp = Date.now();
bru.setVar("requestId", `req-${timestamp}`);
}
script:post-response {
// Extract values from response
if (res.body.token) {
bru.setEnvVar("authToken", res.body.token);
}
if (res.body.id) {
bru.setEnvVar("userId", res.body.id);
}
// Log response info
console.log(`Status: ${res.status}`);
console.log(`Response time: ${res.responseTime}ms`);
}
Tests in Bruno
tests {
test("should return 200", function() {
expect(res.status).to.equal(200);
});
test("should return array of users", function() {
expect(res.body.data).to.be.an("array");
});
test("should include pagination", function() {
expect(res.body.meta).to.have.property("page");
expect(res.body.meta).to.have.property("total");
});
}
CLI Script
#!/usr/bin/env node
// scripts/bruno-gen.ts
import * as fs from "fs";
import { program } from "commander";
program
.name("bruno-gen")
.description("Generate Bruno collection from API routes")
.option("-f, --framework <type>", "Framework type", "express")
.option("-s, --source <path>", "Source directory", "./src")
.option("-o, --output <path>", "Output directory", "./bruno-collection")
.option("-n, --name <name>", "Collection name", "My API")
.option("-b, --base-url <url>", "Base URL", "http://localhost:3000/api")
.option("-a, --auth <type>", "Auth type (bearer|basic|apikey)")
.parse();
const options = program.opts();
async function main() {
const routes = await scanRoutes(options.framework, options.source);
generateBrunoCollection(routes, {
collectionName: options.name,
outputDir: options.output,
baseUrl: options.baseUrl,
authType: options.auth,
});
console.log(`Generated Bruno collection in ${options.output}`);
console.log(`Open with: bruno run ${options.output}`);
}
main();
Best Practices
- Git-friendly: Bruno stores everything as plain text files
- Use environments: Store URLs and tokens in environment files
- Secret variables: Mark sensitive vars with
vars:secret - Add docs: Document each request with the docs block
- Pre/post scripts: Automate token extraction and setup
- Add tests: Include assertions in test blocks
- Organize folders: Group related requests together
- Sequence numbers: Order requests logically with
seq
Output Checklist
- bruno.json manifest created
- Environment files for dev/staging/prod
- Folder structure by resource
- folder.bru for each folder
- Request .bru files with proper syntax
- Path parameters use
{{param}}syntax - Query parameters in query block
- Request bodies in body:json block
- Authentication configured
- Documentation in docs block
- Pre/post scripts for token handling
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
rate-limiting-abuse-protection
Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
rbac-permissions-builder
Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
websocket-realtime-builder
Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications".
webhook-receiver-hardener
Secures webhook receivers with signature verification, retry handling, deduplication, idempotency keys, and error responses. Provides verification code, dedupe storage strategy, runbook for incidents. Use when implementing "webhooks", "webhook security", "event receivers", or "third-party integrations".
auth-module-builder
Implements secure authentication patterns including login/registration, session management, JWT tokens, password hashing, cookie settings, and CSRF protection. Provides auth routes, middleware, security configurations, and threat model documentation. Use when building "authentication", "login system", "JWT auth", or "session management".
rest-to-graphql-migrator
Migrates REST APIs to GraphQL incrementally with schema stitching, REST datasources, and gradual endpoint migration. Use when users request "migrate to GraphQL", "REST to GraphQL", "GraphQL wrapper", or "API modernization".
Didn't find tool you were looking for?