Skip to main content

Filter OpenAPI Script

filter-openapi.mjs adalah Node.js script yang digunakan untuk mengambil OpenAPI specification dari remote source, memfilternya berdasarkan tags yang ditentukan, dan menyimpan hasilnya ke file lokal.

📋 Overview

🔧 Fungsi Utama

Script ini melakukan proses berikut:

  1. Fetch OpenAPI specification dari remote URL
  2. Filter API endpoints berdasarkan tags yang diizinkan
  3. Resolve dependencies dari filtered endpoints
  4. Save hasil filter ke file JSON lokal

🎯 Tujuan Penggunaan

  • Reduce Bundle Size: Menghindari generate code untuk endpoint yang tidak digunakan
  • Faster Generation: Kubb lebih cepat memproses OpenAPI spec yang lebih kecil
  • Organized Development: Fokus hanya pada API yang relevan untuk project

⚙️ Konfigurasi

Environment Variables

VariableDefaultDescription
SWAGGER_URLhttps://dev-sisappra.tsgitdev.com/api/v1/docs.jsonSource OpenAPI URL
FILTERED_PATH./sisappra.filtered.jsonOutput file path

Tags yang Diizinkan

const KEEP_TAGS = new Set([
'Auth - Pengguna',
'Keuangan - Inventarisasi Aset',
'Keuangan - Pengelolaan Gudang Induk',
'Master Keuangan - Parameter',
'Master Keuangan - Jenis Barang',
'Master Keuangan - Kondisi Aset',
'Master Keuangan - Status Pengadaan Aset',
'Master Keuangan - Ruangan',
'Master Parameter',
'Meta Data Pelanggar',
'Laporan Satu Data Pelanggar - Surat',
]);

🔄 Alur Kerja

1. Fetch OpenAPI Specification

async function fetchSwaggerJson(url) {
console.log(`Fetching OpenAPI spec from: ${url}`);

const response = await fetch(url, {
redirect: 'follow', // Follow HTTP redirects
headers: {
'User-Agent': 'filter-openapi/1.0.0'
}
});

if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
}

return await response.json();
}

2. Filter Paths by Tags

function filterOpenApiByTags(swagger, keepTags) {
const filteredPaths = {};

for (const [path, pathItem] of Object.entries(swagger.paths)) {
const filteredOperations = {};

for (const [method, operation] of Object.entries(pathItem)) {
// Skip non-operation items (parameters, etc.)
if (typeof operation !== 'object' || !operation.tags) continue;

// Check if any tag matches keepTags
if (operation.tags.some(tag => keepTags.has(tag))) {
filteredOperations[method] = operation;
}
}

// Keep path if it has filtered operations
if (Object.keys(filteredOperations).length > 0) {
filteredPaths[path] = filteredOperations;
}
}

return { ...swagger, paths: filteredPaths };
}

3. Resolve Dependencies

function resolveRefs(swagger, refsToResolve = new Set()) {
const allRefs = new Set(refsToResolve);
let changed = true;

// Iteratively resolve all transitive dependencies
while (changed) {
changed = false;

// Scan document for new references
const newRefs = extractRefs(swagger);

for (const ref of newRefs) {
if (!allRefs.has(ref)) {
allRefs.add(ref);
changed = true;
}
}
}

return allRefs;
}

4. Build Filtered Document

function buildFilteredDoc(original, filteredPaths, requiredRefs) {
const filtered = {
...original,
paths: filteredPaths
};

// Only keep required schemas
if (original.components?.schemas) {
filtered.components = {
...original.components,
schemas: {}
};

for (const ref of requiredRefs) {
const schemaName = extractSchemaName(ref);
if (original.components.schemas[schemaName]) {
filtered.components.schemas[schemaName] = original.components.schemas[schemaName];
}
}
}

return filtered;
}

🚀 Penggunaan

Basic Usage

# Run dengan default settings
node scripts/filter-openapi.mjs

# Output: ./sisappra.filtered.json

Custom Environment

# Custom OpenAPI source
export SWAGGER_URL="https://api.example.com/docs.json"

# Custom output path
export FILTERED_PATH="./custom-filtered.json"

# Run script
node scripts/filter-openapi.mjs

Integration dengan Kubb

# Complete workflow
node scripts/filter-openapi.mjs && npx kubb build --config kubb.config.ts

Package Scripts

{
"scripts": {
"api:filter": "node scripts/filter-openapi.mjs",
"api:generate": "npm run api:filter && npx kubb build",
"api:watch": "npx kubb build --watch",
"api:clean": "rm -rf sisappra.filtered.json gen && npm run api:generate"
}
}

📊 Output Structure

Generated File Format

