postman-collection-generator
SKILL.md
Postman Collection Generator
Generate importable Postman collections from your API codebase automatically.
Core Workflow
- Scan routes: Find all API route definitions in the codebase
- Extract metadata: Methods, paths, params, request bodies, headers
- Organize endpoints: Group by resource or folder structure
- Generate collection: Create Postman Collection v2.1 JSON
- Add examples: Include request/response examples
- Configure variables: Environment variables for base URL, auth tokens
Supported Frameworks
| Framework | Route Pattern | Detection |
|---|---|---|
| Express | app.get(), router.post() |
Method chaining on app/router |
| Next.js | app/api/**/route.ts |
File-based routing |
| Fastify | fastify.get(), route schema |
Method + schema decorators |
| Hono | app.get(), app.post() |
Similar to Express |
| NestJS | @Get(), @Post() decorators |
Decorator-based |
| Koa | router.get(), router.post() |
Koa-router patterns |
Postman Collection v2.1 Schema
{
"info": {
"name": "API Collection",
"description": "Auto-generated from codebase",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [],
"variable": [],
"auth": {}
}
Express Route Scanner
// scripts/generate-postman.ts
import * as fs from "fs";
import * as path from "path";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
interface RouteInfo {
method: string;
path: string;
name: string;
description?: string;
params?: ParamInfo[];
body?: Record<string, unknown>;
headers?: Record<string, string>;
}
interface ParamInfo {
name: string;
type: "path" | "query";
description?: string;
example?: string;
}
function scanExpressRoutes(filePath: string): RouteInfo[] {
const routes: RouteInfo[] = [];
const code = fs.readFileSync(filePath, "utf-8");
const ast = parse(code, {
sourceType: "module",
plugins: ["typescript"],
});
traverse(ast, {
CallExpression(nodePath) {
const callee = nodePath.node.callee;
if (callee.type === "MemberExpression") {
const method = callee.property.name;
const httpMethods = ["get", "post", "put", "patch", "delete"];
if (httpMethods.includes(method)) {
const args = nodePath.node.arguments;
if (args[0]?.type === "StringLiteral") {
const routePath = args[0].value;
routes.push({
method: method.toUpperCase(),
path: routePath,
name: generateRouteName(method, routePath),
params: extractParams(routePath),
});
}
}
}
},
});
return routes;
}
function extractParams(routePath: string): ParamInfo[] {
const params: ParamInfo[] = [];
const pathParamRegex = /:(\w+)/g;
let match;
while ((match = pathParamRegex.exec(routePath)) !== null) {
params.push({
name: match[1],
type: "path",
example: `{{${match[1]}}}`,
});
}
return params;
}
function generateRouteName(method: string, path: string): string {
const cleanPath = path.replace(/[/:]/g, " ").trim();
return `${method.toUpperCase()} ${cleanPath}`;
}
Next.js App Router Scanner
// scripts/scan-nextjs-routes.ts
import * as fs from "fs";
import * as path from "path";
import { glob } from "glob";
interface NextApiRoute {
method: string;
path: string;
filePath: string;
}
async function scanNextJsRoutes(appDir: string): Promise<NextApiRoute[]> {
const routes: NextApiRoute[] = [];
const routeFiles = await glob(`${appDir}/**/route.{ts,js}`);
for (const file of routeFiles) {
const content = fs.readFileSync(file, "utf-8");
const relativePath = path.relative(appDir, path.dirname(file));
const apiPath = "/" + relativePath.replace(/\\/g, "/");
// Detect exported HTTP methods
const methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
for (const method of methods) {
if (
content.includes(`export async function ${method}`) ||
content.includes(`export function ${method}`) ||
content.includes(`export const ${method}`)
) {
routes.push({
method,
path: convertNextPathToPostman(apiPath),
filePath: file,
});
}
}
}
return routes;
}
function convertNextPathToPostman(nextPath: string): string {
// Convert [param] to :param
return nextPath
.replace(/\[\.\.\.(\w+)\]/g, ":$1*") // [...slug] -> :slug*
.replace(/\[(\w+)\]/g, ":$1"); // [id] -> :id
}
Fastify Route Scanner
// scripts/scan-fastify-routes.ts
interface FastifyRoute {
method: string;
path: string;
schema?: {
body?: object;
querystring?: object;
params?: object;
response?: object;
};
}
function scanFastifyRoutes(filePath: string): FastifyRoute[] {
const routes: FastifyRoute[] = [];
const code = fs.readFileSync(filePath, "utf-8");
// Match fastify.get('/path', { schema: ... }, handler)
const routeRegex =
/fastify\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(\{[\s\S]*?\})\s*,/g;
let match;
while ((match = routeRegex.exec(code)) !== null) {
const [, method, path, optionsStr] = match;
routes.push({
method: method.toUpperCase(),
path,
// Parse schema from options if available
});
}
return routes;
}
Collection Generator
// scripts/generate-collection.ts
interface PostmanCollection {
info: {
name: string;
description: string;
schema: string;
};
item: PostmanItem[];
variable: PostmanVariable[];
auth?: PostmanAuth;
}
interface PostmanItem {
name: string;
request: {
method: string;
header: PostmanHeader[];
url: PostmanUrl;
body?: PostmanBody;
description?: string;
};
response?: PostmanResponse[];
}
interface PostmanUrl {
raw: string;
host: string[];
path: string[];
query?: PostmanQuery[];
variable?: PostmanPathVariable[];
}
interface PostmanVariable {
key: string;
value: string;
type: string;
}
function generatePostmanCollection(
routes: RouteInfo[],
options: {
name: string;
baseUrl: string;
description?: string;
auth?: "bearer" | "basic" | "apikey";
}
): PostmanCollection {
const collection: PostmanCollection = {
info: {
name: options.name,
description: options.description || "Auto-generated API collection",
schema:
"https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [],
variable: [
{ key: "baseUrl", value: options.baseUrl, type: "string" },
{ key: "authToken", value: "", type: "string" },
],
};
// Add auth configuration
if (options.auth === "bearer") {
collection.auth = {
type: "bearer",
bearer: [{ key: "token", value: "{{authToken}}", type: "string" }],
};
}
// Group routes by resource
const groupedRoutes = groupRoutesByResource(routes);
for (const [resource, resourceRoutes] of Object.entries(groupedRoutes)) {
const folder: PostmanItem = {
name: resource,
item: resourceRoutes.map((route) => createPostmanRequest(route)),
};
collection.item.push(folder);
}
return collection;
}
function createPostmanRequest(route: RouteInfo): PostmanItem {
const pathSegments = route.path.split("/").filter(Boolean);
const item: PostmanItem = {
name: route.name,
request: {
method: route.method,
header: [
{ key: "Content-Type", value: "application/json", type: "text" },
],
url: {
raw: `{{baseUrl}}${route.path}`,
host: ["{{baseUrl}}"],
path: pathSegments,
variable: route.params
?.filter((p) => p.type === "path")
.map((p) => ({
key: p.name,
value: p.example || "",
description: p.description,
})),
},
description: route.description,
},
};
// Add request body for POST/PUT/PATCH
if (["POST", "PUT", "PATCH"].includes(route.method) && route.body) {
item.request.body = {
mode: "raw",
raw: JSON.stringify(route.body, null, 2),
options: { raw: { language: "json" } },
};
}
return item;
}
function groupRoutesByResource(
routes: RouteInfo[]
): Record<string, RouteInfo[]> {
const groups: Record<string, RouteInfo[]> = {};
for (const route of routes) {
// Extract resource from path (e.g., /api/users/:id -> users)
const parts = route.path.split("/").filter(Boolean);
const resource = parts[1] || parts[0] || "root";
if (!groups[resource]) {
groups[resource] = [];
}
groups[resource].push(route);
}
return groups;
}
CLI Script
#!/usr/bin/env node
// scripts/postman-gen.ts
import * as fs from "fs";
import * as path from "path";
import { program } from "commander";
program
.name("postman-gen")
.description("Generate Postman collection from API routes")
.option("-f, --framework <type>", "Framework type", "express")
.option("-s, --source <path>", "Source directory", "./src")
.option("-o, --output <path>", "Output file", "./postman-collection.json")
.option("-n, --name <name>", "Collection name", "API Collection")
.option("-b, --base-url <url>", "Base URL", "http://localhost:3000")
.option("-a, --auth <type>", "Auth type (bearer|basic|apikey)")
.parse();
const options = program.opts();
async function main() {
let routes: RouteInfo[] = [];
switch (options.framework) {
case "express":
routes = await scanExpressProject(options.source);
break;
case "nextjs":
routes = await scanNextJsRoutes(path.join(options.source, "app/api"));
break;
case "fastify":
routes = await scanFastifyProject(options.source);
break;
default:
console.error(`Unsupported framework: ${options.framework}`);
process.exit(1);
}
const collection = generatePostmanCollection(routes, {
name: options.name,
baseUrl: options.baseUrl,
auth: options.auth,
});
fs.writeFileSync(options.output, JSON.stringify(collection, null, 2));
console.log(`Generated ${options.output} with ${routes.length} endpoints`);
}
main();
Environment Template
{
"name": "Development",
"values": [
{ "key": "baseUrl", "value": "http://localhost:3000/api", "enabled": true },
{ "key": "authToken", "value": "", "enabled": true, "type": "secret" },
{ "key": "userId", "value": "1", "enabled": true }
]
}
Example Output
{
"info": {
"name": "My API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Users",
"item": [
{
"name": "GET users",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/users",
"host": ["{{baseUrl}}"],
"path": ["users"],
"query": [
{ "key": "page", "value": "1" },
{ "key": "limit", "value": "10" }
]
}
}
},
{
"name": "GET user by ID",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/users/:id",
"host": ["{{baseUrl}}"],
"path": ["users", ":id"],
"variable": [{ "key": "id", "value": "{{userId}}" }]
}
}
},
{
"name": "POST create user",
"request": {
"method": "POST",
"header": [{ "key": "Content-Type", "value": "application/json" }],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\"\n}"
},
"url": {
"raw": "{{baseUrl}}/users",
"host": ["{{baseUrl}}"],
"path": ["users"]
}
}
}
]
}
],
"variable": [
{ "key": "baseUrl", "value": "http://localhost:3000/api" },
{ "key": "authToken", "value": "" }
]
}
Best Practices
- Use variables:
{{baseUrl}},{{authToken}}for flexibility - Group endpoints: Organize by resource/feature folders
- Add descriptions: Document each endpoint's purpose
- Include examples: Pre-fill request bodies with realistic data
- Set up auth: Configure collection-level authentication
- Add tests: Include basic response validation scripts
- Version control: Commit collection JSON to repository
- CI integration: Auto-generate on route changes
Output Checklist
- All routes scanned from codebase
- Endpoints grouped by resource
- Path parameters extracted
- Request bodies included for POST/PUT/PATCH
- Environment variables configured
- Authentication setup (if applicable)
- Collection exported as v2.1 JSON
- Environment template created
Weekly Installs
19
Repository
patricio0312rev/skillsFirst Seen
10 days ago
Installed on
claude-code12
github-copilot12
antigravity11
codex11
trae10
opencode10