{
"openapi": "3.0.0",
"info": {
"title": "SISAPPRA API",
"version": "1.0.0"
},
"paths": {
"/api/v1/auth/pengguna": {
"get": {
"tags": ["Auth - Pengguna"],
"summary": "Get pengguna list",
"responses": { ... }
}
}
},
"components": {
"schemas": {
"Pengguna": { ... },
"PenggunaRequest": { ... }
}
}
}

Reduction Statistics

Script ini biasanya mengurangi ukuran OpenAPI spec hingga 80-90%:

Original spec: 2.5MB (1,200+ endpoints)
Filtered spec: 350KB (150+ endpoints)
Reduction: 86% smaller

🔧 Advanced Usage

Custom Tags Filtering

Untuk menambah atau mengubah tags yang diizinkan:

const KEEP_TAGS = new Set([
// Existing tags...
'New API Group',
'Another Feature'
]);

Multiple Environment Support

// Script untuk multiple environments
const environments = {
development: 'https://dev-api.example.com/docs.json',
staging: 'https://staging-api.example.com/docs.json',
production: 'https://api.example.com/docs.json'
};

const env = process.env.NODE_ENV || 'development';
const swaggerUrl = environments[env];

Validation Script

// Tambahkan validasi setelah filter
function validateFilteredDoc(filtered, original) {
const filteredPaths = Object.keys(filtered.paths || {});
const originalTags = new Set();

// Extract all tags from original
Object.values(original.paths || {}).forEach(pathItem => {
Object.values(pathItem).forEach(operation => {
if (operation.tags) {
operation.tags.forEach(tag => originalTags.add(tag));
}
});
});

console.log(`Filtered ${filteredPaths.length} paths`);
console.log(`From ${originalTags.size} tag groups`);

return filteredPaths.length > 0;
}

🚨 Error Handling

Common Errors

Network Error:

Error: Failed to fetch: 404 Not Found

Solution: Check SWAGGER_URL environment variable

Invalid JSON:

Error: Unexpected token < in JSON at position 0

Solution: API endpoint mungkin return HTML error page

No Matching Tags:

Warning: No paths matched the keepTags

Solution: Check if tags in KEEP_TAGS match API spec

Debug Mode

// Tambahkan debug logging
function debugLog(message, data) {
if (process.env.DEBUG) {
console.log(`[DEBUG] ${message}`, data);
}
}

Error Recovery

// Retry mechanism for network failures
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchSwaggerJson(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`Retry ${i + 1}/${maxRetries}...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

🔍 Monitoring & Logging

Progress Logging

# Sample output
Fetching OpenAPI spec from: https://dev-sisappra.tsgitdev.com/api/v1/docs.json
Original spec has 1,247 paths
Filtered to 156 paths (12.5% of original)
Found 89 unique schema references
Required 45 schemas after dependency resolution
Written filtered spec to: ./sisappra.filtered.json

Statistics Collection

function collectStats(original, filtered) {
return {
originalPaths: Object.keys(original.paths || {}).length,
filteredPaths: Object.keys(filtered.paths || {}).length,
originalSchemas: Object.keys(original.components?.schemas || {}).length,
filteredSchemas: Object.keys(filtered.components?.schemas || {}).length,
reduction: Math.round(
(1 - Object.keys(filtered.paths || {}).length / Object.keys(original.paths || {}).length) * 100
)
};
}

📚 Best Practices

1. Version Control

# Add .gitignore entry
echo "sisappra.filtered.json" >> .gitignore
echo "gen/" >> .gitignore

2. CI/CD Integration

# GitHub Actions example
- name: Generate API client
run: |
npm run api:generate
git add gen/
git commit -m "chore: update generated API client"

3. Team Coordination

  • Communication saat ada perubahan tags
  • Documentation untuk new API endpoints
  • Regular updates untuk OpenAPI spec

4. Performance Optimization

  • Cache filtered spec untuk development
  • Use conditional generation di CI/CD
  • Monitor generation time

🔗 Integrations

Dengan Development Workflow

# Pre-commit hook
#!/bin/sh
# .git/hooks/pre-commit
npm run api:generate
git add gen/

Dengan CI/CD Pipeline

# Azure DevOps / GitHub Actions
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Generate API client
run: npm run api:generate

- name: Build application
run: npm run build

Dengan Monitoring

// Health check untuk API generation
async function healthCheck() {
try {
const spec = await fetchSwaggerJson(process.env.SWAGGER_URL);
const filtered = filterOpenApiByTags(spec, KEEP_TAGS);

if (Object.keys(filtered.paths).length === 0) {
throw new Error('No matching paths found');
}

return { status: 'healthy', paths: Object.keys(filtered.paths).length };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
}